babyre

一道hook题,ida打开代码还是比较清晰的,首先判断长度然后在加密,但是我没有搞清楚这个hook的流程,听别的师傅说的在CRT的时候,从r3到r0时候hook的😥(听的不是很懂),但是我瞎找找到这两个函数

资源解密

资源加载

可以看出程序加载了一个叫CIPHER_DLL的资源,这个资源其实就是经过加密的DLL,我用ResourceHacker(一个可以查看程序资源的工具)来查看程序中的资源

程序中确实有一个叫CIPHER_DLL的资源,且这个资源需要解密才能使用,图中**wow!**就是证明,把这个资源的二进制文件提出来,在解密回去,得到一个DLL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import struct
import os

//解密资源
def ResourceDecrypt():
filepath = 'C:\\Users\\86180\\Desktop\\CIPHER_DLL101'
dllpath = 'C:\\Users\\86180\\Desktop\\DLL.dll'
key = 'wow!'
size = os.path.getsize(filepath) # 获得文件大小
with open(filepath, 'rb') as cipher_stream:
with open(dllpath, 'wb') as dll_stream:
for i in range(size):
data = struct.unpack('B', cipher_stream.read(1))[0] ^ ord(key[i%4])
dll_stream.write(struct.pack('B', data))

把DLL用ida打开,查看加密函数,字符串已经告诉我们是SM4加密,没有魔改.

SM4Github代码链接

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
from ida import ida_bytes
import pysm4

# 从ida中提取加密后的数据
def Dump_ida_data():
addr = 0x00BFA808
res = []
for i in range(32):
res.append(get_byte(addr + i))
print(bytes(res).hex())


# 利用GitHub上找的sm4脚本,写的解密函数
def Decrypt():
string = b'Ez_5M4_C1pH@r!!!'.hex()
key = 0x457a5f354d345f433170484072212121
cipher1 = 0xea6358b78ce2a1e9c5298f53e8083259
cipher2 = 0xaf1b67aed9dacfc472ffb1ec7673f306
plain1 = hex(pysm4.decrypt(cipher1, key))[2:]
plain2 = hex(pysm4.decrypt(cipher2, key))[2:]
for i in range(0,len(plain1),2):
print(chr(int(plain1[i:i+2],16)),end='')
for i in range(0,len(plain1),2):
print(chr(int(plain2[i:i+2],16)),end='')

# flag{42b061b4cb41cfa89ca78047bde1856e}

child_protect

参考看雪的一篇文章:https://bbs.pediy.com/thread-95082.htm

这道题考的是双进程守护问题,上面的文章和这道题类似,但是难点在于调试。

ida打开后发现主函数无法正常反编译,而且看到了int3中断异常指令。由于是双进程守护,需要找到创建子进程的地方

通过搜索CreateProcess函数,确定子进程由函数sub_413670创建

继续跟踪,确定了两个函数调用子进程,分别是sub_413BE0和sub_413950

而函数sub_413950是一个关键函数,他对子进程做了修改。如果想让ida正确的反编译,我们需要nop掉所有的int3指令,且按照程序修改部分指令,这里我用010Editor进行修改。

首先找到所有的int3指令

上图就是需要我们首先nop掉的地方,用010Editor修改后再次用ida打开可以发现主函数已经可以正常反编译,但是还不能够动态调试,

因为所有的过程都是在子进程中进行,但是我们现在已经修改了父进程的数据和子进程一样,所以可以跳过调用子进程的步骤,这需要修改一部分数据。

经过多次下断点调试后,定位到函数sub_413D10,修改过后就可以进行调试,但是还有一个坑。。

每次调试到这里的时候都会闪退,无论怎么调都没有用,最后我选择的办法是直接patch掉调用这个函数的指令,因为这步比较简单,且不影响后面的数据。

在可以动调后,就可以分析算法了,算法有两处,一处异或,一处tea加密,异或函数我将他pach,直接看tea处

异或

tea

tea加密的key是在运行时生成的,但是如果不动态调试也可以直接复制整个函数跑一遍也可以,我喜欢动态调试(懒)

生成了8个数据,但是只用到了前四个,后面只需要找到加密后的数据就ok了,这里我用idapython

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
import idc 
from ida_bytes import *
import idaapi

def DumpCipher():
addr = []
res = []
start = 0x4122C8
end = 0x4123AE
curr_addr = start
dword = ''
while curr_addr <= end:
addr.append(curr_addr)
curr_addr = idc.next_head(curr_addr,end)

for i in range(32):
if i%4==0 and i != 0:
print(dword)
res.append(int(dword, 16))
dword = ''
data = idc.print_operand(addr[i], 1).replace('h', '')
if len(data) == 1:
data = '0' + data
elif len(data) == 3:
data = data[1:]
dword = dword + data
# print(dword)
res.append(int(dword, 16))
print(res)

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
#include <stdio.h>
#include <stdint.h>


void TEA_decrypt(uint32_t* v, uint32_t* k)
{
uint32_t sum = 0xC6EF3720, delta = 0x9E3779B9;
uint32_t v0 = v[0], v1 = v[1];
for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}

void XOR(uint32_t *arr) {
uint32_t sum = 0x73FF8CA6;
for (int i = 0; i < 8; i++) {
arr[i] ^= sum;
sum -= 0x50FFE544;
}

}
int main()
{

uint32_t key[4] = { 0x82aba3fe, 0xac1ddca8, 0x87ec6b60, 0xa2394568 };
uint32_t c1[2] = { 3991505723, 3531991019 };
uint32_t c2[2] = { 1360416890, 2984008969 };
uint32_t c3[2] = { 1169078549, 1301151568 };
uint32_t c4[2] = { 3739270283, 2612854497 };
uint32_t v = 0;

TEA_decrypt(c1, key);
TEA_decrypt(c2, key);
TEA_decrypt(c3, key);
TEA_decrypt(c4, key);

uint32_t arr[] = { 0x3e9affcb, 0x478dce18, 0xb891a541, 0xc191b885, 0x6b9a84ff, 0xad61703e, 0xeb5f6c7d, 0x62772dbb };
XOR(arr);
for (int i = 0; i < 8; i++) {
printf("%x", arr[i]);
}
//打印的是flag的16进制表示形式,再用python转成字符串
return 0;
}

flag: Mesmerizing_And_Desirable_As_wjq

Enigma

这题有意思

异常处理反调试,函数sub_AE10C0调用了SetUnhandledExceptionFilter函数,意思就是发生异常时调用注册的函数,看看注册的函数

注册的函数是一个虚拟机,但是这个虚拟机很特殊,里面的handl是加减运算和位运算,参数是各种寄存器

回到函数sub_AE10C0发现了C7这个字节码触发非法指令异常,然后调用注册的函数相当于用那个虚拟机模拟了一些汇编指令,直接还原后静态看汇编就OK

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
128
129
130
           push    ebp
.text:004018F1 mov ebp, esp
.text:004018F3 push ebx
.text:004018F4 push esi
.text:004018F5 push edi
.text:004018F6 push offset sub_401630
.text:004018FB call ds:SetUnhandledExceptionFilter
.text:004018FB ; ---------------------------------------------------------------------------
.text:00401901 db 0C7h ; and eax, 0
.text:00401902 db 0FFh
.text:00401903 db 4
.text:00401904 db 1
.text:00401905 db 0
.text:00401906 ; ---------------------------------------------------------------------------
.text:00401906 xor ecx, ecx
.text:00401908
.text:00401908 loc_401908: ; CODE XREF: .text:00401922↓j
.text:00401908 cmp ecx, 20h ; ' '
.text:0040190B jge short loc_401924
.text:0040190B ; ---------------------------------------------------------------------------
.text:0040190D db 0C7h ; add eax, 11h
.text:0040190E db 0FFh
.text:0040190F db 0
.text:00401910 db 1
.text:00401911 db 11h
.text:00401912 db 0C7h ; and eax,1Fh
.text:00401913 db 0FFh
.text:00401914 db 4
.text:00401915 db 1
.text:00401916 db 1Fh
.text:00401917 ; ---------------------------------------------------------------------------
.text:00401917 mov dword ptr (byte_457A4C+24h)[ecx*4], eax
.text:00401917 ; ---------------------------------------------------------------------------
.text:0040191E db 0C7h ; inc ecx
.text:0040191F db 0FFh
.text:00401920 db 2
.text:00401921 db 3
.text:00401922 ; ---------------------------------------------------------------------------
.text:00401922 jmp short loc_401908
.text:00401924 ; ---------------------------------------------------------------------------
.text:00401924
.text:00401924 loc_401924: ; CODE XREF: .text:0040190B↑j
.text:00401924 xor ecx, ecx
.text:00401926
.text:00401926 loc_401926: ; CODE XREF: .text:00401956↓j
.text:00401926 cmp ecx, 20h ; ' '
.text:00401929 jge short loc_401958
.text:0040192B mov ebx, dword ptr (byte_457A4C+24h)[ecx*4]
.text:00401932 mov edx, dword ptr (byte_457A4C+28h)[ecx*4]
.text:00401939 mov al, byte_457A4C[edx]
.text:0040193F mov byte_4579E0[ebx], al
.text:00401945 mov al, byte_457A4C[ebx]
.text:0040194B mov byte_4579E0[edx], al
.text:0040194B ; ---------------------------------------------------------------------------
.text:00401951 db 0C7h
.text:00401952 db 0FFh ; add ecx, 2
.text:00401953 db 0
.text:00401954 db 3
.text:00401955 db 2
.text:00401956 ; ---------------------------------------------------------------------------
.text:00401956 jmp short loc_401926
.text:00401958 ; ---------------------------------------------------------------------------
.text:00401958
.text:00401958 loc_401958: ; CODE XREF: .text:00401929↑j
.text:00401958 xor ecx, ecx
.text:0040195A
.text:0040195A loc_40195A: ; CODE XREF: .text:00401992↓j
.text:0040195A cmp ecx, 20h ; ' '
.text:0040195D jge short loc_401994
.text:0040195F mov bl, byte_4579E0[ecx]
.text:0040195F ; ---------------------------------------------------------------------------
.text:00401965 db 0C7h ; and ebx, 1Fh
.text:00401966 db 0FFh
.text:00401967 db 4
.text:00401968 db 2
.text:00401969 db 1Fh
.text:0040196A db 0C7h ; shl bl, 3
.text:0040196B db 0FFh
.text:0040196C db 7
.text:0040196D db 2
.text:0040196E db 3
.text:0040196F ; ---------------------------------------------------------------------------
.text:0040196F mov esi, ecx
.text:00401971 inc esi
.text:00401972 and esi, 1Fh
.text:00401975 mov dl, byte_4579E0[esi]
.text:0040197B and dl, 0E0h
.text:0040197E and edx, 0FFh
.text:0040197E ; ---------------------------------------------------------------------------
.text:00401984 db 0C7h ; shr dl, 5
.text:00401985 db 0FFh
.text:00401986 db 8
.text:00401987 db 4
.text:00401988 db 5
.text:00401989 ; ---------------------------------------------------------------------------
.text:00401989 or bl, dl
.text:0040198B mov byte_457A04[ecx], bl
.text:00401991 inc ecx
.text:00401992 jmp short loc_40195A
.text:00401994 ; ---------------------------------------------------------------------------
.text:00401994
.text:00401994 loc_401994: ; CODE XREF: .text:0040195D↑j
.text:00401994 mov al, byte_457A04
.text:00401999 mov byte_457A28, al
.text:0040199E mov ecx, 1
.text:004019A3
.text:004019A3 loc_4019A3: ; CODE XREF: .text:004019CE↓j
.text:004019A3 cmp ecx, 20h ; ' '
.text:004019A6 jge short loc_4019D0
.text:004019A8 mov bl, byte_457A04[ecx]
.text:004019AE mov esi, ecx
.text:004019AE ; ---------------------------------------------------------------------------
.text:004019B0 db 0C7h ; inc ecx
.text:004019B1 db 0FFh
.text:004019B2 db 3
.text:004019B3 db 5
.text:004019B4 ; ---------------------------------------------------------------------------
.text:004019B4 xor bl, byte_457A04[esi]
.text:004019BA mov esi, ecx
.text:004019BA ; ---------------------------------------------------------------------------
.text:004019BC db 0C7h ; and esi, 3
.text:004019BD db 0FFh
.text:004019BE db 4
.text:004019BF db 5
.text:004019C0 db 3
.text:004019C1 ; ---------------------------------------------------------------------------
.text:004019C1 xor bl, byte ptr aBier[esi] ; "Bier"
.text:004019C7 mov byte_457A28[ecx], bl
.text:004019CD inc ecx
.text:004019CE jmp short loc_4019A3

加密过程不复杂,首先计算了一组0到31的无序数,然后按照无序数两个一组交换输入数据,接着进行位运算,总体向前移动了三位,最后进行了异或加密。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 解异或加密的脚本

def xor():
res = []
key = 'Bier'
Cipher = '938b8f431268f7907a4b6e421301b42120738d68cb19fcf8b26bc4abc89b8d22'
for i in range(0,len(Cipher),2):
res.append(int(Cipher[i:i+2], 16))
for i in range(1,len(res)):
res[i] = res[i] ^ ord(key[(i)%4]) ^ res[i-1]
return res

# [147, 113, 155, 170, 250, 251, 105, 139, 179, 145, 154, 170, 251, 147, 66, 17, 115, 105, 129, 155, 18, 98, 251, 113, 129, 131, 34, 251, 113, 131, 107, 59]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 位运算脚本

#include <stdio.h>
#include <stdint.h>
#include <Windows.h>

int main()
{

BYTE byte[] = { 147, 113, 155, 170, 250, 251, 105, 139, 179, 145, 154, 170, 251, 147, 66, 17, 115, 105, 129, 155, 18, 98, 251, 113, 129, 131, 34, 251, 113, 131, 107, 59 };
BYTE index = 0;
unsigned char first = byte[0] >> 3;
printf("%c", (147 >>3)&0xFF | (59 <<5)&0xFF);
for (int i = 0; i < 31; i++) {
BYTE x = (byte[i] << 5) & 0xFF;
BYTE y = (byte[i + 1] >> 3) & 0xFF;
printf("%c", x | y);
}
}

// rn3u__m1vr3U_rhB.m03bL_n00d_n0mg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 交换位置

def Change():
flag = list('rn3u__m1vr3U_rhB.m03bL_n00d_n0mg')
index = []
num = 0
for i in range(32):
num += 0x11
num &= 0x1F
index.append(num)
for i in range(0,32,2):
flag[index[i]], flag[index[i+1]] = flag[index[i+1]], flag[index[i]]
for i in range(len(flag)):
print(flag[i],end='')

flag: B0mb3_L0nd0n_m0rg3n_um_v13r_Uhr.

总结

​ 这道题过程不复杂,调用了SetUnhandledExceptionFilter,注册异常处理函数,通过C7这个非法指令触发异常,运行这个异常处理函数,从而进行加密操作

​ 如果想动态调试的话可以直接改字节码(我只会这一种方法。。)不知道还有没有其他方法,但是做题的时候需要细心一点,因为汇编的一些过程没有看清导致浪费了我很多时间

obfu

我搞得最烦的一道题。。以前没有搞过带iv的AES,也从来没有看过sha256和md5的反编译代码,这次算是遇到了。。看了很久都没有看出来,最后看了别的师傅的wp才出来的。其实这道题知道了md5和sha256之后加密过程并不复杂,但是如果是第一次遇到那就恼火了(比如像我)

总共算下来这道题一共有5个步骤

  1. 字符串转16进制

    打个比方:比如我们输入123456,那么程序会将我们输入的转为 12 23 45这三个16进制数进行保存,因此前面只允许输入0-9 a-f的字符

    image-20210205003234483

  2. 位运算

    总体向前移动了三位(我怀疑这次的题是一个人出的,加密算法都好相似。。)

  3. 计算key和iv

  4. rc4和AES

    注意AES是解密,题目用的是Res盒子,并不是常规的S盒子,所以我们写脚本的时候是加密

5.与用md5生成的序列比较

如果序列号比对成功,程序就会读取flag.enc中的二进制数据,我们输入然后通过加密计算得到的序列号就会作为key解密flag.enc的二进制数据流,同样也是AES CBC模式,但是这部分不用我们关心,我们只需要得到正确的序列号就可以了

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
import hashlib
import binascii
from Cryptodome.Cipher import AES,ARC4

flag = ''
string = b'admin'
new_res = [0x8c]

Plain_AES = binascii.unhexlify(hashlib.md5(string).hexdigest())
digest = hashlib.sha256(string).hexdigest()

res = [i for i in binascii.unhexlify(digest)]
for i in range(1,32):
new_res.append(res[i] ^ res[i - 1])

# aes加密
key = binascii.a2b_hex((bytes(new_res).hex()[:32]).encode())
iv = binascii.a2b_hex((bytes(new_res).hex()[32:]).encode())
aes = AES.new(key, AES.MODE_CBC, iv)
Cipher_AES = aes.encrypt(Plain_AES)

# rc4加密
rc4_cipher = Cipher_AES
rc4 = ARC4.new(key)
rc4_plain = rc4.decrypt(rc4_cipher)
print(rc4_plain)

# 位运算
big_num_hex = binascii.hexlify(rc4_plain)
big_num_bin = bin(int(big_num_hex, 16))[2:].rjust(16*8,'0')
big_num_bin = big_num_bin[3:] + big_num_bin[:3]
for i in range(16):
flag += hex(int(big_num_bin[i*8:i*8+8],2))[2:].rjust(2,'0')
print(flag)

# 653b987431e5a2fc7c3d748fba008869

复现总结

  1. 常见的加密算法特征不清楚(大问题)
  2. 看汇编不仔细(需要改掉自己粗心的毛病)
  3. windows方面知识还是不够
  4. 题型见得太少了,感觉做了一道题就累的要死😑,真的佩服比赛从开始肝到最后的师傅们
  5. 学习了怎么使用Cryptdome库,windows的几种反调试(异常注册,debug bloker),下次在遇到就不会这么痛苦了吧(大概)