SUCTF2019 Akira Homework分析文档
2021-03-11 18:58:39 Author: mp.weixin.qq.com(查看原文) 阅读量:159 收藏

本文为看雪论坛精华文章

看雪论坛作者ID:flag0

程序流程

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

逆向分析

查看基础信息

运行一下,查看其输出结果。
首先使用Exeinfo查壳,发现没有壳,是X64的程序。
用X64dbg打开发现有地址随机化,用CFF关闭地址随机化OptionHandle->DllCharacteristics,便于后续继续分析。

反调试

TLS反调试

在main函数处下断点,运行后发现有弹框并退出进程,则判断有TLS反调试。
 
使用ida查看TLS函数处,发现有IsDebuggerPresent反调试和四个加密的数组。

手动Path掉反调试,继续分析,等待数组的内容解密后可以看到解密后的字符串。
NtQueryInformationProcess取进程信息函数,可以用于反调试(有调试端口,可以检测进程是否被调试)。
ZwQueryInformationThread获取线程信息,可以用于反调试。
NtQueryApcThread apc队列
APC注入可以让一个线程在它正常的执行路径运行之前执行一些其他的代码,每一个线程都有一个附加的APC队列,他们在线程处于可警告的时候才被处理(WaitForSingObjectEx,SleepEx)。
异步过程调用,apc可以看成就是内核里的定时器,为了给自己一个在本函数返回后还能执行的一次机会,有很多操作是需要在函数返回后才能执行。类似于析构函数但不完全是。
 
反反调试:跟进IsDebuggerPresentAPI中,进行如下修改即可。

线程反调试1

位置:mian函数->beginthreadex中的线程启动函数(140009180)-> sub_140008B20
 
使用快照遍历进程获取进程的md5值与原有的md5值进行判断,当存在指定进程时则退出程序,这里可以判断为反调试。
 
反反调试方案:patch掉exit函数即可。

md5加密算法的识别

md5加密的两个特征:
  1. 加密后的字符是32位。

  2. 在md5函数中有查表相关的操作。


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

线程反调试2

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

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

解密函数分析

顺利进入到main函数,开始下一步的分析,进入sub_140009C20函数中。
 
可以看到为全局变量qword_140016178,qword_140016180 申请堆空间的操作。
 
跟进其中的sub_1400086C0函数内,发现了疑似函数参数内有编号0x10001~0x10003
依次跟进这些函数中。 
这里跟进函数中的虚表的第二项可以看到对应的虚函数的地址。
跳到对应的函数地址sub_14000a8a0去查看,发现有对原有数据进行异或的操作。 
跟进去byte_1400111A0可以看到,其为一大段数据,长度为19456,这里可能藏有一个文件。
继续分析剩下的两个sub_140008370、sub_1400083F0发现其结构与sub_14000a8a0中一致,找到虚表中的虚函数地址依次跟进去。
现其都对byte_1400111A0数组内的数据进行了变换的操作,同时由于if判断条件的不同,各对其中的一部分数据做变换,猜测其可能为解密的函数,并与之前传入的ID可能有所关联。
退出去sub_1400086C0函数,回到sub_140009C20中,接着往下分析。
 
可以看到创建事件对象的操作,结合上述解密函数里有SetEvent设置事件对象的操作,猜测这里是为了防止重复对数据进行变换。
接着往下跟进sub_140008850,可以看到这里获取函数地址的操作,而这几个函数的名称是在上面分析过的TLS函数中解密的。
此时第一个函数sub_140009c20分析完毕,再次回到main函数中。 
可以看到其有创建线程的操作,跟进其线程启动函数StatAddress中去。 
跟进去后,依次对其中的函数来进行分析。
首先进入第一个函数sub_140008B20中去,进行分析。
 
可以看到其中的sub_140008300的参数为我们之前分析过的解密函数的对应序号,而其参数v7在下面sub_140006C10函数中被当作参数使用,并且其中参数还有之前看到过的qword_140016178,可以判定其与我们之前分析的疑似的解密函数必有关联。
 
跟进去sub_140008300函数中,发现其是将编号0x100001赋值给了a1+4的位置。 
跟进去sub_140006c10,发现其中为解密函数调用的位置(虚表函数调用),并且a3为传入参数。 

解密函数0x100001

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

解密函数0x100002

由于之前解密函数有三个,再次进入sub_140006c10后,按X查看交叉引用,发现其调用处也有三个。 
跟进去后,发现sub_140008300和sub_140006c10其为成对出现的。
 
分别取得其传入参数a3(密钥)的值。
 
其中0x100002 => 0,根据之前解密函数内分析可知,其有一个是没有参数的,正好对应了起来。
 
在运行此解密函数之前有个判断条件,读取了 filePath:signature 文件流,判断其内容为加密后和MD5值做比较。 

 
其中字符串的xor解密可以编写ida Python脚本来进行解密。
BEGIN = 0x140015ee0len = 0x16for i in range(len): tmp = Byte(BEGIN + i) tmp = tmp ^ 0x0c1 PatchByte(BEGIN+i, tmp)
对MD5值进行解密。
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
 
可以通过以下方式写入文件流:
type Test.txt >> WinRev.exe:signature
把"Overwatch"字符串写入后就能通过检测。

解密函数0x100003

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

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

编写解密程序

根据其算法编写对应的解密代码。
#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;}
输出结果为Akira_aut0_ch3ss_!,此结果就是我们的0x10003对应的密钥了。
 
此时整理可得:
  • sub_1400089E0 => 0x100001 key:106

  • sub_140008A80=> 0x100002 key:0

  • sub_140008910 => 0x100003 key:Akira_aut0_ch3ss_!

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

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

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

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

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

 
根据以上信息编写解密代码,将DLL文件解密出来。
#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。

AES加密函数的识别

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

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

在Ida中按G键跳转过去,查看交叉引用,可以看到在函数sub_180001000中有对其的使用。 
在函数180001000头部查看交叉引用,依次回溯可以最终确定到是在sub_180002800处进行的调用,根据其参数则可以判定sub_180002800为AES加密函数。
 
回溯过程如下:
1800041e0=>sub_180001000=>sub_1800013D0=>sub_180002800

解密flag

根据分析结果对其在共享内存中传递的密文使用密钥Ak1i3aS3cre7K3y进行解密。
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
>>> 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值。 
最后的flag值为flag{Ak1rAWin!}。

总结


在解题过程中学习到了IDA Python脚本的编写和加密函数的识别。有兴趣的小伙伴可以下载题目来进行复现。
https://xz.aliyun.com/t/6042#toc-9
https://blog.csdn.net/qq_41252520/article/details/100738585
https://bbs.pediy.com/thread-255081.htm

- End -

看雪ID:flag0

https://bbs.pediy.com/user-home-873556.htm

  *本文由看雪论坛 flag0 原创,转载请注明来自看雪社区。

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458382440&idx=1&sn=983890338fc792c1ec23de5115323e67&chksm=b180c0e286f749f4bb26b7041ed0a31d39cfb1712881646843380fe50ffb152dcf7835f45585#rd
如有侵权请联系:admin#unsafe.sh