程序中有多处反调试,还有两处check判断,第一个check函数中有三段解密,最终还原出一个dll文件,然后通过第二个check函数,向dll文件中传入密文,并进行AES解密,解密的结果即为flag。
主函数:
1.查壳:
64位,C++,无壳。
IDA64打开,发现程序有基址随机化,去除:
详细请参考:去除程序的基址随机化_皮皮蟹!的博客-CSDN博客
2.在main函数处下断点,出现弹框并结束调试:
应该是TLS反调试,搜索TLS,找到TlsCallback_0函数,发现有IsDebuggerPresent反调试和四个加密的数组:
手动patch掉反调试:
(1)可以手动异或出TLS函数下面的结果:
(2)也可以在x64dbg中F8运行查看结果:
NtQueryInformationProcess: 取进程信息函数,可以用于反调试(有调试端口,可以检测进程是否被调试)。
ZwQueryInformationThread: 获取线程信息,可以用于反调试。
NtQueryApcThread:apc队列
APC注入可以让一个线程在它正常的执行路径运行之前执行一些其他的代码,每一个线程都有一个附加的APC队列,他们在线程处于可警告的时候才被处理(WaitForSingObjectEx,SleepEx)。
异步过程调用,apc可以看成就是内核里的定时器,为了给自己一个在本函数返回后还能执行的一次机会,有很多操作是需要在函数返回后才能执行.类似于析构函数但不完全是。
反反调试:跟进IsDebuggerPresent
API中,进行如下修改即可。
3.主函数下的线程反调试:
位置:beginthreadex中StartAddress
其中的sub_140008B20为反调试函数。
sub_140008B20函数:
使用快照遍历进程获取进程的md5值与原有的md5值进行判断,当存在指定进程时则退出程序,这里可以判断为反调试。 详细请参考:用快照对进程、模块、线程进行遍历(代码段) - Wings_翅膀 - 博客园
反反调试方法,patch掉exit()函数:
其中sub_140008B20函数中加密算法的识别:
MD5加密的两个特征:
(1)加密后的结果为32位。
(2)在函数中发现有查表的操作。
在sub_140008B20下的sub_140007DD0函数中,发现了MD5的特征值:
在sub_140008B20函数中的&a438078d884693c中,发现了MD5的对比值:
因此可以判断是MD5加密。
4.StartAddress中的sub_140008500函数也为反调试函数:
函数中全是进行反调试的代码。
isDebuggerPresent:
确定调用进程是否正在被用户模式调试器调试。
如果当前进程在调试器的上下文中运行,则返回值非零。
如果当前进程未在调试器的上下文中运行,则返回值为零。
CheckRemoteDebuggerPresent:
确定是否正在调试指定的进程。
解决方案:直接patch掉sub_140008500函数:
去掉所有的反调试,就可以通过动态调试分析程序的过程了。
5.进入sub_140009C20函数:
在函数中看到为全局变量qword_140016178和qword_140016180申请堆空间的操作。C语言学习笔记——堆区空间申请(一)_Lanciberrr的博客-CSDN博客_申请堆空间https://blog.csdn.net/qq_39079631/article/details/102531330
进入sub_1400086C0函数中:
发现函数的参数中有0x100001-0x100003
分别进入带有参数的函数中:
进入SignCheckHandler:
可以看到每个函数对应的偏移地址,分别为sub_1400089E0、sub_140008910、sub_140008A80进入其中一个sub_140008A80:
发现是一个异或的操作,进入byte_1400111A0中:
一共是19456字节,猜测是一个文件。
同理,分别进入sub_1400089E0函数和sub_140008910函数中:
发现都有byte_1400111A0这个数据,并且都进行了变换操作,三个if条件判断不同,通过观察三个if条件,我们可以看出,将byte_1400111A0数据分为三个一组,每组的三个数据分别调用这三个函数进行变换。
回到sub_140009C20函数继续分析:
可以看到创建事件对象的操作,同时我们发现刚才的对数据进行变换操作的函数中:
出现了SetEvent设置事件对象的操作,综上,可以猜测这里是为了防止重复对数据进行变换。
继续分析sub_140009C20函数下的sub_140008850函数:
可以看到这里是获取函数地址的操作,而这几个函数的名称是在上面分析过的TLS函数中解密的。
综上,整个sub_140009C20函数分析完毕,重新进入main函数往下分析。
5.可以看到有创建新线程的操作,进入线程启动函数StatAddress中。
StartAddress函数:
依次对函数进行分析,首先分析sub_140008B20函数:
其中sub_140008300的参数0x100001是我们之前分析过的解密函数的对应序号,而该函数的参数v7在下面sub_140006C10函数中也被当作参数使用,并且其中参数还有之前看到过的qword_140016178,可以判定其与我们之前分析的疑似的解密函数必有关联。
进入sub_140008300函数:
发现是将编号0x100001赋值给了a1+4的位置。
进入sub_140006C10函数:
红框部分为解密函数调用的位置(虚表函数调用),并且a3为传入参数,综上所述,我们已经知道了0x100001-0x100003对应的函数为三个解密函数,需要分别进行分析:
0x100001:
sub_140006C10函数下的a3参数的值为v5的值,即106,联想之前解密函数的内容,可以判断其为解密函数的“密钥”
0x100002:
由于之前解密函数有三个,再次进入sub_140006c10后,按X查看交叉引用,发现其调用处也有三个。
进入check2函数:
根据之前解密函数内分析可知,其有一个是没有参数的,正好对应了起来。
在运行此解密函数之前有个判断条件,读取了 filePath:signature文件流,判断其内容为加密后和MD5值做比较。
先提取出MD5值,然后进行MD5解密:
MD5值为:FCAEEB6E34B4303E99B91206BD325F2B
在线解密:
解密的结果为:Overwatch
可以通过以下方式写入文件流:
type Test.txt >> WinRev.exe:signature
把"Overwatch"字符串写入后就能通过检测。
0x100003:
找到1003对应的位置:
0x100003前面有一个验证输入的函数sub_140009FF0函数,打开该函数:
就是一个简单的加密算法,写python脚本解密:
a = [0 for i in range(18)]
v5 = [1,5,4,2,3,0]
b =[0x6A,0x5A,0x65,0x6B,0x71,0x41,0x72,0x68,0x55,0x7C,0x39,0x67,0x3E,0x30,0x4F,0x7D,0x7C,0x64,0x45]
for k in range(0x12) :
a[6 * (int(k / 6)) + v5[k % 6] ] = b[k]
for j in range(18) :
key = a[j]^j
print(chr(key),end='')
#Akira_aut0_ch3ss_!
得到密钥Akira_aut0_ch3ss_!,这就是0x100003的密钥。
综上,三个加密函数的密钥分别为:
0x100001:106
0x100002:Overwatch
0x100003:Akira_aut0_ch3ss_!
继续在线程启动函数StartAddress函数中向下分析:
发现有向apc队列添加函数的操作,跟进添加的函数地址sub_140009850:
可以看到其有WaitFormultipleObjects的操作
WaitFormultipleObjects函数:
等待一个或所有指定对象处于信号状态或超时间隔结束。
如果函数成功,则返回值指示导致函数返回的事件。
等待3个解密函数全部执行完成,之后创建了共享内存,并且申请了一段空间,将解密后的数据复制过去,并且对齐验证了PE标记。
根据第61行CreateEventW函数的最后一个参数可以判断该加密后的文件为DLL文件,所以猜测sub_140007D80中有LoadLibrary的操作。
在sub_140007D80下的sub_1400079A0下的sub_140007580函数中找到:
由此基本可以判定sub_140007D80调用DLL中的函数执行功能的操作,而被加密的数据正是DLL。
我们根据上述操作,需要编写代码解密出dll文件,根据大佬的解密代码:
#include <iostream>
#include <Windows.h>
#include <io.h>
#define DLLSIZE 19456
#include <stdio.h>
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;
}
运行之后即可导出Test.DLL文件,将Test.DLL文件用IDA打开,并找到关键函数sub_180002880:
通过findcrypt插件:
不难看出是AES加密。
其中密钥为Ak1i3aS3cre7K3y。
密文在原来的exe程序的共享内存中,即sub_140009850函数,并通过这个函数进行传递密文,密文为:
先将这一串数据进行base64加密:
import base64
a = b"\x94\xBF\x7A\x0C\xA4\x35\x50\xD1\xC2\x15\xEC\xEF\x9D\x9A\xAA\x56"
b = base64.b64encode(a).decode()
print(b)
'lL96DKQ1UNHCFezvnZqqVg=='
在线AES解密:
最终得到flag:
flag{Ak1rAWin!}