继续进行着栈溢出的练习,这次是参考了ctf-wiki上面的演示题目,在复现过程中的一些记录。
over
安恒月赛的题目,首先看程序保护,开启了NX,不能写shellcode,同时分析了程序结果发现buffer有0x50,但是输入有0x60也就是说只能覆盖rbp和ret地址,所以必须要改变栈的位置。
首先是一个trick,里面读入的函数是read
,所以读入的数据是不会自动加上\0
的,其他的两个函数gets
和scanf
都会对读入的字符串加上\0
截断。利用这个差异性,我们只要填入0x50个非0数据,这样puts
就会打印出rbp的值。泄露出栈基址之后,我们就能利用相对偏移来确定题目随意的栈地址。
这样我们输入80个a,在puts的地方下断点,看看gdb里面的信息。
在输出我们数据的时候rbp的值为0x7fffffffdc20
,然后观察我们的栈顶处的地址为0x7fffffffdbb0
。我们这里利用的思路是伪造栈帧,在这里我们泄露出来rbp的值通过位移差我们就能得到栈的起始地址。之后伪造的格式如下:
| 8字节fake rbp | 需要调用的地址 |…| stack addr | leave ret addr |
这种技术是通过两次调用leave
来劫持rsp
的值,使得它最终指向我们要调用的地址处执行ret
,通过控制rsp
的值来控制rip
。
因为函数在正常调用结束的时候会leave; ret;
,这样我们设置的stack addr
覆盖了原始的rbp
,那么leave
中会mov rsp, rbp; pop rbp
。rsp
会成为指向stack addr
的指针,然后pop rbp
将我们伪造的值给rbp
。但是在之后再次执行一次leave; ret
,我们现在的rsp
就会指向我们构造的地址处,将fake rbp
填入rbp
中,这之后的ret
就会将填充的调用地址作为返回地址来处理,这样就成功劫持了控制流.
所以上面那张图我们得到泄露出来的rbp
的值减去0x70就是我们输入缓冲区的栈顶地址。我们拿到地址之后伪造栈帧泄露libc的地址,然后再次伪造栈帧getshell。其中第二次构造的时候,栈的位置会有一定偏移,需要调试获取
从上图可以知道在第二次伪造栈帧输入的时候栈顶地址为0x7fffffffdb80
,而我们一开始获取的栈地址为0x7fffffffdbb0
,相差0x30,在构造的时候需要修正一下。最后给出exp:
1 | #!/usr/bin/env python2 |
里面比较坑的地方有两个,一个是我们的输入不能带\n
回车,因为这个回车会覆盖掉rbp
最后的值。。这样我们就无法得到正确的值。另外一个是pwntools里面的libc设置,如果设置了libc的address,那么我们在使用libc进行搜索的时候就不用额外再加libc的基地址,因为里面已经自动帮你加上了。但是没有使用libc符号或者搜索的地址还是需要手动增加libc的基地址值。
readme
一道神奇的题目,里面有一个循环会覆盖flag的内容。同时文件开启了canary和堆栈执行保护。这里有一个有趣的利用思路,那就是stack smash,恰恰利用canary的check机制,它在check失败之后会打印出argv[0]
的字符串,如果能覆盖成其他地址,这里就能够利用来泄露内存数据。而这道题目的flag已经在一开始就存进了内存中,同时由于.bss
节的特性,ELF文件在执行的时候,它会被映射两次,所以我们只需要寻找另外一处没有被覆盖掉的内容地址填入argv[0]
的地址处,就能够get flag。
刚好这里有一个溢出漏洞可以利用,那么先来看看argv[0]
的地址。
在这里能够很明显的看到程序名,而这里一定就是argv[0]
的地址,0x7fffffffdc78(大雾?)。这里的地址指向另外一个地址,而那个地址才是存储着字符串的指针的值,所以一开始我还出错了,最后需要覆盖的地址为0x7fffffffdd48。接下来只需要确定出我们的输入的地址,就能控制溢出的字节数。
看到我们的rsi
和rsp
的值都是0x7fffffffdb30,说明接下来gets的输入会写入这里。接下来就是需要读出来的flag地址了。
我在覆盖的flag中填入的aa,通过搜索这个字符串找到相应的地址,这是第一个映射地址,通过IDA我们能知道flag的样子为32C3...
,所有搜索这个就能得到第二个flag的地址。
这里看到另外一个完整的flag地址为0x400d20,通过一个栈溢出来覆盖argv[0]
的地址为flag的地址,这样溢出之后的check失败就能把flag打印出来。
1 | #!/usr/bin/env python2 |
最后从stack smash的check中get flag信息,如果是在服务器上就能拿到真正的flag。这道题也解决了我的一个疑惑,虽然地址不一样,但是偏移是一样的,所以我们找到地址之后计算需要覆盖的长度也是一样的,同时.bss
节的映射又是不变的,这样就能稳定的覆盖成另外一个flag的地址。
babypie
一道安恒的月赛题目,题目中有两次输入,第一次能输入0x30,能够刚好溢出8个字节,然后第二次能输入0x60,都写在同一段缓冲区中。程序开启了canary,PIE,NX等等保护,所以第一步需要先泄露出canary的值。
这里利用了之前的trick,由于读入数据是使用的read并不会有\0
在末尾,由于canary的最低位为0,这样我们多覆盖一个字节的数据,让printf直接把canary的值打印出来。
获取到canary之后,再次观察程序,发现有留有getshell的函数,那题目就简单很多了。这里如果能修改返回地址为函数地址就能getshell。但是程序开了PIE怎么办???
不用怕,PIE的随机化不会影响低12bit的地址,这里只需要暴力测试地址就很有可能得到正确的地址。只是在程序中有4bit在变化,getshell的地址为a3e
,由于还有1bit是不能确定的,所以我们直接覆盖成0a3e
来碰运气即可。
1 | #!/usr/bin/env python2 |
这里要注意ljust和rjust是左对齐和右对齐,一开始没有弄清楚。。。左对齐是在末尾填充,右对齐是在头部填充。
chess(未解出)
先检查安全选项,开启了NX和PIE,不能写shellcode了。发现是一个下棋的游戏,然后里面通过输入字母数字来移动,每次移动都会打印一次。
然而搜到的韩文解法肯本看不懂。。。这里就记录一下了,期望以后可以做出来
Reference
https://23r3f.github.io/2018/12/03/2018-12-3-pwndbg%E7%94%A8%E6%B3%95/
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/fancy-rop-zh/