Contents
  1. 1. 程序流程
  2. 2. 逆向分析
    1. 2.1. 查看基础信息
    2. 2.2. 反调试
      1. 2.2.1. TLS反调试
      2. 2.2.2. 线程反调试1
        1. 2.2.2.1. md5加密算法的识别
      3. 2.2.3. 线程反调试2
    3. 2.3. 解密函数分析
      1. 2.3.1. 解密函数0x100001
      2. 2.3.2. 解密函数0x100002
      3. 2.3.3. 解密函数0x100003
      4. 2.3.4. 编写解密程序
    4. 2.4. DLL分析
      1. 2.4.1. AES加密函数的识别
    5. 2.5. 解密flag
  3. 3. 参考
  4. 4. 总结

该程序没有壳,有三处简单的反调试,patch掉后,通过输入的检测后,会有三段解密函数来还原出一个dll,通过文件映射向dll内传递flag的密文,在dll中进行aes解密,最终得到flag。

程序流程

1614925201124

逆向分析

查看基础信息

运行一下,查看其输出结果。

1614862244096

首先使用Exeinfo查壳,发现没有壳,是X64的程序。

1614861975162

用X64dbg打开发现有地址随机化,用CFF关闭地址随机化OptionHandle->DllCharacteristics,便于后续继续分析。

1614862750802

反调试

TLS反调试

在main函数处下断点,运行后发现有弹框并退出进程,则判断有TLS反调试。

1614863314408

使用ida查看TLS函数处,发现有IsDebuggerPresent反调试和四个加密的数组。

1614864293475

手动Path掉反调试,继续分析,等待数组的内容解密后可以看到解密后的字符串。

1614735598871

  • NtQueryInformationProcess 取进程信息函数,可以用于反调试(有调试端口,可以检测进程是否被调试)。
  • ZwQueryInformationThread 获取线程信息,可以用于反调试。
  • NtQueryApcThread apc队列

APC注入可以让一个线程在它正常的执行路径运行之前执行一些其他的代码,每一个线程都有一个附加的APC队列,他们在线程处于可警告的时候才被处理(WaitForSingObjectEx,SleepEx)。

异步过程调用,apc可以看成就是内核里的定时器,为了给自己一个在本函数返回后还能执行的一次机会,有很多操作是需要在函数返回后才能执行.类似于析构函数但不完全是。

反反调试:跟进IsDebuggerPresentAPI中,进行如下修改即可。

1614910362383

线程反调试1

位置:mian函数->beginthreadex中的线程启动函数(140009180)-> sub_140008B20。

使用快照遍历进程获取进程的md5值与原有的md5值进行判断,当存在指定进程时则退出程序,这里可以判断为反调试。

img

反反调试方案:patch掉exit函数即可。

md5加密算法的识别

md5加密的两个特征:

  1. 加密后的字符是32位。
  2. 在md5函数中有查表相关的操作。

1614820886234

md5对比值

1614820914195

据此可以判断其为md5算法。

线程反调试2

这里为线程启动函数中的第二个函数,里面全部都是进行反调试的代码,除了调用常见的IsDebuggerPresentCheckRemoteDebuggerPresent来进行反调试,还调用了TLS解密后的函数NtQueryInformationProcessZwQueryInformationThread来进行DebugPort调试端口的判断。

1614909973818

反反调试:直接patch掉这个函数即可。

解密函数分析

顺利进入到main函数,开始下一步的分析,进入sub_140009C20函数中。

可以看到为全局变量qword_140016178qword_140016180 申请堆空间的操作。

跟进其中的sub_1400086C0函数内,发现了疑似函数参数内有编号0x10001~0x10003。

1614866705853

依次跟进这些函数中。

1614866417586

这里跟进函数中的虚表的第二项可以看到对应的虚函数的地址。

1614849163738

跳到对应的函数地址sub_14000a8a0去查看,发现有对原有数据进行异或的操作。

1614866496727

跟进去byte_1400111A0可以看到,其为一大段数据,长度为19456,这里可能藏有一个文件。

1614866637379

继续分析剩下的两个sub_140008370sub_1400083F0发现其结构与sub_14000a8a0中一致,找到虚表中的虚函数地址依次跟进去。

1614867024753

1614867077718

发现其都对byte_1400111A0数组内的数据进行了变换的操作,同时由于if判断条件的不同,各对其中的一部分数据做变换,猜测其可能为解密的函数,并与之前传入的ID可能有所关联。

退出去sub_1400086C0函数,回到sub_140009C20中,接着往下分析。

可以看到创建事件对象的操作,结合上述解密函数里有SetEvent设置事件对象的操作,猜测这里是为了防止重复对数据进行变换。

1614867903314

接着往下跟进sub_140008850,可以看到这里获取函数地址的操作,而这几个函数的名称是在上面分析过的TLS函数中解密的。

1614868067713

此时第一个函数sub_140009c20分析完毕,再次回到main函数中。

可以看到其有创建线程的操作,跟进其线程启动函数StatAddress中去。

1614868203054

跟进去后,依次对其中的函数来进行分析。

1614868251419

首先进入第一个函数sub_140008B20中去,进行分析。

可以看到其中的sub_140008300的参数为我们之前分析过的解密函数的对应序号,而其参数v7在下面sub_140006C10函数中被当作参数使用,并且其中参数还有之前看到过的qword_140016178,可以判定其与我们之前分析的疑似的解密函数必有关联。

跟进去sub_140008300函数中,发现其是将编号0x100001赋值给了a1+4的位置。

1614868567187

跟进去sub_140006c10,发现其中为解密函数调用的位置(虚表函数调用),并且a3为传入参数。

1614907502663

解密函数0x100001

查看其传入的a3参数为106,联想之前解密函数的内容,可以判断其为解密函数的“密钥”

1614907593190

解密函数0x100002

由于之前解密函数有三个,再次进入sub_140006c10后,按X查看交叉引用,发现其调用处也有三个。

1614907817714

跟进去后,发现sub_140008300和sub_140006c10其为成对出现的。

分别取得其传入参数a3(密钥)的值。

其中0x100002 => 0,根据之前解密函数内分析可知,其有一个是没有参数的,正好对应了起来。

在运行此解密函数之前有个判断条件,读取了 filePath:signature 文件流,判断其内容为加密后和MD5值做比较。

1614918293068

其中字符串的xor解密可以编写ida Python脚本来进行解密。

1
2
3
4
5
6
BEGIN = 0x140015ee0
len = 0x16
for i in range(len):
tmp = Byte(BEGIN + i)
tmp = tmp ^ 0x0c1
PatchByte(BEGIN+i, tmp)

对MD5值进行解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sub_140007DD0(Buffer, v0, (__int64)v10);
v8[0] = 0xFC;
v8[1] = 0xAE;
v8[2] = 0xEB;
v8[3] = 0x6E;
v8[4] = 0x34;
v8[5] = 0xB4;
v8[6] = 0x30;
v8[7] = 0x3E;
v8[8] = 0x99;
v8[9] = 0xB9;
v8[10] = 0x12;
v8[11] = 6;
v8[12] = 0xBD;
qmemcpy(v9, "2_+", sizeof(v9));

FCAEEB6E34B4303E99B91206BD325F2B => Overwatch

可以通过以下方式写入文件流:

1
type Test.txt >> WinRev.exe:signature

把”Overwatch”字符串写入后就能通过检测。

解密函数0x100003

继续寻找编号0x100003对应的密钥,可以看到在上方判断条件中有验证的函数sub_140009200,来验证输入。

1614908185009

跟进sub_140009200函数内,发现其对输入进行了加密验证,还原正确的输入。

1614908321097

编写解密程序

根据其算法编写对应的解密代码。

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
#include <iostream>

int main(int argc)
{
char v6[] = { 0x2F,0x1F,0x20,0x2E,0x34,0x4,0x37,0x2D,0x10,0x39,0x7C,0x22,0x7B,0x75,0x0A,0x38,0x39,0x21, 0};
char v7[0x13] = { 0 };
int v5[] = {1, 5, 4, 2, 3, 0};
char v8[0x12] = {};

for (size_t i = 0; i < 0x13; i++)
{
v6[i] ^= 0x45;
}

for (size_t k = 0; k < 0x12; k++)
{
v7[6 * (k / 6) + v5[k % 6]] = v6[k];
}

for (size_t i = 0; i < 0x12; i++)
{
v8[i] = v7[i] ^ i;
printf("%c", v8[i]);
}
return 0;
}

输出结果为

此结果就是我们的0x10003对应的密钥了。

此时整理可得:

  • sub_1400089E0 => 0x100001 key:106
  • sub_140008A80=> 0x100002 key:0
  • sub_140008910 => 0x100003 key:Akira_aut0_ch3ss_!

继续在线程启动函数中向下分析可以发现有向apc队列添加函数的操作。

1614910768527

1614910809762

跟进去其添加的函数地址sub_140009850。

可以看到其有WaitFormultipleObjects的操作,等待3个解密函数全部执行完成,之后创建了共享内存,并且申请了一段空间,将解密后的数据复制过去,并且对齐验证了PE标记,根据其创建事件时的字符串可以简单判断该加密后的文件为DLL文件,则可以猜测sub_140007D80中有LoadLibrary的操作。

img

查看导入表中的LoadLibrary,然后查看交叉引用,可以发现在sub_140007D80中却有引用。

1614912983070

1614913013372

1614913030721

由此基本可以判定sub_140007D80作为用调用DLL中的函数执行功能的操作,而被加密的数据正是DLL。

根据以上信息编写解密代码,将DLL文件解密出来。

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
#include <iostream>
#include <Windows.h>
#include <io.h>
#define DLLSIZE 19456

int main()
{
FILE* pFile = fopen("WinRev.exe", "rb+");

if (pFile == NULL)
{
return -1;
}

//获取文件大小
DWORD dwFileLen = _filelength(_fileno(pFile));

if (dwFileLen <= 0)
{
return -1;
}

PBYTE pFileBuff = new BYTE[dwFileLen];
ZeroMemory(pFileBuff,dwFileLen);
if (pFileBuff == NULL)
{
return -1;
}

fread(pFileBuff, 1, dwFileLen, pFile);
fclose(pFile);

PBYTE pDllBuff = new BYTE[19456];
ZeroMemory(pDllBuff, 19456);

memcpy(pDllBuff, pFileBuff + 0xEFA0, DLLSIZE);

//解密1001
DWORD pdwKey1001 = 106 ^ 0x33;
for (size_t i = 0; i < DLLSIZE; i++)
{
if (i % 3 == 1)
pDllBuff[i] ^= pdwKey1001; //函数解密
}

//解密1002
for (size_t i = 0; i < DLLSIZE; i++)
{
if (i % 3 == 2)
{
pDllBuff[i] = ((int)(unsigned __int8)pDllBuff[i] >> 4) | (16 * pDllBuff[i]);//函数解密
}
}

//解密1003
PCHAR pKey1003 = (PCHAR)"Akira_aut0_ch3ss_!";
DWORD dwKey1Len = strlen(pKey1003);
for (size_t i = 0; i < DLLSIZE; i++)
{
if (!(i % 3))
{
pDllBuff[i] ^= pKey1003[i / 3 % dwKey1Len];//函数解密
}
}

FILE* pFileNew = fopen("Test.DLL", "wb+");
fwrite(pDllBuff, 1, DLLSIZE, pFileNew);
fclose(pFileNew);

delete pFileBuff;
delete pDllBuff;
return 0;
}

DLL分析

可以看到DLL就是最终解密的主阵地了,在DLL中打开了共享内存,读取到了密文来进行解密,其中的密钥为Ak1i3aS3cre7K3y

1614913742712

AES加密函数的识别

ida中使用FindCrypto插件可以进行识别,但是发现ida7.5中装不上该插件,所以这里使用HashCryptoDetector来进行识别。

1614914482020

通过CFF来进行FOA=>VA的转换。

1614914574276

在Ida中按G键跳转过去,查看交叉引用,可以看到在函数sub_180001000中有对其的使用。

1614914761735

在函数180001000头部查看交叉引用,依次回溯可以最终确定到是在sub_180002800处进行的调用,根据其参数则可以判定sub_180002800为AES加密函数。

回溯过程如下:1800041e0=>sub_180001000=>sub_1800013D0=>sub_180002800

解密flag

根据分析结果对其在共享内存中传递的密文使用密钥Ak1i3aS3cre7K3y进行解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Src[0] = 0x94;
Src[1] = 0xBF;
Src[2] = 0x7A;
Src[3] = 0xC;
Src[4] = 0xA4;
Src[5] = 0x35;
Src[6] = 0x50;
Src[7] = 0xD1;
Src[8] = 0xC2;
Src[9] = 0x15;
Src[10] = 0xEC;
Src[11] = 0xEF;
Src[12] = 0x9D;
Src[13] = 0x9A;
Src[14] = 0xAA;
Src[15] = 0x56;
memcpy(v7, Src, 0x10ui64);

将其转换为base64.

1
2
3
4
>>> import base64
>>> strCryptoText = b"\x94\xBF\x7A\x0C\xA4\x35\x50\xD1\xC2\x15\xEC\xEF\x9D\x9A\xAA\x56"
>>> base64.b64encode(strCrypto).decode()
'lL96DKQ1UNHCFezvnZqqVg=='

在线解密得到flag值。

1614853619724

最后的flag值为flag{Ak1rAWin!}

参考

https://xz.aliyun.com/t/6042#toc-9

https://blog.csdn.net/qq_41252520/article/details/100738585

https://bbs.pediy.com/thread-255081.htm

总结

自从19年12月份下定决心想要从Web转到逆向以来,经过一年的学习,终于有能力去做RE题的复现了,感谢科锐,人生苦短,逆向苦长,虽然花了很大的代价,但是感觉一切都是值得的,选择了自己喜欢的方向去努力下去,相信操千曲而后能晓声,观千剑而后终能识器!

在解题过程中学习到了ida Python脚本的编写和加密函数的识别。

有兴趣的小伙伴可以下载题目来进行复现。

链接:https://pan.baidu.com/s/1sL45Tk-RLLEXKPjJ2woPSQ
提取码:ws05

Contents
  1. 1. 程序流程
  2. 2. 逆向分析
    1. 2.1. 查看基础信息
    2. 2.2. 反调试
      1. 2.2.1. TLS反调试
      2. 2.2.2. 线程反调试1
        1. 2.2.2.1. md5加密算法的识别
      3. 2.2.3. 线程反调试2
    3. 2.3. 解密函数分析
      1. 2.3.1. 解密函数0x100001
      2. 2.3.2. 解密函数0x100002
      3. 2.3.3. 解密函数0x100003
      4. 2.3.4. 编写解密程序
    4. 2.4. DLL分析
      1. 2.4.1. AES加密函数的识别
    5. 2.5. 解密flag
  3. 3. 参考
  4. 4. 总结