Pwnable.kr tiny_easy 记录
这道题现在回头来看不难,但是做的过程一步一步学习到的知识很多,也很有收获,因此题目写了记录二次,这个不能算是题解,算是还原了我当时是怎么做的过程。
给我自己的话:直接去看总结反思。
程序分析
在 gdb 中查看执行流,可以看到最后会执行程序名。比如程序名为 /home/allen/AAA ,那么最后就会 call 0x6d6f682f("/hom") 。
漏洞点
argv[0] 与 execve
很明显,我们需要改变程序名。然而程序名这怎么可能改变,陷入死路。所以这就是学习到的知识点了。
实际上程序名是 argv[0] ,我们要改变的是 argv[0] 。
argv[0] 是程序名只是约定俗称的事情,我们可以让它变成不一样的东西!
怎么变?使用 execve() 。
// 只要传入argv,那么argv[0]就是我们传入的参数,而不是程序名称
int execve(const char * filename, char * const argv[ ], char * const envp[ ]);
Heap Spray
那么现在的思路是什么?这个又卡住了,参考了网上的题解:将 shellcode 传入 argv 列表中,这样栈上就有 shellcode ,然后把 argv[0] 改成该 shellcode 地址即可。
关键...栈的位置随时变,我们不能准确知道 shellcode 的确定位置。
所以:Heap Spray,该你上场了!
方法就是 shellcode 不仅仅就传入 argv 的某一项,而是传入很多项。比如传入 50 个,这样 argv[1-50] 就全是 shellcode,栈里面就有许多许多 shellcode ,这样我们打入地址的成功概率将大大增加。
调试过程
argv[0] 与 pwntools.gdb
现在的想法就很自然了:能否使用 pwntools 的 gdb 模块,这样可以便于调试。然而事实是不行的。
首先是 GDB DEBUG 使用方式
# 最普通的方式
p = gdb.debug('./my_program')
# 加入参数
p = gdb.debug(['./my_program', 'AAAA', 'BBBB'])
可以看到 argv[0] 必须是程序名,如果我把 argv[0] 强制改成别的会如何?
p = gdb.debug("./tiny_easy")
['/bin/gdbserver', '--multi', '--no-disable-randomization', 'localhost:0', './tiny_easy']
p = gdb.debug(['AAAA', 'BBBB', 'CCCC'])
['/bin/gdbserver', '--multi', '--no-disable-randomization', 'localhost:0', 'AAAA', 'BBBB', 'CCCC']
可以看到 GDB DEBUG 是利用 gdbserver 来启动的,反正最后 argv[0] 必须要是程序名,否则就不能启动了
当然如果不涉及 gdb,那么 python 还是和 c 语言一样,可以控制 argv,使用 process(argv, './my_program') 即可。
gdb 如何跟踪 execve 的子程序
所以老老实实编写 C 程序,在该程序调用我们的 tiny_easy。
这里面就要跟踪 C 程序执行的 execve 语句,使用 catchpoint
利用 python 加快 gdb 交互
假设我们的程序为 debug,那么我们使用 gdb debug 来看调用过程。
如果我们定向的栈地址没有成功,那只能结束掉本次的 gdb,然后重新再来一次。可怕的是,这个成功率其实特别低,而且就算成功了,我们也很可能没有完完全全保证说通过这一次我就完全了解了过程,很有可能需要好几次才真正理解。
所以,要使用脚本,在循环里面判断,那么问题来了,python 怎么和 gdb 打交道?
实际上和执行普通的程序是一样的呀。代码胜千言,看代码!代码进行了简化,反正大致意思就是和执行普通程序进行交互就可以。
for i in range(0x1000):
p = process(['gdb', 'debug'])
p.recvuntil('...')
p.sendline('r')
p.recvuntil('...')
stack_lo = int( p.recvuntil('...', drop=True), 16 )
p.sendline('vmmap')
p.recvuntil('...')
stack_hi = int( p.recvuntil('...', drop=True), 16 )
# 如果成功就会收到相关信息,如果失败就会有 EOF_ERROR
# 成功,进入交互,一定要来个 interactive()
if (0xffbce630 < stack_hi and 0xffbce630 > stack_lo):
p.interactive()
else:
p.close()
C 语言的注意点
C 语言处理 16 进制字符串
warning: hex escape sequence out of range
对于这样的语句 char *test = "\x90\x901"; ,这样会编译出错。因为 C 语言并没有规定 \x 后面只进行两位数转义,所以他相当于转义了901,而这会超出范围,关键是他还不报错只报警告,后续会导致一些问题!
所以最好的方式是 char *test = "\x90\x90""1";,每个特殊转义末都加个引号隔开。
execve 需要最后的 args 为 NULL
否则将会执行失败。
总结反思
-
学到的 pwn 知识
-
看到程序名就要想到 argv[0] ,对于 argv[0] ,就要想到可以使用
execve来改变 -
使用 argv 列表将 shellcode 传入栈中,并且使用 Heap Spray 增加成功率
-
学到的 gdb 知识
-
如何跟踪 execve 的子程序:使用 catchpoint
-
学到的 python 知识
-
对于
pwntools.debug,它的原理是通过gdbserver实现,并且要求 argv[0] 必须是程序名 -
对于某个程序,想用 gdb 执行它,怎么利用 python 进行交互?
-
学到的 c 知识
-
C 程序处理 16 进制字符串要小心
execve需要最后的 args 为 NULL