知识杂项
- strncpy(char *s1,const char *s2,int n);
其中有三个参数分别表示目标字符串s1,源字符串s2,拷贝长度。意思是将s2指向的字符串的前n个长度的字符放到s1指向的字符串中,并将s1原有的前n个字符覆盖。 - sys_execve()
在真正的开始执行系统调用函数之前,系统调用服务程序已经将一些系统调用的函数的参数传递给了相应的寄存器,比如这里的ebx,ecx,edx都分别保存了系统调用的参数,ebx保存的是第一个参数,依次类推(当然最多传递的参数个数不能大于5个),首先这个函数通过ebx获取需要执行的文件的绝对路径。获取到文件名之后他就会调用do_execve()。- do_execve()
ecx,edx寄存器就是传递给可执行文件的参数指针和环境变量参数指针,这里会生成一个记录可执行文件的信息的结构体--struct linux_binprm。
这个结构体记录可执行文件的信息,用于比对可执行文件的格式找到相应的加载函数,如a.out就调用load_aout_binary()函数开始通过 linux_binprm结构体里面的信息准备此程序的执行工作。 - load_aout_binary()
到了这个函数就是要建立新进程了,与过去告别的时候了,它通过调用flush_old_exec()函数来独立门户,准备成为一个独立的进程,比如更新PCB,更新信号处理表,内存等。 - flush_old_exec()
这个函数就专门负责与过去告别,他先调用exec_mmap()函数将内存结构释放,他将从父进程复制过来的(fork())mm_struct下的vm_area_struct全部释放掉,但是如果通过vfork()到此步骤就不需要释放,当然对于vfork()的父进程的mm_struct是不可能为零的,此时就成为了真正的进程,还需要把其从父进程的线程组中脱离出来,这个通过de_thread()实现,此时vfork()之后调用的execve()就成为了一个进程了。虚拟内存反面独立之后就完成了一大步了,这里就是信号的独立了,信号处理表是从父进程复制过来的,而信号处理表指向的信号服务程序有三种方式,他们分别是:忽视此信号,采用默认的方式,采用进程注册的用户处理方法,但是从父进程复制过来的信号处理表指向父进程的用户注册的函数,并不在次进程空间,因此这 里要调用flush_signal_handle()函数来遍历一遍信号处理表将第三种方式的处里信号的方式改为默认的,这里完成了信号的独立。最后就是关闭从父进程复制过来的文件系统,将父进程打开的文件在这里关闭,这里可以保留0,1,2,三个文件不关闭(stdin,stdout,stderr)这里就完全和父进程独立成为了一个真正的进程了,然后就是建立可执行文件和虚拟内存之间的映射, 建成了vm_area_struct结构,将text,data,bss,建立起虚拟映射,同时他还要将在运行main()函数的参数和环境变量建立起映射,这个都在linux_binprm结构中,到了这里execve()函数的任务也就完成了。
- do_execve()
- 本地连接process()、远程连接remote()。对于remote函数可以接url并且指定端口。
- IO模块
- send(data) : 发送数据
- sendline(data) : 发送一行数据,相当于在末尾加
- recv(numb=4096, timeout=default) : 给出接收字节数,timeout指定超时
- recvuntil(delims, drop=False) : 接收到delims的pattern
- recvline(keepends=True) : 接收到 ,keepends指定保留
- recvall() : 接收到EOF
- recvrepeat(timeout=default) : 接收到EOF或timeout
- interactive() : 与shell交互
- 数据处理:主要是对整数进行打包,就是转换成二进制的形式,比如转换成地址。p32、p64是打包,u32、u64是解包。
- voiddlsym(voidhandle,constcharsymbol)
函数描述:
dlsym(dynamic library symbol)
根据 动态链接库 操作句柄(handle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。
handle:由dlopen打开动态链接库后返回的指针;
symbol:要求获取的函数或全局变量的名称。
返回值:
void 指向函数的地址,供调用使用。 - int sprintf( char *buffer, const char *format, [ argument] … );
参数列表
buffer:char型指针,指向将要写入的字符串的缓冲区。
format:格式化字符串。
[argument]...:可选参数,可以是任何类型的数据。
返回值
返回写入buffer 的字符数,出错则返回-1. 如果 buffer 或 format 是空指针,且不出错而继续,函数将返回-1,并且 errno 会被设置为 EINVAL。sprintf 返回以format为格式argument为内容组成的结果被写入buffer 的字节数,结束字符‘ ’不计入内。即,如果“Hello”被写入空间足够大的buffer后,函数sprintf 返回5,同时buffer的内容将被改变。 - strlen所作的仅仅是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符' '为止,然后返回计数器值(长度不包含' ')。
ROP技术
- 如果程序里找不到system函数,我们可以用用ROPGadget --binary pwn5 | grep “int 0x80”找到了一个可用的gadget。
- 我们知道在http://syscalls.kernelgrok.com/ 上可以找到sys_execve调用,同样可以用来开shell,这个系统调用需要设置5个寄存器,其中eax = 11 = 0xb, ebx = &(“/bin/sh”),ecx = edx = edi = 0。
- “/bin/sh”我们可以在前面输入到地址固定的全局变量中。接下来我们就要通过ROPgadget搜索pop eax/ebx/ecx/edx/esi; ret了。
内容来源
execve系统调用
CTF常用python库PwnTools的使用学习
i春秋月刊第六期——Linux pwn零基础入门