2021GFCTF RE_WP

wordy

去除花指令

image-20211125095945784

1
2
3
4
5
6
7
8
9
addr =0X556AE377FD56
end = 0x0556AE377FE40
flag = ""
for i in range(addr, end, 13):
c = get_bytes(i+4, 1)[0]
flag+=chr(c)

print(flag)
# GFCTF{u_are2wordy}

BabyReverse

IDA打开,去除所有的花指令

image-20211125100636917

直接看下面对flag如何加密的,进入sub_412E10, 发现是SM4加密

image-20211125100743339

而传入的第二个参数是key,即byte_4409c0是key,回到main函数再往上看,发现前面有一个对byte_4409c0类似RC4加密的操作,

byte_4409C0进行交叉引用

image-20211125100949459

image-20211125101052255

于是下断点调试来获取key,发现不行,猜测前面是反调试,从main函数头部下断点调试

image-20211125101538394

最终定位到这个函数

image-20211125101557018

采用的是self_mapping技术实现反调试,本质是创建secion的时候设置SEC_NO_CHANGE,映射后不能改变

Self-Remapping-Code

关于这个技术,可以参考下这位大佬的笔记 https://jev0n.com/2021/09/23/Self-Remapping.html

我们直接将call sub_411CE0 的地方nop掉,手动的把byte_4409c0的地方加1

1
2
3
4
5
6
7
8
a = [  0x07, 0xB8, 0x0D, 0x24, 0xB1, 0x0C, 0x2D, 0xC7, 0x28, 0x2D, 
0xC3, 0x61, 0x66, 0x4F, 0x72, 0x13]

addr = 0x04409C0
for i in range(16):
patch_byte(addr+i, a[i]+1)

print("OK")

运行起来

image-20211125102324766

得到key为 GF?->GirlFriend?

提取密文

1
0D 40 3B 87 A5 66 DA 74 92 7F BB E1 B8 CD EB BC 59 45 1B C0 38 99 AA 22 AA 3F 9D 21 07 4E 81 1F

SM4在线解密

image-20211125102923390

2e69df5961f20aee0897cf1905156344 , 最终得到flag为 GFCTF{2e69df5961f20aee0897cf1905156344}

re_EasyRE_0x00

IDA打开分析,最关键的是sub_100016A0函数

image-20211125151517064

image-20211125151806585

经过分析,发现sub_10001180是解密login.key文件,生成的数据放到V13里面

然后下面这个地方是将V13处的数据与生成的一些数据进行对比,猜测是机器码的验证

image-20211125151922458

这是V13处的数据

1
11 55 66 55 0D 50 51 0C FF 01 80 12 CE A9 08 75 73 65 72 32 33 33 33

最后8个字符是user2333

将对比的数据也提取出来, 然后结合题目,用户名用admin6677登录,长度是9,整理得

1
11 55 66 55 98 FA 9B 59 6F F6 14 8F E9 DA 09 61 64 6D 69 6E 36 36 37 37

我们写脚本,每次运行到对比数据的时候就把v13的数据给他替换掉

1
2
3
4
5
data = [0x11, 0x55, 0x66, 0x55, 0x98, 0xFA, 0x9B, 0x59, 0x6F, 0xF6, 0x14, 0x8F, 0xE9, 0xDA, 0x09, 0x61, 0x64, 0x6D, 0x69, 0x6E, 0x36, 0x36, 0x37, 0x37]
addr = 0x004CB348 # v13的地址
for i in range(len(data)):
patch_byte(addr+i, data[i])
print("OK")

然后绕过机器码验证,往下走,来到sub_10001610

image-20211125152844760

可以发现,这个地方肯定是与服务器通信了,我们直接运行,直接Wireshark抓包

提取数据

1
2
3
4
5
6
7
---> 11 55 66 55 1a 27 00 00 00 00

<--- 11 55 66 55 66 27 00 00 0f 00 f3 46 8a be 81 62 ed 36 d5 df 28 dc 04 8a fd

---> 11 55 66 55 1a 27 01 00 40 00 0e a2 60 19 1f df 39 0d bc 62 48 57 5a 11 87 78 69 11 03 76 4b f9 2c 1f 35 fd ff 4a b8 d8 63 8f b6 b1 f0 cd d3 90 2d 27 05 b7 1e 01 22 74 91 1a a4 53 df 1d f4 69 7d 3e 29 bd d3 30 da 94 a3 03

<--- 11 55 66 55 66 27 01 00 48 00 84 cb 11 ef 71 51 30 0b b3 d8 c1 22 ac c4 ca f1 29 12 cf 79 f5 36 5f 5a 5e a8 f5 fa 62 3c e8 32 69 d6 a1 54 eb 1b 06 06 b0 68 20 5a 62 ea 48 ec 8a 3d 5c 40 d0 a8 03 94 6a 2e b7 f0 e4 33 aa a0 e3 f2 da f8 a9 cf 5d 92

重新调试,接着刚才的位置往下分析,看到了RC4的初始化及加密

image-20211125155302278

image-20211125155318573

猜测是刚开始,服务器端返回RC4的key,然后后面全部使用RC4加密方式进行加密

image-20211125155723782

根据sub_10001350这个函数可以猜测出数据包的格式, 拿上面服务器返回的key举例子

1
2
3
4
5
11 55 66 55 //标志
66 27 //版本
00 00 //命令
0f 00 //后面数据的长度
f3 46 8a be 81 62 ed 36 d5 df 28 dc 04 8a fd //数据,当命令为0的时候,是RC4的key,命令为1和2的时候,是RC4加密的数据

写脚本验证RC4加密

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
from Crypto.Cipher import ARC4 as rc4cipher
import binascii


def rc4_algorithm(encrypt_or_decrypt, data, key1):
if encrypt_or_decrypt == "enc":
key = key1
enc = rc4cipher.new(key)
res = enc.encrypt(data)
return res
elif encrypt_or_decrypt == "dec":
key = key1
enc = rc4cipher.new(key)
res = enc.decrypt(data)
return res


key = binascii.unhexlify("f3468abe8162ed36d5df28dc048afd")
data1 = binascii.unhexlify(
"0ea260191fdf390dbc6248575a118778691103764bf92c1f35fdff4ab8d8638fb6b1f0cdd3902d2705b71e012274911aa453df1df4697d3e29bdd330da94a303")
m1 = rc4_algorithm("dec", data1, key)


data2 = binascii.unhexlify(
"84cb11ef7151300bb3d8c122acc4caf12912cf79f5365f5a5ea8f5fa623ce83269d6a154eb1b0606b068205a62ea48ec8a3d5c40d0a803946a2eb7f0e433aaa0e3f2daf8a9cf5d92")
m2 = rc4_algorithm("dec", data2, key)


print(m1)
print(m2)
# b'\x8ayqv,\x8eYjj\xdb\xfa\x10\xd6\xa0=\xed!w\xa9/\xdd\xa3\x1a \x05!+\xbd\xd0\xa7\xe7\xd4\xba\t%\xb9N\xeeYR\xdc\xb0Pfq\xae\xe9\xc7\x1eB\xa3\x0eA\xb3\x08\xcf1\xb3\x12\xa5L\xd4`\xcc'
# b'\x00\x10\x00\x80B\x00Please update client!\r\nClient version=10010, Server version=10086\x00'

image-20211125160348983

结合login.key,发现当命令为1的时候,向服务器发送的是login.key的数据,然后服务器返回信息

所以现在需要构造 真正的login.key(11 55 66 55 98 FA 9B 59 6F F6 14 8F E9 DA 09 61 64 6D 69 6E 36 36 37 37)

加密后的数据

sub_10001180是解密函数,进去分析,发现是RSA的PKCS#1加密

image-20211125160757257

根据这个结构找到e和n

image-20211125161211503

image-20211125161253534

提取出来

1
2
3
4
5
e: 65537
n: 0xd928b8efe000f72db5bda67a9aa0740defb555b2603736eecd6d01f38ef2fc79
分解得到p, q
p = 322922590106035145437937724697895880569
q = 304171468404401467258708275665013611777

利用rsatool.py生成private.pem

1
python rsatool.py -e 65537 -p 322922590106035145437937724697895880569 -q 304171468404401467258708275665013611777 -o private.pem

利用在线解密网站测试 https://the-x.cn/cryptography/Rsa.aspx

image-20211125162250321

发现解密成功,将构造好的数据进行加密,

image-20211125162342644

对于PKCS#1的填充方式可以参考下面2篇文章

https://www.cloudcared.cn/3155.html

https://www.cnblogs.com/feng9exe/p/8075447.html

然后写程序与服务器交互,发现服务器返回命令为2的验证码问题

Question(Send result in uint32_t format, 1 second!): 9540808 * 32 + 509 * 859 = ?

然后利用eval计算数值,构造,返回给服务器,即可得到flag,完整的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
import socket
from Crypto.Cipher import ARC4 as rc4cipher
import re
import struct

login_key = [0x5D, 0x98, 0xEE, 0x8B, 0x68, 0x86, 0x2F, 0x56, 0xBA, 0xA1, 0x27, 0x2A, 0x68, 0x8B, 0x19, 0x31, 0x37, 0xC1, 0x2B, 0x1A, 0x80, 0x5F, 0xAB, 0x8C, 0xE0, 0xE6, 0x81, 0xDF, 0x05, 0xC6, 0xB1,
0x2F, 0x0E, 0x59, 0xC8, 0x45, 0x8A, 0x7D, 0x83, 0x35, 0x5F, 0x02, 0x05, 0x10, 0x8A, 0x35, 0x6D, 0x0C, 0xE8, 0x3C, 0x9C, 0x15, 0xD7, 0xDA, 0xF0, 0x96, 0x6D, 0x2E, 0x77, 0xEC, 0x78, 0x3B, 0x83, 0xB2]


def rc4_algorithm(encrypt_or_decrypt, data, key1):
if encrypt_or_decrypt == "enc":
key = key1
enc = rc4cipher.new(key)
res = enc.encrypt(data)
return res
elif encrypt_or_decrypt == "dec":
key = key1
enc = rc4cipher.new(key)
res = enc.decrypt(data)
return res


def get_data(_cmd, _len, _data, _key):
sig = [0x11, 0x55, 0x66, 0x55] # 签名
banben = [0x66, 0x27] # 版本
cmd_list = [_cmd, 0x00] # 命令
data_len_list = [_len, 0x00] # 数据长度
if _len != 0:
return bytes(sig + banben + cmd_list + data_len_list) + rc4_algorithm('enc', bytes(_data), _key)
return bytes(sig + banben + cmd_list + data_len_list)


def get_captcha(_captcha_str):
m = re.search(
r"Question\(Send result in uint32_t format, 1 second!\): (.*?) = ", _captcha_str)
c = eval(m.group(1))
return struct.pack("I", c)


if __name__ == '__main__':
address = ('119.27.179.145', 10086)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(address)

s.send(get_data(0, 0, [], None))
data = s.recv(1024)
rc4_key = data[10:] # 获取RC4密钥

s.send(get_data(1, 0x40, login_key, rc4_key))
data = s.recv(1024)
captcha_str = rc4_algorithm("dec", data[10:], rc4_key).decode()
captcha = get_captcha(captcha_str) # 计算得到验证码
print(f"Captcha: {captcha}")

# 向服务器返回验证码
send_data = get_data(2, len(captcha), list(captcha), rc4_key)
s.send(send_data)
data = s.recv(1024)
m = rc4_algorithm("dec", data[10:], rc4_key)
print(m)
s.close()

# Captcha: b'\xc84\x06\x03'
# b'\x00\x10\x00\x805\x00flag_0x00 = \x00GFCTF{e8e9071b7a70770bec1f6415c4ed4c1d}\x00'

得到flag为 GFCTF{e8e9071b7a70770bec1f6415c4ed4c1d}