2023年春秋杯冬季赛 RE Writeup

本文最后更新于 2024年1月27日 凌晨

2023年春秋杯冬季赛 Reverse 题解

UPX2023

0x0 脱壳

工具一键脱壳


带有UPX壳,直接用工具脱会报错,打开010Editor,将小写的upx改为大写的upx即可

修改之后,就可以一键脱壳了

1
2
3
4
5
6
7
8
9
10
 ⚡Administrator ❯❯ upx.exe -d .\upx2023fix.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024

File size Ratio Format Name
-------------------- ------ ----------- -----------
1859146 <- 1177162 63.32% win64/pe upx2023fix.exe

Unpacked 1 file.

手动脱壳

自己做的时候没有注意到大小写的问题,也就借此机会记录一下详细手脱的过程
使用的软件:x64dbg及其自带插件 Scylla
参考链接:https://www.anquanke.com/post/id/99750

首先使用ida找到大跳转

在x64dbg中找到这个jmp直接,跟进去

找到OEP

选择插件 Scylla dump另存为

dump完成后修复IAT表,首先搜索IAT表
Get
Imports

修复Dump出来的文件

出现下面的情况表示修复成功

我们可以直接运行试试

1
2
3
❯ .\upx2023_dump_SCY.exe
input your flag(用flag{}包裹): aaaaaaaaaaaaaaaaaaaa
len error

可以运行,脱壳成功

但是电脑重启之后好像就不行 了…
并且对照工具脱壳后的结果,差异有点大,主要是在符号上

0x1 分析

main函数的逻辑比较清晰

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
std::ostream *v3; // rax
char *tb; // rax
int cmpData[44]; // [rsp+20h] [rbp-60h] BYREF
char input[16]; // [rsp+D0h] [rbp+50h] BYREF
char v8[16]; // [rsp+E0h] [rbp+60h] BYREF
char v9[20]; // [rsp+F0h] [rbp+70h] BYREF
int ta; // [rsp+104h] [rbp+84h]
unsigned int Seed; // [rsp+108h] [rbp+88h]
int i; // [rsp+10Ch] [rbp+8Ch]

_main(argc, argv, envp);
Seed = time(0i64);
srand(Seed);
std::string::string((std::string *)input);
std::operator<<<std::char_traits<char>>((std::ostream *)&std::cout, Str);
std::operator>><char>((std::istream *)&std::cin, (std::string *)input);
std::string::string((std::string *)v9, (const std::string *)input);
change(v8, v9);
std::string::operator=(input, v8);
std::string::~string((std::string *)v8);
std::string::~string((std::string *)v9);
if ( std::string::length((std::string *)input) != 42 )// 判断长度
{
v3 = (std::ostream *)std::operator<<<std::char_traits<char>>((std::ostream *)&std::cout, "len error");
std::endl<char,std::char_traits<char>>(v3);
exit(0);
}
qmemcpy(cmpData, &::cmpData, 0xA8ui64);
for ( i = 0; i <= 41; ++i )
{
ta = rand() % 255;
tb = (char *)std::string::operator[](input, i);
if ( (ta ^ *tb) != cmpData[i] )
exit(0);
}
std::string::~string((std::string *)input);
return 0;
}

大概流程:输入经过打乱顺序后->判断长度是否为42->以当前的时间为种子生成随机数,使用该随机数异或打乱后的flag-> 进行对比

打乱顺序可以通过调试得到固定的次序

1
2
input: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop
en: AEIMQUYcgkoBDFHJLNPRTVXZbdfhjlnpCGKOSWaeim

根据上述关系可进行还原

0x2 求解

分析的重点是对随机数的处理,首先我们通过提示知道flag的格式是flag{xxxxx},那么我们可以通过异或还原出六个随机数,并且通过打乱顺序后字符串确定其索引

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
oStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop'
cStr = 'AEIMQUYcgkoBDFHJLNPRTVXZbdfhjlnpCGKOSWaeim'

order = [oStr.index(cStr[i]) for i in range(len(cStr))]

demoStr = 'flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}'
enStr = ''

for i in range(len(demoStr)):
enStr += demoStr[order[i]]
print(enStr)
print(f"{enStr.index('f')} -> {hex(cmpData[enStr.index('f')] ^ ord('f'))}")
print(f"{enStr.index('l')} -> {hex(cmpData[enStr.index('l')] ^ ord('l'))}")
print(f"{enStr.index('a')} -> {hex(cmpData[enStr.index('a')] ^ ord('a'))}")
print(f"{enStr.index('g')} -> {hex(cmpData[enStr.index('g')] ^ ord('g'))}")
print(f"{enStr.index(r'{')} -> {hex(cmpData[enStr.index(r'{')] ^ ord('{'))}")
print(f"{enStr.index(r'}')} -> {hex(cmpData[enStr.index(r'}')] ^ ord('}'))}")

'''
0 -> 111
11 -> 170
32 -> 155
12 -> 2
1 -> 24
31 -> 128
'''

通过网站得到出题前的 2023.1.1的时间戳 Epoch Converter - Unix Timestamp
Converter
和文件的修改时间的时间戳,写爆破

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

int main() {
// 设置自定义的种子值
int cmp[] = { 111, 24, 170, 2, 128, 155};
int order[] = {0, 1, 11, 12, 31, 32};// f{lg}a
for (int k = 1672531200; k < 1685836800; k++) {
unsigned int customSeed = k;
srand(customSeed);

// 生成一些随机数
int temp = 0;
int delta = 0;
for (int i = 0; i < 33; ++i) {
int randomValue = rand();
randomValue %= 255;
if (i == order[temp]) {
if (randomValue == cmp[temp]) {
delta++;
}
temp++;
}
}
if (delta == 6)
printf("The seed is %x\n", customSeed); // The seed is 64437f56
}
return 0;
}

得到异或值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>

int main() {
unsigned int customSeed = 0x64437f56;
srand(customSeed);
for (int i = 0; i < 42; i++) {
int randomValue = rand();
// printf("The randomValue-> %x\nthe result = %x\n", randomValue, randomValue % 255);
printf("0x%x,", randomValue % 255);
// 0x6f,0x18,0xec,0xc4,0x3a,0xba,0x5d,0x61,0x3d,0x33,0xa9,0xaa,0x2,0x11,0x71,0x8b,0xa2,0x26,0xe,0x4d,0x83,0x42,0x70,0xca,0x50,0x71,0xe7,0x6b,0xf,0x32,0x9f,0x80,0x9b,0xb7,0xe3,0xb8,0xe0,0x1c,0x10,0xb4,0x2a,0x39
}
return 0;
}

根据异或值得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
oStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop'
cStr = 'AEIMQUYcgkoBDFHJLNPRTVXZbdfhjlnpCGKOSWaeim'

cmpData = [0x00000009, 0x00000063, 0x000000D9, 0x000000F6, 0x00000058, 0x000000DD, 0x0000003F, 0x0000004C, 0x0000000F, 0x0000000B, 0x00000098, 0x000000C6, 0x00000065, 0x00000021, 0x00000041, 0x000000ED, 0x000000C4, 0x0000000B, 0x0000003A, 0x0000007B, 0x000000E5, 0x00000075, 0x0000005D, 0x000000A9, 0x00000031, 0x00000041, 0x000000D7, 0x00000052, 0x0000006C, 0x0000000A, 0x000000FA, 0x000000FD, 0x000000FA, 0x00000084, 0x000000DB, 0x00000089, 0x000000CD, 0x0000007E, 0x00000027, 0x00000085, 0x00000013, 0x00000008]
xorData = [0x6f,0x18,0xec,0xc4,0x3a,0xba,0x5d,0x61,0x3d,0x33,0xa9,0xaa,0x2,0x11,0x71,0x8b,0xa2,0x26,0xe,0x4d,0x83,0x42,0x70,0xca,0x50,0x71,0xe7,0x6b,0xf,0x32,0x9f,0x80,0x9b,0xb7,0xe3,0xb8,0xe0,0x1c,0x10,0xb4,0x2a,0x39]
res = []
for i in range(len(cmpData)):
res.append(xorData[i] ^ cmpData[i])
result = ''.join(chr(i) for i in res)
enOrder = [cStr.index(oStr[i]) for i in range(len(oStr))]

flag = ''
for i in range(len(result)):
flag += result[enOrder[i]]
print(flag) # flag{0305f8f2-14b6-fg7b-bc7a-010299c881e1}

file_encryptor

0x0 去花指令

TLS回调函数里面有反调试,TLS回调函数和main函数里面有异常处理,这些干扰了我们的分析

首先处理TLS回调函数中的反调试和异常处理

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
.text:004018DA                 test    eax, eax
.text:004018DC jz short loc_401957
.text:004018DE ; __try { // __except at loc_401916
.text:004018DE mov [ebp+ms_exc.registration.TryLevel], 0
.text:004018E5 call ds:IsDebuggerPresent
.text:004018EB test eax, eax
.text:004018ED jz short loc_4018F7
.text:004018EF push 0 ; Code
.text:004018F1 call ds:__imp_exit
.text:004018F7 ; ---------------------------------------------------------------------------
.text:004018F7
.text:004018F7 loc_4018F7: ; CODE XREF: TlsCallback_0+4D↑j
.text:004018F7 mov [ebp+var_20], 0
.text:004018FE mov ecx, [ebp+var_20]
.text:00401901 mov dword ptr [ecx], 2Ah ; '*'
.text:00401901 ; } // starts at 4018DE
.text:00401907 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:0040190E jmp short loc_401950
.text:00401910 ; ---------------------------------------------------------------------------
.text:00401910
.text:00401910 loc_401910: ; DATA XREF: .rdata:stru_403828↓o
.text:00401910 ; __except filter // owned by 4018DE
.text:00401910 mov eax, 1
.text:00401915 retn
.text:00401916 ; ---------------------------------------------------------------------------
.text:00401916
.text:00401916 loc_401916: ; DATA XREF: .rdata:stru_403828↓o
.text:00401916 ; __except(loc_401910) // owned by 4018DE
.text:00401916 mov esp, [ebp+ms_exc.old_esp]
.text:00401919 mov [ebp+var_1C], 0
.text:00401920 jmp short loc_40192B

过了反调试后,程序会在00401901触发异常,该异常的处理在00401916,那么从004018DA到00401916这一段的代码应该是可以直接patch掉的,Patch后:

1
2
3
4
5
6
7
8
9
10
11
void __stdcall TlsCallback_0(int a1, int a2, int a3)
{
int i; // [esp+14h] [ebp-1Ch]

if ( byte_405034 )
{
for ( i = 0; i < 23; ++i )
byte_40501C[i] ^= 0x22u;
byte_405034 = 0;
}
}

接下来处理main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:0040199A                 mov     large fs:0, eax
.text:004019A0 mov [ebp+ms_exc.old_esp], esp
.text:004019A3 ; __try { // __except at loc_4019CC
.text:004019A3 mov [ebp+ms_exc.registration.TryLevel], 0
.text:004019AA mov [ebp+var_3C], 0
.text:004019B1 mov eax, [ebp+var_3C]
.text:004019B4 mov dword ptr [eax], 2Ah ; '*'
.text:004019B4 ; } // starts at 4019A3
.text:004019BA mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:004019C1 jmp loc_401B18
.text:004019C6 ; ---------------------------------------------------------------------------
.text:004019C6
.text:004019C6 loc_4019C6: ; DATA XREF: .rdata:stru_403848↓o
.text:004019C6 ; __except filter // owned by 4019A3
.text:004019C6 mov eax, 1
.text:004019CB retn
.text:004019CC ; ---------------------------------------------------------------------------
.text:004019CC
.text:004019CC loc_4019CC: ; DATA XREF: .rdata:stru_403848↓o
.text:004019CC ; __except(loc_4019C6) // owned by 4019A3
.text:004019CC mov esp, [ebp+ms_exc.old_esp]
.text:004019CF push 0 ; lpModuleName
.text:004019D1 call ds:GetModuleHandleW

004019B4处的异常触发会跳转到004019CC,从004019A3到004019CC之间的代码都可以nop掉
这里有一个花指令

1
2
.text:008419F5                 call    sub_842140
.text:008419FA call near ptr 15C9782h
1
2
3
4
5
.text:00842140 sub_842140      proc near               ; CODE XREF: .text:00841329↑p
.text:00842140 ; _main+85↑p
.text:00842140 add dword ptr [esp+0], 1
.text:00842144 retn
.text:00842144 sub_842140 endp

sub_842140 函数中调整了函数返回后的下一条地址,也就是返回地址是
008419FB,patch修改一下

1
2
3
4
5
6
7
8
.text:008419F2                 mov     [ebp+hResInfo], eax
.text:008419F5 nop
.text:008419F6 nop
.text:008419F7 nop
.text:008419F8 nop
.text:008419F9 nop
.text:008419FA nop
.text:008419FB cmp [ebp+hResInfo], 0

修改后就可以见到较为完整的伪代码了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *Src; // [esp+24h] [ebp-34h]
HGLOBAL hResData; // [esp+28h] [ebp-30h]
HMODULE hModule; // [esp+2Ch] [ebp-2Ch]
HRSRC hResInfo; // [esp+30h] [ebp-28h]
DWORD Size; // [esp+34h] [ebp-24h]
DWORD i; // [esp+38h] [ebp-20h]
void *v10; // [esp+3Ch] [ebp-1Ch]

hModule = GetModuleHandleW(0);
hResInfo = FindResourceW(hModule, (LPCWSTR)0x65, L"DATA");
if ( hResInfo )
{
Size = SizeofResource(hModule, hResInfo);
hResData = LoadResource(hModule, hResInfo);
v10 = (void *)sub_842156(Size);
if ( hResData )
{
Src = LockResource(hResData);
if ( Src )
{
memcpy(v10, Src, Size);
for ( i = 0; i < Size; ++i )
*((_BYTE *)v10 + i) ^= 0x33u;
dword_84541C = sub_841CE0(v10, Size);
}
FreeResource(hResData);
}
j_j_free(v10);
}
((void (*)(void))loc_841320)();
sub_842000();
system("pause");
return 0;
}

类似的花指令还有

1
2
3
4
5
6
7
8
9
.text:00841320                 push    ebp
.text:00841321 mov ebp, esp
.text:00841323 sub esp, 1Ch
.text:00841326 push ebx
.text:00841327 push esi
.text:00841328 push edi
.text:00841329 call sub_842140
.text:0084132E call near ptr 81301Bh
.text:00841333 inc dword ptr [ebp-767BF040h]

同样的进行Patch处理

1
2
3
4
5
6
7
8
.text:00841328                 push    edi
.text:00841329 nop
.text:0084132A nop
.text:0084132B nop
.text:0084132C nop
.text:0084132D nop
.text:0084132E nop
.text:0084132F call sub_841050

此时整个程序就较为清晰了

0x1 分析

相当于附件中还有另一个程序,程序先加载这个程序,然后进行解密,解密后得到

1
2
3
4
5
6
7
cryptsp.dll:00E30000 cryptsp_dll     segment byte public 'CODE' use32
cryptsp.dll:00E30000 assume cs:cryptsp_dll
cryptsp.dll:00E30000 ;org 0E30000h
cryptsp.dll:00E30000 assume es:debug013, ss:debug013, ds:debug013, fs:debug013, gs:debug013
cryptsp.dll:00E30000 db 4Dh ; M
cryptsp.dll:00E30001 db 5Ah ; Z
cryptsp.dll:00E30002 db 90h

在函数sub_F81050中,检查了卷序列号

1
2
3
4
5
6
7
8
9
10
11
int sub_F81050()
{
DWORD VolumeSerialNumber; // [esp+4h] [ebp-8h] BYREF

VolumeSerialNumber = 0;
GetVolumeInformationA(0, 0, 0, &VolumeSerialNumber, 0, 0, 0, 0);
if ( VolumeSerialNumber == 0x7DAB1D35 )
return 0x7DAB1D35;
else
return 0;
}

卷序列号是一个唯一标识磁盘卷的数值,这里我们需要手动跳过一下,或者修改程序,直接让其返回

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
void sub_F82000()
{
HANDLE hFindFile; // [esp+0h] [ebp-874h]
PWSTR ppszPath; // [esp+4h] [ebp-870h] BYREF
struct _WIN32_FIND_DATAW FindFileData; // [esp+8h] [ebp-86Ch] BYREF
WCHAR v3[260]; // [esp+258h] [ebp-61Ch] BYREF
WCHAR FileName[260]; // [esp+460h] [ebp-414h] BYREF
WCHAR pszDest[260]; // [esp+668h] [ebp-20Ch] BYREF

if ( !SHGetKnownFolderPath(&rfid, 0, 0, &ppszPath) )
{
if ( PathCombineW(pszDest, ppszPath, L"document") )
{
if ( PathCombineW(FileName, pszDest, L"*.*") )
{
hFindFile = FindFirstFileW(FileName, &FindFileData);
if ( hFindFile != (HANDLE)-1 )
{
do
{
if ( FindFileData.cFileName[0] != 46 )
{
PathCombineW(v3, pszDest, FindFileData.cFileName);
sub_F817E0(FindFileData.cFileName, v3);
}
}
while ( FindNextFileW(hFindFile, &FindFileData) );
FindClose(hFindFile);
}
}
}
CoTaskMemFree(ppszPath);
}
}

上述代码遍历了用户目录的文档目录

形成了通配符

得把文件放到这个目录

进入函数之后,发现有rsa相关特征

1
2
3
debug022:007D7360 off_7D7360      dd offset rsaenh_CPGenKey
...
debug022:007CA5E0 off_7CA5E0 dd offset rsaenh_CPAcquireContext

跟进去之后,调用了解密的cryptsp.dll
因为是对文件进行加密,我们直接找到加密的地方

1
2
if ( !v6(*a1, 0, FileSize == 0, 0, lpBuffer, &NumberOfBytesRead, dwBytes)
|| !WriteFile(hFile, lpBuffer, NumberOfBytesRead, &NumberOfBytesRead, 0) )

这里的v6是dll中的一个函数,跟进v6,调用了 rasenh.dll 中的CPEncrypt函数

经过调试呢,发现就是这里进行的加密,也就是CPEncrypt函数进行加密

0x2 求解

去分析这个文件的加密有点复杂,因为这个dll里面是有解密函数的,我们可以通过更改调用函数实现解密
找到CPDecrypt函数

将esi寄存器的值修改为这个,直接F9 之后看1.txt

得到了flag

coos

0x0 分析

main函数

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
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-184h]
char v5; // [esp+0h] [ebp-184h]
int v6; // [esp+Ch] [ebp-178h]
unsigned int i; // [esp+D8h] [ebp-ACh]
int *a2; // [esp+E4h] [ebp-A0h] BYREF
_QWORD v9[4]; // [esp+E8h] [ebp-9Ch] BYREF
int *Str[27]; // [esp+10Ch] [ebp-78h] BYREF
int **ta; // [esp+178h] [ebp-Ch]

__CheckForDebuggerJustMyCode(&unk_972016);
printf("%d\n", dword_9705D0[0]);
sub_9612D0(); // 初始化相关内存
sub_96128A(); // 相关数据赋值
ta = 0;
j_memset(Str, 0, 0x64u);
deData(&unk_96F5AC); // 解密输出 “plz input ...”
printf(&unk_96F5AC, v4);
deData(&unk_96F5AC);
deData(aC); // 解密 %s
scanf(aC, (char)Str);
deData(aC);
if ( j_strlen((const char *)Str) != 32 )
exit(0);
ta = Str;
a2 = 0;
memset(v9, 0, 0x1C);
Encrypt((int *)Str, (int *)&a2);
for ( i = 0; i < 4; ++i )
{
v6 = dword_9704C8[i + 44];
if ( (&a2)[2 * i] != (int *)dword_96E280[2 * v6] || LODWORD(v9[i]) != dword_96E284[2 * v6] )
exit(0);
}
deData(&unk_96F5C4);
printf(&unk_96F5C4, v5);
deData(&unk_96F5C4);
return 0;
}

可知长度是32,在主要的加密函数中有一个switch语句,还有很多分支,分析了一下,发现是一个虚拟机逆向,大概是个用32位模拟64位环境的过程
调试分析较为麻烦,这里就使用之前用过的idapython来使得在调试的过程中能够打印出相应的操作

0x1 IDApython 脚本编写

我将switch语句中用到的一些函数进行了分析,用一些指令来表示他们

这里面的PUSH 和 POP 写的有点随便

然后在适当的位置下断点,通过读取寄存器的值,打印当前函数的主要功能

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
import idc
import ida_dbg
import idaapi

BASE_ADDR = idaapi.get_imagebase()

# 断点字典
BPT_DICT = {'MOV':0x1484A,
'PUSH':0x14A29,
'POP': 0x149A8,
'SHL': 0x14C1C,
'SHR': 0x14C7C,
'ADD': 0x12D96,
'XOR': 0x12D36,
'AND': 0x12BD6
}

# 终止条件
BPT_DICT['END'] = 0x1513B

# 断点
def bpt(arr):
arr = list(arr)
for i in range(len(arr)):
idc.add_bpt(arr[i]+BASE_ADDR)


# 分发器
def dispatcher(addr):
opcode = next((key for key, value in BPT_DICT.items() if value == (addr-BASE_ADDR)), '')
if opcode == 'MOV':
a1 = idc.get_reg_value('eax')
a2 = idc.get_reg_value('ecx')
a3 = idc.get_reg_value('edx')
print(f"MOV : [{hex(a1)}] = {hex(a3)} {hex(a2)} ")

elif opcode == 'PUSH':
num = idc.get_reg_value('eax')
a1 = idc.get_reg_value('ecx')
a2 = idc.get_reg_value('edx')
print(f"PUSH: 0x51FBA8[{num}] = {hex(a2)} {hex(a1)} ")

elif opcode == 'POP':
num = idc.get_reg_value('eax')
a1 = idc.get_reg_value('edx')
a2 = idc.get_reg_value('ecx')
print(f"POP: [{hex(num)}] = {hex(a2)} {hex(a1)}")

elif opcode == 'SHL':
a2 = idc.get_reg_value('ecx')
a1H = idc.get_reg_value('eax')
a1L = idc.get_reg_value('edx')
print(f"SHL: {hex(a1L)} {hex(a1H)} << {hex(a2)}")

elif opcode == 'SHR':
a2 = idc.get_reg_value('ecx')
a1H = idc.get_reg_value('eax')
a1L = idc.get_reg_value('edx')
print(f"SHR: {hex(a1L)} {hex(a1H)} >> {hex(a2)}")

elif opcode == 'ADD':
a1 = idc.get_reg_value('ecx')
ebp = idc.get_reg_value('ebp')
a2 = idc.read_dbg_qword(ebp + 0xc)
print(f"ADD: {hex(a1)} += {hex(a2)}")

elif opcode == 'XOR':
eax = idc.get_reg_value('eax')
a1 = idc.read_dbg_qword(eax)
ebp = idc.get_reg_value('ebp')
a2 = idc.read_dbg_qword(ebp + 0xc)
print(f"XOR: {hex(a1)} ^= {hex(a2)}")

elif opcode == 'AND':
eax = idc.get_reg_value('eax')
a1 = idc.read_dbg_qword(eax)
ebp = idc.get_reg_value('ebp')
a2 = idc.read_dbg_qword(ebp + 0xc)
print(f"AND: {hex(a1)} &= {hex(a2)}")

else:
print(f"The addr is {hex(addr)}, THe opcode is {opcode}. Something Wrong.")

def main():
#
print("[*] Begin")

# 获取基址
print('BASE_ADDR= ' + hex(BASE_ADDR))


# 打上断点
bpt(BPT_DICT.values())


# 执行
idc.run_to(BPT_DICT['END']+BASE_ADDR)
idc.wait_for_next_event(ida_dbg.WFNE_SUSP, -1)
CurrEIP = idc.get_reg_value('eip')
# 判断是否到达终止条件
while(CurrEIP != BPT_DICT['END']+BASE_ADDR):
# 判断断点是否合法
if (CurrEIP-BASE_ADDR) not in BPT_DICT.values():
print(f"Something Wrong. The EIP is {hex(CurrEIP)}")
break
# 分发处理
dispatcher(CurrEIP)

# 下一轮循环
idc.run_to(BPT_DICT['END']+BASE_ADDR)
idc.wait_for_next_event(ida_dbg.WFNE_SUSP, -1)
CurrEIP = idc.get_reg_value('eip')


if __name__ == '__main__':
main()

处理得很慢,大概需要个十分钟左右,得到下面的结果(因为是8字节一组的加密,这里只选取一组数据,并展示其的几十行)

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
[*] Begin
BASE_ADDR= 0x500000
MOV : [0x51fb88] = 0x0 0x0
PUSH: 0x51FBA8[1] = 0x31313131 0x31313131
PUSH: 0x51FBA8[2] = 0x696c6f76 0x65696368
POP: [0x51fb78] = 0x696c6f76 0x65696368
POP: [0x51fb80] = 0x31313131 0x31313131
XOR: 0x696c6f7665696368 ^= 0x3131313131313131
XOR: 0x585d5e4754585259 ^= 0x33
MOV : [0x51fba0] = 0x0 0x0
MOV : [0x51fb88] = 0x0 0x0
MOV : [0x51fb78] = 0x0 0x0
SHL: 0x0 0x0 << 0x2
MOV : [0x51fb80] = 0x585d5e47 0x5458526a
SHR: 0x585d5e47 0x5458526a >> 0x0
MOV : [0x51fb78] = 0x585d5e47 0x5458526a
XOR: 0x585d5e475458526a &= 0xf
MOV : [0x51fb80] = 0x0 0x0
MOV : [0x51fb90] = 0x0 0x0
SHL: 0x0 0x0 << 0x2
SHL: 0x0 0x0 << 0x0
ADD: 0x0 += 0x0
ADD: 0x0 += 0x1
MOV : [0x51fb78] = 0x0 0x1
SHL: 0x0 0x1 << 0x2
MOV : [0x51fb80] = 0x585d5e47 0x5458526a
SHR: 0x585d5e47 0x5458526a >> 0x4
MOV : [0x51fb78] = 0x585d5e4 0x75458526
XOR: 0x585d5e475458526 &= 0xf
MOV : [0x51fb80] = 0x0 0xe
MOV : [0x51fb90] = 0x0 0x1
SHL: 0x0 0x1 << 0x2
SHL: 0x0 0xe << 0x4
ADD: 0x0 += 0xe0
ADD: 0x1 += 0x1
MOV : [0x51fb78] = 0x0 0x2
SHL: 0x0 0x2 << 0x2
MOV : [0x51fb80] = 0x585d5e47 0x5458526a
SHR: 0x585d5e47 0x5458526a >> 0x8
...
SHR: 0xdf6289f3 0x20c409d >> 0x3f
MOV : [0x51fb78] = 0x0 0x1
XOR: 0x1 &= 0x1
MOV : [0x51fb80] = 0x0 0x0
SHL: 0x0 0x1 << 0x0
ADD: 0x9847c872 += 0x1
ADD: 0x3f += 0x1
PUSH: 0x51FBA8[1] = 0xc0e302ce 0x9847c873
PUSH: 0x51FBA8[2] = 0xcb51f952 0x43105185
POP: [0x51fb78] = 0xcb51f952 0x43105185
POP: [0x51fb80] = 0xc0e302ce 0x9847c873
XOR: 0xcb51f95243105185 ^= 0xc0e302ce9847c873
XOR: 0xbb2fb9cdb5799f6 ^= 0x33

其中,有一些数据的来源无法确定,还需要经过调试。

0x2 复现算法&求解

经过调试后,发现其加密算法大概如下

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
xorData = [0x696C6F7665696368, 0xCEADD4F8344B7D10, 0xCEBF000259A5B1DB, 0xD1EE63EA2F3AB735, 0xF7EBA4C0BB3AFC6A, 0x0FCB5F041F47B9F6, 0x7BAC2E8CEFDFAE97, 0x2CE86AE8883F2D42, 0x98CDB684B9EEB0A8, 0xC8F10A0794B3A1E3, 0xA89E4C0DEA6336BB, 0xAD4DC0BA7323C479, 0xBA2FC2EC42A27960, 0x52287D00F6B53759, 0x6FC4C7BD96E8BF25, 0x84A63D6C5D48A1C4, 0x3C37F71239BF1354, 0xE4A9BB13CA1298E3, 0xA551883F04F0DFA7, 0x138696DAE392A6B7, 0x1CFCE1EF4E954623, 0xA7EB1762FC4E1A5F, 0x22BFD0AA9873F3DF, 0x19174775129FAC47, 0x1A834072808AFF42, 0x943851D760645D1C, 0x32BD54D0A06A0D46, 0x7AC4490F2250E153, 0xD06535542CCAF560, 0xC414628E29EB1142, 0x01C3F8BAF34194B8, 0xCB51F95243105185]
input = [0x3131313131313131] * 4
BoxA = [0x0000000000000002, 0x0000000000000001, 0x0000000000000007, 0x0000000000000004, 0x0000000000000008, 0x000000000000000F, 0x000000000000000E, 0x0000000000000003, 0x000000000000000D, 0x000000000000000A, 0x0000000000000000, 0x0000000000000009, 0x000000000000000B, 0x0000000000000006, 0x0000000000000005, 0x000000000000000C, 0x000000000000003F, 0x000000000000002F, 0x000000000000001F, 0x000000000000000F, 0x000000000000003E, 0x000000000000002E, 0x000000000000001E, 0x000000000000000E, 0x000000000000003D, 0x000000000000002D, 0x000000000000001D, 0x000000000000000D, 0x000000000000003C, 0x000000000000002C, 0x000000000000001C, 0x000000000000000C, 0x000000000000003B, 0x000000000000002B, 0x000000000000001B, 0x000000000000000B, 0x000000000000003A, 0x000000000000002A, 0x000000000000001A, 0x000000000000000A, 0x0000000000000039, 0x0000000000000029, 0x0000000000000019, 0x0000000000000009, 0x0000000000000038, 0x0000000000000028, 0x0000000000000018, 0x0000000000000008, 0x0000000000000037, 0x0000000000000027, 0x0000000000000017, 0x0000000000000007, 0x0000000000000036, 0x0000000000000026, 0x0000000000000016, 0x0000000000000006, 0x0000000000000035, 0x0000000000000025, 0x0000000000000015, 0x0000000000000005, 0x0000000000000034, 0x0000000000000024, 0x0000000000000014, 0x0000000000000004, 0x0000000000000033, 0x0000000000000023, 0x0000000000000013, 0x0000000000000003, 0x0000000000000032, 0x0000000000000022, 0x0000000000000012, 0x0000000000000002, 0x0000000000000031, 0x0000000000000021, 0x0000000000000011, 0x0000000000000001, 0x0000000000000030, 0x0000000000000020, 0x0000000000000010, 0x0000000000000000]
for i in range(len(input)):
for x in range(len(xorData)-1):
input[i] ^= xorData[x]
input[i] ^= 0x33
input[i] &= 0xffffffffffffffff
ta = 0
for k in range(0, 0x40, 4):
tc = input[i] >> k
tb = tc & 0xf
tb = BoxA[tb]
tb = tb << k
ta += tb
input[i] = ta
ta = 0
for k in range(0x40):
tc = input[i] >> k
tb = tc & 0x1
td = BoxA[k+0x10]
ta += tb << td
input[i] = ta
input[i] ^= 0xcb51f95243105185
input[i] ^= 0x33
print(hex(input[i]))

根据上述写出解密算法

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
import struct
enData = [0x9C149C0FCE40E599, 0xDF4C680DCD759C04, 0xBBAFB056E52E3DC0, 0x8E299C86B8F527CB]
xorData = [0x696C6F7665696368, 0xCEADD4F8344B7D10, 0xCEBF000259A5B1DB, 0xD1EE63EA2F3AB735, 0xF7EBA4C0BB3AFC6A, 0x0FCB5F041F47B9F6, 0x7BAC2E8CEFDFAE97, 0x2CE86AE8883F2D42, 0x98CDB684B9EEB0A8, 0xC8F10A0794B3A1E3, 0xA89E4C0DEA6336BB, 0xAD4DC0BA7323C479, 0xBA2FC2EC42A27960, 0x52287D00F6B53759, 0x6FC4C7BD96E8BF25, 0x84A63D6C5D48A1C4, 0x3C37F71239BF1354, 0xE4A9BB13CA1298E3, 0xA551883F04F0DFA7, 0x138696DAE392A6B7, 0x1CFCE1EF4E954623, 0xA7EB1762FC4E1A5F, 0x22BFD0AA9873F3DF, 0x19174775129FAC47, 0x1A834072808AFF42, 0x943851D760645D1C, 0x32BD54D0A06A0D46, 0x7AC4490F2250E153, 0xD06535542CCAF560, 0xC414628E29EB1142, 0x01C3F8BAF34194B8, 0xCB51F95243105185]
BoxA = [0x0000000000000002, 0x0000000000000001, 0x0000000000000007, 0x0000000000000004, 0x0000000000000008, 0x000000000000000F, 0x000000000000000E, 0x0000000000000003, 0x000000000000000D, 0x000000000000000A, 0x0000000000000000, 0x0000000000000009, 0x000000000000000B, 0x0000000000000006, 0x0000000000000005, 0x000000000000000C, 0x000000000000003F, 0x000000000000002F, 0x000000000000001F, 0x000000000000000F, 0x000000000000003E, 0x000000000000002E, 0x000000000000001E, 0x000000000000000E, 0x000000000000003D, 0x000000000000002D, 0x000000000000001D, 0x000000000000000D, 0x000000000000003C, 0x000000000000002C, 0x000000000000001C, 0x000000000000000C, 0x000000000000003B, 0x000000000000002B, 0x000000000000001B, 0x000000000000000B, 0x000000000000003A, 0x000000000000002A, 0x000000000000001A, 0x000000000000000A, 0x0000000000000039, 0x0000000000000029, 0x0000000000000019, 0x0000000000000009, 0x0000000000000038, 0x0000000000000028, 0x0000000000000018, 0x0000000000000008, 0x0000000000000037, 0x0000000000000027, 0x0000000000000017, 0x0000000000000007, 0x0000000000000036, 0x0000000000000026, 0x0000000000000016, 0x0000000000000006, 0x0000000000000035, 0x0000000000000025, 0x0000000000000015, 0x0000000000000005, 0x0000000000000034, 0x0000000000000024, 0x0000000000000014, 0x0000000000000004, 0x0000000000000033, 0x0000000000000023, 0x0000000000000013, 0x0000000000000003, 0x0000000000000032, 0x0000000000000022, 0x0000000000000012, 0x0000000000000002, 0x0000000000000031, 0x0000000000000021, 0x0000000000000011, 0x0000000000000001, 0x0000000000000030, 0x0000000000000020, 0x0000000000000010, 0x0000000000000000]
for i in range(len(enData)):
enData[i] ^= 0x33
enData[i] ^= 0xcb51f95243105185
for x in range(len(xorData)-2, -1, -1):
ta = 0
for k in range(0x40):
td = BoxA[k+16]
tc = enData[i] >> td
tb = tc & 0x1
ta += tb << k
enData[i] = ta
ta = 0
for k in range(0, 0x40, 4):
tc= enData[i] >> k
tb = tc & 0xf
tb = BoxA.index(tb)
ta += tb << k
enData[i] = ta
enData[i] ^= 0x33
enData[i] ^= xorData[x]
print(hex(enData[i]))
flag = b''.join(struct.pack("<Q", i) for i in enData)
print(flag) # a9d99caef9ae999a299129c91299fc95

在处理对比数据的时候,还是需要调试情况下去获取,并不是在一个连续的内存空间上


2023年春秋杯冬季赛 RE Writeup
https://l3isu7e.github.io/2024/01/27/ichunqiu2023/
作者
L3iSu7e
发布于
2024年1月27日
更新于
2024年1月27日
许可协议