西湖论剑 BabyRe 复现

逆向分析

我们拖入IDA加载后可以很明显的观察到对应的 Main 函数空空如也

对于这种情况我们运行程序可以发现其有一个明显的字符串 Input:,我们通过这个字符串可以定位到相关的代码处,一番查找后我们可以发现有三处加密

通过 initterm 来初始化加密的全局函数

其中调用了一个RC4加密

一个魔改sha1加密

一个Base 8加密 从密文的16位开始比较

通过分析可以发现调用过程为:base8 -> sha1 -> rc4

程序沟通过 atexit 函数来进行注册每一次的加密,同时其调用顺序为栈式,即先后注册先调用

程序对应的执行顺序为:

1
2
3
4
5
6
7
8
9
10
11
12
13
sub_401170  判断输入是否在'0'-'9'的区间内

sub_401230 字符串按位取反

sub_4012B0 通过 Hook 将 GetLastError 在IAT中地址进行修改为 sub_4019D0 的地址

main

sub_401670 Base 8 加密

sub_4015C0 SHA1 加密

sub_4014E0 RC4 加密

我们通过调试可以发现在Base8的过程中会将我们输入的前36字节进行判断,随后将加密后的数据的前16字节进行SHA1的魔改加密,最后使用加密的 Base8 的末 6 个字节作为 RC4 加密的 key 进行使用,来加密整个 Base8 后的数据。

解密

Base8解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def decode(input):
table = "01234567"
decoded = []
for i in range(0, len(input), 8):
char_1 = table.index(input[i]) << 5 | table.index(input[i + 1]) << 2 | (table.index(input[i + 2]) >> 1)
decoded.append(char_1)
if input[i + 3] == '=' and input[i + 4] == '=':
break
char_2 = (table.index(input[i + 2]) & 1) << 7 | table.index(input[i + 3]) << 4 | table.index(input[i + 4]) << 1 | (table.index(input[i + 5]) >> 2)
decoded.append(char_2)
if input[i + 6] == '=' and input[i + 7] == '=':
break
char_3 = (table.index(input[i + 5]) & 3) << 6 | table.index(input[i + 6]) << 3 | table.index(input[i + 7])
decoded.append(char_3)
return bytes(decoded)

input_str = '162304651523346214431471150310701503207116032063140334661543446114434066142304661563446615430464'
output = decode(input_str)
print(output)
#b'915572239428449843076691286116796614' # 此时我们得到flag 6位后的所有数字

SHA1 & RC4 解密

对于魔改后的SHA1我们采用单独爆破的效率有点低…而且似乎不用爆破?我们可以直接对RC4的6位Key进行爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Cipher import ARC4

enc = [0x3F, 0x95, 0xBB, 0xF2, 0x57, 0xF1, 0x7A, 0x5A, 0x22, 0x61, 0x51, 0x43, 0xA2, 0xFA, 0x9B, 0x6F, 0x44, 0x63, 0xC0, 0x08, 0x12, 0x65, 0x5C, 0x8A, 0x8C, 0x4C, 0xED, 0x5E, 0xCA, 0x76, 0xB9, 0x85, 0xAF, 0x05, 0x38, 0xED, 0x42, 0x3E, 0x42, 0xDF, 0x5D, 0xBE, 0x05, 0x8B, 0x35, 0x6D, 0xF3, 0x1C, 0xCF, 0xF8, 0x6A, 0x73, 0x25, 0xE4, 0xB7, 0xB9, 0x36, 0xFB, 0x02, 0x11, 0xA0, 0xF0, 0x57, 0xAB, 0x21, 0xC6, 0xC7, 0x46, 0x99, 0xBD, 0x1E, 0x61, 0x5E, 0xEE, 0x55, 0x18, 0xEE, 0x03, 0x29, 0x84, 0x7F, 0x94, 0x5F, 0xB4, 0x6A, 0x29, 0xD8, 0x6C, 0xE4, 0xC0, 0x9D, 0x6B, 0xCC, 0xD5, 0x94, 0x5C, 0xDD, 0xCC, 0xD5, 0x3D, 0xC0, 0xEF, 0x0C, 0x29, 0xE5, 0xB0, 0x93, 0xF1, 0xB3, 0xDE, 0xB0, 0x70]
key = 0
for i in range(1000000):
rc4 = ARC4.new(str(i).zfill(6).encode())
m = rc4.decrypt(bytes(enc))
if m.isdigit():
key = i
print(m)

# key = '807391'
#m='1523306115230466162304651523346214431471150310701503207116032063140334661543446114434066142304661563446615430464'
# m 为所有flag加密一次Base8后的结果

我们发现第一次Base8加密后的数字与m中存在有大部分相同点

第一次 Base8 后数据 162304651523346214431471150310701503207116032063140334661543446114434066142304661563446615430464

可以发现flag在RC4与Base8共同加密后,逆回RC4加密前的Base8加密。可以发现多出来1523306115230466这样一串数据,我们将其对Base8的解密,即可得到flag的前六位

其实直接对 m 解一次 Base8 再加上最后的 RC4 的 6位 Key 就可以得到 flag

Flag

1
DASCTF{561516915572239428449843076691286116796614807391}

西湖论剑 BabyRe 复现
https://equinox-shame.github.io/2023/03/01/西湖论剑 BabyRe 复现/
作者
梓曰
发布于
2023年3月1日
许可协议