LectureNote

Linux Command Line: Linux命令行:

File System: 文件系统:

File System Structure
  • Absolute Paths: start with /绝对路径:以 " /" 开头。
  • Relative Paths: do NOT start with /, relative to the current working directory相对路径:不要以“ / ”开头,相对于当前工作目录

Environment Variables:

Environment Variables: a set of Key/Value pairs passed into every process when launched. Critical variables include:环境变量:在启动进程时传递给每个进程的一组键/值对。关键变量包括:

  • PATH: list of directories to search for programs in[b0]:要搜索程序的目录列表
  • PWD: current working directory:当前工作目录
  • HOME: path to home directory家目录路径
  • HOSTNAME: name of the system系统的名称

Files:

  • Symbolic Links (Soft Links): special type of file that references another file 符号链接(软链接):一种特殊的文件类型,它指向另一个文件。
    • symbolic links to relative paths are relative to the directory containing the link指向相对路径的符号链接相对于包含链接的目录是相对的。
  • Hard Links: a perfect reference to the data/content inside the linked file. They link only to the data of the linked file, not its original path硬链接:是对链接文件内部数据/内容的完美引用。它们仅链接到被链接文件的数据,而不链接到其原始路径。

Different Types of Files:不同类型的文件:

  • -: regular file - :普通文件
  • d: a directory (yes, directories are actually just special files!)d : 一个目录(是的,目录实际上只是一种特殊的文件!)
  • l: a symbolic link (a file that transparently points to another file or directory)`l `: 符号链接(一种透明地指向其他文件或目录的文件)
  • p: is a named pipe (also known as a FIFO)"p " 是一个命名管道(也称为 FIFO)。
  • c: is a character device file (i.e., backed by a hardware device that produces or receives data streams, such as a microphone)`/dev/b0` 是一个字符设备文件(即由硬件设备支持,该硬件设备可以生成或接收数据流,例如麦克风)。
  • b: a block device file (i.e., backed by a hardware device that stores and loads blocks of data, such as a hard drive)`b `: 块设备文件(即由硬件设备支持的文件,该硬件设备用于存储和加载数据块,例如硬盘)
  • s: a unix socket (essentially a local network connection encapsulated in a file)s : 一个 Unix 套接字(本质上是一个封装在文件中的本地网络连接)

Pipes:

  • Unnamed Pipes (|): ethereal channels of communication, often used to direct data from one command to another匿名管道( | ):一种用于通信的无形通道,通常用于将数据从一个命令传输到另一个命令。
  • Named Pipes (FIFOs): used to help facilitate data flow in certain situations. The flow of data is First In First Out (FIFO)命名管道(先进先出队列):用于在某些情况下帮助数据流的传输。数据的传输遵循先进先出的原则(FIFO)。

Useful Commands (CLI):

  • witch programName: will return the absolute path of the program given found by the PATH variable它将返回由  PATH  变量找到的程序的绝对路径。
  • env: prints all the environment variables to the screen`env ` 会将所有环境变量打印到屏幕上。
  • export variable=value: can set new/existing variables to a new value[b0] 可以为现有或新创建的变量设置新的值。
  • ln -s fileToBeLinked symbolicLinkName: creates a symbolic link创建一个符号链接。
  • ln fileToBeLinked hardLinkName: creates a hard link创建硬链接
  • ls -ld: Lists all files in current directory along with the files’ absolute pathls -ld : 列出当前目录下的所有文件及其绝对路径
  • mkfifo fifoName: makes a new FIFO创建一个新的FIFO(先进先出队列)。
  • command < in_file: redirect in_file into command’s input将 `in_file` 重定向到命令的输入中。
  • command > out_file: redirect command’s output into out_file, overwriting it将命令的输出重定向到 out_file 文件中,并覆盖该文件。
  • command >>out_file: redirect the command’s output into out_file, appending to it将命令的输出重定向到 out_file 文件中,并追加到该文件中。
  • command 2>error_file: redirect the command’s errors into error_file, overwriting it将命令的错误重定向到 error_file 中,并覆盖该文件。
  • command 2>>error_file: redirect the command’s errors into error_file, appending to it将命令的错误重定向到 error_file 中,并追加到其中。

Binary Files

ELF Files:

Executable and Linkable Format (ELF): defines a program as it will be loaded and executed in memory for Linux/BSD systems. Allows for a compiler to create and define a program可执行和可链接格式(ELF):定义了Linux/BSD系统上加载并执行的程序在内存中的格式。允许编译器创建并定义程序。

  • is a binary file format containing the program, its data, how the program should be loaded (program/segment headers), and the metadata describing program components (section headers)这是一种二进制文件格式,其中包含程序本身、数据以及程序的加载方式(程序/段头),以及描述程序组件的元数据(节头)。
  • For Windows the equivalent are Portable Executables (PE)对于Windows系统来说,对应的文件格式是Portable Executables(PE)。
  • For MacOS the equivalent are Mach-O对于MacOS,其对应的格式是Mach-O。

Dynamically Linked ELF: The ELF file relies on libraries that also need to be loaded动态链接的ELF文件:该ELF文件依赖于需要加载的库文件。

Program Headers: specify information and define segments needed to prepare the program for execution. The source of information used when loading a file程序头:指定需要的信息并定义为使程序能够执行所需的段。加载文件时使用的信息来源

  • Segments: parts of an ELF file that are loaded into the memory of a computer when that file is executed段:当ELF文件被执行时,加载到计算机内存中的文件部分

Important Entry Types: 重要的入口类型:

  • INTERP: entry type defining the library that should be used to load this ELF into memory[b0] 入口类型定义了应该使用哪个库来将此ELF加载到内存中。
  • LOAD: entry type defining a part of the file that should be loaded into memoryLOAD : 定义文件中应加载到内存中的一部分的入口类型

Section Headers: represent a different view inside an ELF file with a lot more semantic information that is less important for the actual loading process节头:代表ELF文件中一种不同的内部视图,包含更多与实际加载过程关系不大的语义信息。

  • They are not required in an ELF file, they are stored as metadata它们在ELF文件中不是必需的,而是作为元数据存储的。

Important Sections: 重要的部分:

  • .text: the executable code of your program.[b0]:您程序的可执行代码。
  • .plt and .got: used to resolve and dispatch library calls.和:用于解决和分发库的调用。
  • .data: used for pre-initialized global writable data (such as global arrays with initial values).data : 用于预先初始化的全局可写数据(例如具有初始值的全局数组)
  • .rodata: used for global read-only data (such as string constants).rodata :用于全局只读数据(如字符串常量)
  • .bss: used for uninitialized global writable data (such as global arrays without initial values).bss : 用于未初始化的全局可写数据(例如没有初始值的全局数组)

Symbols:

Symbols: binaries and libraries that use dynamically loaded libraries to find libraries, resolve function calls into those libraries, etc 6081

Resources to Interact with ELF:

  • gcc: to make your ELF.制作你的ELF文件。
  • readelf: to parse the ELF header.解析ELF头文件。
  • objdump: to parse the ELF header and disassemble the source code.解析ELF头文件并反汇编源代码。
  • nm: to view your ELF’s symbols.[b0]:查看您的ELF文件的符号。
  • patchelf: to change some ELF properties.[b0]:用来更改一些ELF文件的属性。
  • objcopy: to swap out ELF sections.替换ELF段。
  • strip: to remove otherwise-helpful information (such as symbols).删除原本有用的信息(例如符号)。
  • kaitai struct: to look through your ELF interactively.凯泰结构:用于交互式查看ELF文件。

Useful Commands (Binary):

  • readelf -a programName: will parse out the ELF file, providing information such as headers它会解析ELF文件,提供诸如头文件之类的信息。
  • nm -a programName: will list out all of the symbols associated with a program[b0] 将会列出与程序相关的所有符号。
  • nm -D programName: will list out all of the dynamic imports used at runtime将会列出在运行时使用的所有动态导入。

Lifecycle of a Linux Process

What is a Process:

Every Linux process has: 每个Linux进程都有:

  • State 状态
    • running, waiting, stopped, zombie跑步、等待、停止、僵尸
  • Priority (and other scheduling information)优先级(以及其他调度信息)
  • Parent, Siblings, Children父母、兄弟姐妹、子女
  • Shared Resources 共享资源
    • files, pipes, sockets 文件、管道、套接字
  • Virtual Memory Space 虚拟内存空间
  • Security Context 安全上下文
    • effective uid and gid 有效的UID(用户ID)和GID(组ID)
    • saved uid and gid 已保存的uid和gid
    • capabilities 功能

Virtual Memory: memory dedicated to a specific process虚拟内存:专门分配给某个进程的内存

Physical Memory: memory shared among the whole system物理内存:系统中所有组件共享的内存

libc: a library full of helper functions that is used by almost every program (including common C functions)libc 是一个包含大量辅助函数的库,几乎所有的程序(包括常见的 C 函数)都会使用它。

Process Timeline: 过程时间轴:

  1. Process is created 创建流程
    • Kernel will check for executable permissions
    • Calls execve() to begin loading
  2. Process is loaded 进程已加载
  • Figures out what steps need to be taken to load the file
    • Starts from the beginning of the file, looks for #! (sh-bang) to extract the rest of the line (the interpreter)
      • Will see if the interpreter matches one in the system, and will instead run your file as an argument to the interpreter from #!
    • If there is no #! (not a script) then it will check if the file matches a format in /proc/sys/binfmt_misc
      • If a match is found, then it will execute the interpreter for that format with your file as an argument
    • If it is a dynamically-linked ELF file, the kernel will read the interpreter/loader (collectively loader) defined in the ELF file
      • The interpreter will be loaded and will be given control
      • The interpreter then locates the libraries
        • LD_PRELOAD: environment variable, and anything in /etc/ld.so.preload
        • LD_LIBRARY_PATH: environment variable (can be set in the shell)
        • DT_RUNPATH or DT_RPATH: specified in the binary file (both can be modified with patchelf)
        • /etc/ld.so.conf: system-wide configuration
        • /lib and /usr/lib /lib和/usr/lib
      • The interpreter then runs the libraries
        • May cause more libraries to load if libraries depend on other libraries
        • Will also update relocations during this process
    • If it is a static ELF file, the kernel will just load the file
  • All of this information will be loaded to its own virtual memory space containing (located in /proc/self/maps):
    • the binary 二进制
    • the libraries 库
    • the “heap” “堆”
      • for dynamically allocated memory
    • the “stack” “栈”
      • for function local variables
    • any memory specifically mapped by the program
    • some helper regions 一些辅助区域
    • kernel code in the “upper half” of memory is inaccessible to the process
      • above 0x8000000000000000 on 64-bit architectures
  1. Process is initialized 进程已初始化。
    • Will run any constructors specified in the ELF file将会运行ELF文件中指定的所有构造函数。
  2. Process is launched 进程启动
    • The ELF file automatically calls _libc_start_main() from the libc library, which then calls the program’s main() functionELF文件会自动从libc库中调用 _libc_start_main() ,然后libc库会调用程序中的 main() 函数。
    • Now the code is running现在代码正在运行。
  3. Process reads its arguments and environment程序会读取其参数和环境变量。
  • The int main(int argc, void **argv, void **envp); function will take in three arguments:

    • argc: the loaded objects (binaries and libraries)加载的对象(二进制文件和库文件)
    • argv: command-line arguments in argvargv 中的命令行参数
    • envp: environment variablesenvp : 环境变量

    这个 `b0` 函数将接受三个参数:

  1. Process executes 流程执行
  • To interact with the operating system(OS), the process will use system calls为了与操作系统(OS)进行交互,进程将使用系统调用。
  • For the OS to communicate with the process, signals are used 操作系统通过信号与进程进行通信。
    • Signals will pause process execution and invoke the handler (functions that take in the signal number) 信号会暂停进程的执行,并调用处理程序(即接收信号号码的函数)。
      • If the signal has no handler, the default action is to kill the process
  • Another method of outside interaction is by sharing memory with other processes
    • This requires an initial system call, but then is self sustaining
    • One way is to use a shared memory-mapped file in /dev/shm
  1. Process terminates 进程终止
  • Only two ways a process can terminate:
    • Receive an unhandled signal
    • Calling the exit() system call
  • After termination, processes must be “reaped”
    • A process will remain in a zombie state and take up memory until the wait() function is called by their parent
    • This will return the process’s exit code to the parent and then the process will be freed
      • If this does not happen, the process is re-parented to PID 1 and will remain there until cleaned up

Useful Commands (Viewing Processes):

  • readelf -a fileName | grep interpret: will return the specific loader being used to load the file将会返回用于加载该文件的特定加载器。
  • patchelf --set-interpreter /some/interpreter ./fileName: will forcibly set the interpreter for a provided file[b0] 会强制为指定文件设置解释器。
  • ldd ./fileName: will list the libraries necessary for the file to run, including the interpreter它会列出该文件运行所需的库,包括解释器。
  • strace ./executable uncompiledFile: will “trace” out all of the system calls from when the process is first created to when its terminated它会追踪该进程从创建到终止期间的所有系统调用。
  • ./executable /proc/self/maps: will show the virtual memory storage layout for the executable它将展示可执行文件的虚拟内存存储布局。
  • man 2 open: documentation for all the different system calls所有系统调用的文档
  • du -sb programFile: will output how many bytes a file is`du -sb programFile ` 会输出文件的大小(以字节为单位)。

Sources:

Challenges

Program Interaction

这一部分主要涉及到基本的程序交互方法,包括linux环境下运行程序一些基本方法和知识,除此以外也教了一些基本的shell脚本、python脚本、c语言等知识

embryoio 胚胎难度 真的是这样吗

level1

无密码直接运行

level2

有密码,提示上写了

level3

./embryoio_level3 suvobmbtle

env 命令主要用于显示或设置环境变量,其常见的指令包括:

  1. 显示当前环境变量

    env
  2. 设置环境变量并运行命令

    env VARIABLE_NAME=value your_command_here
  3. 清除所有环境变量并运行命令

    env -i your_command_here
  4. 显示特定环境变量的值

    env VARIABLE_NAME
  5. 以逗号分隔显示多个环境变量的值

    env VARIABLE1,VARIABLE2 your_command_here
  6. 通过-u选项删除特定的环境变量

    env -u VARIABLE_NAME your_command_here
  7. 使用-i选项启动一个不包含任何环境变量的环境

    env -i your_command_here

level4

env -i gizpoq=tkikjioeyj ./embryoio_level4

level7

env -i ./embryoio_level7

level5

hacker@program-interaction~level5:/challenge$ echo "hxhxyvux" > /tmp/cwxhwa
hacker@program-interaction~level5:/challenge$ ./embryoio_level5 < /tmp/cwxhwa

level6

hacker@program-interaction~level6:/challenge$ ./embryoio_level6 > /tmp/tzdetd
hacker@program-interaction~level6:/challenge$ cat /tmp/tzdetd 

level8

echo "/challenge/embryoio_level8" > /tmp/my_script.sh && bash /tmp/my_script.sh

level9

同上,加个密码传参

level10

echo "./embryoio_level10 xolzrhbfsw" > /tmp/my_script.sh && bash my_script.sh

level11

echo "env -i huawoi=hzrutrkycu ./embryoio_level11" > /tmp/my_script.sh && bash /tmp/my_script.sh

level12

hacker@program-interaction~level12:/challenge$ echo "bczijbap" > /tmp/kzgaox
hacker@program-interaction~level12:/challenge$ echo "./embryoio_level12 < /tmp/kzgaox" > /tmp/my_script.sh && bash /tmp/my_script.sh

level13

echo "./embryoio_level13 > /tmp/xtnsry" >/tmp/my_script.sh && bash /tmp/my_script.sh &&cat /tmp/xtnsry

level14

echo "env -i  ./embryoio_level14" > /tmp/my_script.sh && bash /tmp/my_script.sh

level15

Python中运行程序时,有几种常见的方法

  1. 使用subprocess模块:通过subprocess模块可以在Python中执行外部命令和程序。可以使用subprocess.run()函数来执行程序,还可以使用subprocess.Popen()来获得更多控制和灵活性。

    要在Python中执行一个文件,你可以使用subprocess模块中的run函数。这个函数允许你运行外部命令,并等待它完成。以下是一个简单的示例,演示如何使用Python来执行一个名为script.py的Python脚本文件:

    import subprocess
    
    # 指定要执行的文件名
    filename = "script.py"
    
    # 执行文件
    try:
        subprocess.run(["python", filename], check=True)
    except subprocess.CalledProcessError as e:
        print("执行文件时出错:", e)

    这将运行名为script.py的Python脚本。如果希望传递参数给脚本,可以在subprocess.run中的第二个参数中添加额外的参数。例如,如果要传递一个名为arg1的参数,可以这样做:

    subprocess.run(["python", filename, "arg1"], check=True)

    这会将arg1作为参数传递给script.py脚本。

  2. 使用exec语句:使用exec语句可以在当前Python进程中执行另一个Python文件。但是要注意,它会改变当前进程的状态,包括变量、函数等。

  3. 使用import语句:你可以使用import语句来导入一个Python文件作为模块,然后调用它的函数或者执行其中的代码。这种方法更安全,因为它会创建一个新的命名空间,不会直接修改当前进程的状态。

  4. 使用os.system()函数:os模块提供了一个system()函数,它可以在操作系统级别执行命令。但是这个方法不够灵活,并且容易受到操作系统限制。

  5. 使用eval()函数:虽然不推荐,但你也可以使用eval()函数来执行Python代码字符串。这种方法潜在的安全风险很高,应该尽量避免使用。

  6. 使用execfile()函数:在Python 2中,你可以使用execfile()函数执行一个Python文件。但是在Python 3中已经移除了这个函数。

In [1]: import os

In [2]: os.system("./embryoio_level15")
WELCOME! This challenge makes the following asks of you:
- the challenge checks for a specific parent process : ipython

ONWARDS TO GREATNESS!

[INFO] This challenge will now perform a bunch of checks.
[INFO] If you pass these checks, you will receive the flag.
[TEST] Performing checks on the parent process of this process.
[TEST] We will now check that that the process is an interactive ipython instance.

[INFO] Since ipython runs as a script inside python, this will check a few things:
[INFO] 1. That the process itself is python.
[INFO] 2. That the module being run in python is ipython.
[INFO] If the process being checked is just a normal 'ipython', you'll be okay!

[INFO] The process' executable is /usr/bin/dash.
[INFO] This might be different than expected because of symbolic links (for example, from /usr/bin/python to /usr/bin/python3 to /usr/bin/python3.8).
[INFO] To pass the checks, the executable must be python3.8.
[FAIL] You did not satisfy all the execution requirements.
[FAIL] Specifically, you must fix the following issue:
[FAIL]    Executable must be 'python'. Yours is: dash
Out[2]: 512

尝试os不行,因为os模块还是调用的dash来执行的

发现用subprocess可以

subprocess.run(["./embryoio_level15"])

level16

同上,运行后再输入密码即可

level17

subprocess.run(["./embryoio_level17","jduimogzro"])

level18

In [1]: import subprocess

In [2]: subprocess.run(["env","-i","uaznno=hqbclqnobf","./embryoio_level18"])

level19

这里需要详细了解一下,如何用subprocess.run进行重定向

Python3 subprocess | 菜鸟教程 (runoob.com)

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)
  • args:表示要执行的命令。必须是一个字符串,字符串参数列表。
  • stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
  • timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired 异常。
  • check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹 出 CalledProcessError 异常。
  • encoding: 如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
  • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
  • input:程序运行后的交互输入 注意与stdin区分
import subprocess

# 定义命令及其参数
command = ["/challenge/embryoio_level19"]

# 打开输入文件并读取数据
with open("/tmp/xhekoe", "rb") as f:
    input_data = f.read()

# 使用subprocess.run()执行命令并重定向输入
result = subprocess.run(command, input=input_data, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# 打印输出结果
print("stdout:", result.stdout)
print("stderr:", result.stderr)

然而提示

[ADVICE] File descriptors are inherited from the parent, unless the FD_CLOEXEC is set by the parent on the file descriptor.
[ADVICE] For security reasons, some programs, such as python, do this by default in certain cases. Be careful if you are
[ADVICE] creating and trying to pass in FDs in python.
[FAIL] You did not satisfy all the execution requirements.
import subprocess

# 定义命令及其参数
command = ["/challenge/embryoio_level19"]

# 打开输入文件并读取数据
with open("/tmp/xhekoe", "rb") as f:

# 使用subprocess.run()执行命令并重定向输入
    result = subprocess.run(command, stdin=f, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# 打印输出结果
print("stdout:", result.stdout.decode())
print("stderr:", result.stderr.decode()) 

事实上必须要用stdin传入,两者是有区别的:

在Linux系统中,标准输入流(stdin)是指向终端(通常是键盘)的文件描述符。当你在终端上键入数据时,这些数据被发送到正在运行的程序的标准输入流中。

FD_CLOEXEC 是一个常量,通常在操作系统中用于文件描述符(file descriptor)。它是文件描述符标志之一,用于指示当执行 exec 系列函数时,文件描述符是否应该关闭。

  1. input 参数
    • input 参数是一个字节串(bytes)或者 None。
    • 如果指定了 input 参数且不为 None,那么它会作为子进程的标准输入。
    • 如果 input 参数为 None(默认情况),则子进程的标准输入为空。
    • 当你有一个字节串数据需要传递给子进程作为标准输入时,可以使用 input 参数。
  2. stdin 参数
    • stdin 参数是一个文件对象或者一个文件描述符。
    • 如果指定了 stdin 参数,那么它会作为子进程的标准输入。
    • 如果 stdin 参数为 None(默认情况),则子进程的标准输入将会从 /dev/null 中读取空数据。
    • 当你已经有一个已经打开的文件对象或者文件描述符,想要将其传递给子进程作为标准输入时,可以使用 stdin 参数

根据提示,input不会传递FD_CLOEXECstdin则会传递FD_CLOEXEC

当然我们这题包含库文件可以很好的解决这个问题,事实上题目可能是要要求我们手搓,我们用轮子绕过了这一个过程

手搓一个试试

import os

def run_program_with_input(program_path, input_file):
    # 打开输入文件
    input_fd = os.open(input_file, os.O_RDONLY)
    
    # 创建子进程
    pid = os.fork()
    
    if pid == 0:  # 子进程
        # 重定向子进程的标准输入到输入文件
        os.dup2(input_fd, 0)
        
        # 执行外部程序
        os.execl(program_path, os.path.basename(program_path))
    else:  # 父进程
        # 等待子进程结束
        os.waitpid(pid, 0)
        
        # 关闭文件描述符
        os.close(input_fd)

if __name__ == "__main__":
    program_path = "./your_program"
    input_file = "./input.txt"
    run_program_with_input(program_path, input_file)

level20

有了上题的经验

import subprocess

# 打开并读取文件内容
with open('/tmp/rfqbmu', 'w') as f:
# 将文件内容作为输入传递给子进程
    result = subprocess.run(['/challenge/embryoio_level20'], stdin=None, stdout=f, stderr=subprocess.PIPE)

然后直接cat就可以了

level21

subprocess.run(['env','-i','/challenge/embryoio_level21'])

level22

md,又来一轮,把上面的脚本ipython运行改为python运行,真是服了

subprocess.run(['env','-i','/challenge/embryoio_level22'])

level23

subprocess.run(['./embryoio_level23'],input=b"eyejkdde")

level24

subprocess.run(['/challenge/embryoio_level24',"yogjcbumez"])

level25

subprocess.run(['/challenge/embryoio_level25'],env={"qpvtwc":"eghdabuygy"})

level26

with open("/tmp/sjfilu","r") as f:
    subprocess.run(['/challenge/embryoio_level26'],stdin=f)

level27

with open("/tmp/zjvuap","w") as f:
    subprocess.run(['/challenge/embryoio_level27'],stdin=None,stdout=f)

level28

subprocess.run(['/challenge/embryoio_level28'],stdin=None,env={})

level29

现在到C语言编程的篇章了

这部分对linux系统进程的创建调用需要理解清除

Difference between fork() and exec() - GeeksforGeeks

听一遍课感觉还是收获很大

题目要求fork开子进程然后调用文件

  • 在父进程中,pid 的值是新创建的子进程的进程 ID。这个值会大于 0。
  • 在子进程中,pid 的值是 0,用来表示当前正在执行的代码是子进程部分。
  • 对父进程的检测需要使用exec()系列的函数,因为system()或popen()函数都会执行一个shell,然后用shell来执行,所以此时父进程为shell(测试后是dash),而不是你的程序。
  • 而exec()只是替换掉正在exec()的进程,当用fork()函数时子进程调用exec()系列函数杀死自己的子进程副本的时候就会建立与当前主进程的父子关系。
  • 当一个父亲创建了一个孩子但没有等它结束就自己结束的话就可能造成系统异常,查询waitpid()的使用来等待孩子进程。

另外查询了一下,说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
const char *filename:表示要执行的程序的文件路径。这个参数指定了要执行的程序的路径。通常情况下,应该提供程序的绝对路径,以确保系统可以找到并正确执行程序。如果程序在 PATH 环境变量中,则可以直接提供程序的名称。

char *const argv[]:表示程序的命令行参数列表。这个参数是一个字符串数组,每个字符串都是一个命令行参数。数组的第一个元素通常是程序的名称,后续元素是传递给程序的命令行参数。数组的最后一个元素必须是 NULL,以表示参数列表的结束。

char *const envp[]:表示程序的环境变量列表。这个参数是一个字符串数组,每个字符串都是一个环境变量设置,格式为 "name=value"。数组的最后一个元素也必须是 NULL,以表示环境变量列表的结束。

下面是这些参数的详细说明:

filename:是一个字符串,指定了要执行的程序的路径。如果路径指向一个可执行文件,则 execve() 将加载并执行该文件。注意,execve() 不会查找 PATH 环境变量中指定的路径,因此必须提供绝对路径或者相对路径。

argv[]:是一个字符串数组,用于传递命令行参数给新程序。数组的第一个元素通常是程序的名称,后续元素是程序的命令行参数。数组的最后一个元素必须是 NULL,以表示参数列表的结束。

envp[]:是一个字符串数组,用于传递环境变量给新程序。每个字符串都是一个环境变量设置,格式为 "name=value"。数组的最后一个元素必须是 NULL,以表示环境变量列表的结束。
#include <stdio.h>
#include <unistd.h>void pwncollege(char* argv[],char *env[]){
    execve("/challenge/embryoio_level29",argv,env);//使用exec系列函数执行时不会改变新进程的父亲,相当于只是将当前进>程替换掉了
    return ;
}//char *argv[]是一个指针数组,指向传入的参数,每个指针都指向一个字符串
int main(int argc,char* argv[],char* env[]){
    pid_t fpid;
​
    fpid=fork();//fork()执行之后,会复制一个基本一样的进程作为子进程,然后两个进程会分别执行后面的代码
    if(fpid<0)//如果fpid为-1,说明fork失败
            printf("error in fork!\n");
    else if (fpid==0){//成功则会出现两个进程,fpid==0的是子进程
            printf("我是子进程\n");
            pwncollege(argv,env);
    }
    else{//fpid==1的是父进程
            printf("我是父进程\n");
            wait(NULL);//等待子进程结束
    }
    return 0;
}

弄懂了基本原理后

写出一个简单的运行程序

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void pwncollege(char *argv[],char *env[])
{
    char *newargv[]={"embryoio_level29",NULL};
    execve("/challenge/embryoio_level29",newargv,env);
}
int main(int argc,char* argv[],char *env[])
{
    pid_t fpid;
    fpid=fork();
    if(fpid<0)
    {
        printf("Error in fork!");
    }
    else if(fpid==0)
    {
        printf("Im child!");
        pwncollege(argv,env);
    }
    else if (fpid>0)
    {
        printf("Im Parent");
        wait(NULL);
    }
    return 0;
}

level30

同上多个密码传参

level31

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void pwncollege(char *argv[],char *env[])
{
    char *newargv[]={"embryoio_level31","aomapmyibu",NULL};
    char *env[]={"",NULL}
    execve("/challenge/embryoio_level31",newargv,env);
}
int main(int argc,char* argv[],char *env[])
{
    pid_t fpid;
    fpid=fork();
    if(fpid<0)
    {
        printf("Error in fork!");
    }
    else if(fpid==0)
    {
        printf("Im child!");
        pwncollege(argv,env);
    }
    else if (fpid>0)
    {
        printf("Im Parent");
        wait(NULL);
    }
    return 0;
}

level32

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void pwncollege(char *argv[],char *env[])
{
    char *newargv[]={"embryoio_level32",NULL};
    char *newenv[]={"rnzara=mtqxpnweqm",NULL};
    execve("/challenge/embryoio_level32",newargv,newenv);
}
int main(int argc,char* argv[],char *env[])
{
    pid_t fpid;
    fpid=fork();
    if(fpid<0)
    {
        printf("Error in fork!");
    }
    else if(fpid==0)
    {
        printf("Im child!");
        pwncollege(argv,env);
    }
    else if (fpid>0)
    {
        printf("Im Parent");
        wait(NULL);
    }
    return 0;
}

level33

输入流重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void pwncollege(char *argv[],char *env[])
{
    char *newargv[]={"embryoio_level33",NULL};
    char *newenv[]={NULL};
    execve("/challenge/embryoio_level33",newargv,newenv);
}
int main(int argc,char* argv[],char *env[])
{
    pid_t fpid;
    fpid=fork();
    if(fpid<0)
    {
        printf("Error in fork!");
    }
    else if(fpid==0)
    {
        FILE *fp=freopen("/tmp/aoztam","r",stdin);
        printf("Im child!");
        pwncollege(argv,env);
    }
    else if (fpid>0)
    {
        printf("Im Parent");
        wait(NULL);
    }
    return 0;
}

level34

输出流重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void pwncollege(char *argv[],char *env[])
{
    char *newargv[]={"embryoio_level34",NULL};
    char *newenv[]={NULL};
    execve("/challenge/embryoio_level34",newargv,newenv);
}
int main(int argc,char* argv[],char *env[])
{
    pid_t fpid;
    fpid=fork();
    if(fpid<0)
    {
        printf("Error in fork!");
    }
    else if(fpid==0)
    {
        FILE *fp=freopen("/tmp/ivyyzs","w",stdout);
        printf("Im child!");
        pwncollege(argv,env);
    }
    else if (fpid>0)
    {
        printf("Im Parent");
        wait(NULL);
    }
    return 0;
}

level35

设置空环境变量,不重复贴了

level36

- the challenge checks for a specific parent process : bash
- the challenge checks for a specific process at the other end of stdout : cat

那么我们之间bash执行, 将stdout 输送到 cat即可

./embryoio_level36 | cat

level37

这里有个坑,因为我用的vscode ,默认是dash

需要手动输入bash切换到bash然后才能通过检查

./embryoio_level37 | grep "pwn"

level38

复习一下sed

  1. 替换文本
    • s/old/new/:将行中的第一个匹配的 old 替换为 new
    • s/old/new/g:将行中所有匹配的 old 替换为 new
    • s/old/new/2:将行中第二个匹配的 old 替换为 new
  2. 删除行
    • d:删除匹配的行。
    • 1d:删除第一行。
    • 2,5d:删除第二到第五行。
  3. 插入和追加文本
    • i\text:在匹配行前插入文本。
    • a\text:在匹配行后追加文本。
  4. 打印文本
    • p:打印匹配的行。
  5. 行的选择
    • 5p:打印第五行。
    • 1,5p:打印第一到第五行。
  6. 执行文件中的命令
    • -f scriptfile:从指定文件中读取 sed 命令并执行。
  7. 编辑标记
    • b label:跳转到标记 label
    • t label:如果有替换,则跳转到标记 label
  8. 行号
    • =:打印行号。
    • N:将下一行添加到模式空间。
  9. 模式匹配
    • ^:匹配行的开头。
    • $:匹配行的结尾。
    • .:匹配任意单个字符。
    • *:匹配零个或多个前面的元素。
  10. 其他命令
    • q:退出 sed
./embryoio_level38 | sed "p"

level39

./embryoio_level39 | rev |rev

level40

cat | ./embryoio_level40

level41

有点sb?

没懂什么原理

开始使用rev | ./embryoio_level41输入后没反应

• Discord | #program-interaction | pwn.college

发现很多人和我一个问题,在vscode里面的终端搞就不太行

这里有详细的讨论

原因大概是因为rev不会处理\n,需要我们手动输入ctrl+D发生EOF信号

这样就解决了

level42

./embryoio_level42 | cat

然后bash 1.sh执行这个脚本,又是一遍轮回

level43

./embryoio_level43 | grep "pwn"

level44

./embryoio_level44 | sed "p"

level45

./embryoio_level45 | rev | rev

level46

cat | ./embryoio_level46

level47

rev|rev | ./embryoio_level47

记得输入ctrl + D

level48

根据提示,可以使用subprocess也可以使用pwntools

subprocess只能使用popen因为run的区别在于

subprocess.Popensubprocess.run 都是 Python 中用于执行外部命令的模块。它们之间的主要区别在于:

  1. 返回值
    • subprocess.Popen 返回一个 Popen 对象,该对象表示正在运行的子进程。你可以使用该对象的方法来与子进程进行交互,例如等待其完成或发送输入。
    • subprocess.run 返回一个 CompletedProcess 对象,该对象包含有关运行命令的信息,如返回码、标准输出和标准错误
import subprocess

p1 = subprocess.Popen(["/challenge/embryoio_level48"], stdout=subprocess.PIPE, shell=False)
p2 = subprocess.Popen(["cat"], stdin=p1.stdout, stdout=subprocess.PIPE, shell=False)

print(p2.communicate()[0])

异或??

我阅读了pwntools的文档,我认为

from pwn import *

# 启动一个子进程执行命令,并将输出重定向到一个管道
p1 = process("/challenge/embryoio_level48")

# 使用cat命令读取子进程的输出
p2 = process(["cat"], stdin=p1.stdout)

# 读取cat命令的输出
output = p2.recvall()

# 打印输出
print(output)

为什么不行呢

心态爆炸了

研究一波pwntools

Pwntools 2022简明手册_pwntools手册-CSDN博客

pwntools-cheatsheet.md (github.com)

level49

import subprocess

p1 = subprocess.Popen(["/challenge/embryoio_level49"], stdout=subprocess.PIPE, shell=False)
p2 = subprocess.Popen(["grep","pwn"], stdin=p1.stdout, stdout=subprocess.PIPE, shell=False)

print(p2.communicate()[0])

level50

在Ipython中运行

level51

import subprocess

p1 = subprocess.Popen(["/challenge/embryoio_level51"], stdout=subprocess.PIPE, shell=False)
p2 = subprocess.Popen(["rev"], stdin=p1.stdout, stdout=subprocess.PIPE, shell=False)
p2 = subprocess.Popen(["rev"], stdin=p2.stdout, stdout=subprocess.PIPE, shell=False)

print(p2.communicate()[0])

level52

import subprocess
p1 = subprocess.Popen(["cat"], stdout=subprocess.PIPE, shell=False)

p2 = subprocess.Popen(["/challenge/embryoio_level52"],stdin=p1.stdout,stdout=subprocess.PIPE, shell=False)

print(p2.communicate()[0].decode('utf8'))

level53

import subprocess
p1 = subprocess.Popen(["rev"], stdout=subprocess.PIPE, shell=False)
p1 = subprocess.Popen(["rev"], stdin=p1.stdout,stdout=subprocess.PIPE, shell=False)
p2 = subprocess.Popen(["/challenge/embryoio_level53"],stdin=p1.stdout,stdout=subprocess.PIPE, shell=False)

print(p2.communicate()[0].decode('utf8'))

输入密码后记得ctrl+D发生fd标识

level54

用python执行刚才的脚本又是一遍轮回 我觉得可以精简一下

import subprocess
p1=subprocess.Popen(["/challenge/embryoio_level54"],stdout=subprocess.PIPE,shell=False)
p2=subprocess.Popen(["cat"],stdin=p1.stdout,stdout=subprocess.PIPE,shell=False)
print(p2.communicate()[0].decode())

level55

import subprocess
p1=subprocess.Popen(["/challenge/embryoio_level55"],stdout=subprocess.PIPE,shell=False)
p2=subprocess.Popen(["grep","pwn"],stdin=p1.stdout,stdout=subprocess.PIPE,shell=False)
print(p2.communicate()[0].decode())

level56

import subprocess
p1=subprocess.Popen(["/challenge/embryoio_level56"],stdout=subprocess.PIPE,shell=False)
p2=subprocess.Popen(["sed","p"],stdin=p1.stdout,stdout=subprocess.PIPE,shell=False)
print(p2.communicate()[0].decode())

level57

import subprocess
p1=subprocess.Popen(["/challenge/embryoio_level57"],stdout=subprocess.PIPE,shell=False)
p2=subprocess.Popen(["rev"],stdin=p1.stdout,stdout=subprocess.PIPE,shell=False)
p2=subprocess.Popen(["rev"],stdin=p2.stdout,stdout=subprocess.PIPE,shell=False)

print(p2.communicate()[0].decode())

level58

import subprocess

p1=subprocess.Popen(["cat"],stdout=subprocess.PIPE,shell=False,close_fds=True)
p2=subprocess.Popen(["/challenge/embryoio_level58"],stdin=p1.stdout,stdout=subprocess.PIPE)

print(p2.communicate()[0].decode())

level59

import subprocess
p2=subprocess.Popen(["rev"],stdout=subprocess.PIPE,shell=False)
p2=subprocess.Popen(["rev"],stdin=p2.stdout,stdout=subprocess.PIPE,shell=False)
p1=subprocess.Popen(["/challenge/embryoio_level59"],stdin=p2.stdout,stdout=subprocess.PIPE,shell=False)

print(p1.communicate()[0].decode())

level60

用C语言编程实现以上过程,又是一遍轮回关于我9次轮回这件事

这里需要深入了解Linuxpipe机制,不能老当调包侠

Linux 的进程间通信:管道 - 知乎 (zhihu.com)

管道的设计也是遵循UNIX的“一切皆文件”设计原则的

实现形态上是文件,但是管道本身并不占用磁盘或者其他外部存储的空间