无壳, 拖进IDA
, F5查看伪代码
可以看到两个关键函数
image-20220126164811383
两个函数结构区别不大,
里面调用了很多Widnwos
的CryptoAPI
这里以sub_40100A()
为例,
通过它间接调用sub_401230()
查询微软文档,
CryptCreateHash()
函数的第二个参数指明算法类型
image-20220126165402916
查表可以得知这个函数是进行SHA-1
哈希
微软文档: MSDN
image-20220126170805262
同理可以得知sub_401019()
是进行MD5
哈希的函数
通过这一段可以得知第一段密码长度是6位, 且为6位数字, 可以直接穷举
1 2 3 4 5 6 7 8 if ( strlen (Destination) != 6 ) { printf ("Must be 6 characters!\n" ); ExitProcess(0 ); } v7 = atoi(Destination); if ( v7 < 100000 ) ExitProcess(0 );
代码如下
1 2 3 4 5 6 7 8 9 import hashlibfor i in range (100001 ,1000000 ): hasher = hashlib.sha1() hasher.update((str (i)+"@DBApp" ).encode("utf-8" )) if hasher.hexdigest().upper() == "6E32D0943418C2C33385BC35A1470250DD8923A9" : print (i) break
所以第一段密码就是123321
第二段密码, 同样长度为6位, 但是第二段密码并没有限制是纯数字.
第二次验证的算法改成了MD5
,
进行哈希的内容就是第二段密码+第一段密码+@DBApp
组成的18字节字符.
方法一: 真·暴力破解法
所有ASCII
可打印字符从32~126
共95个,
第二段密码长6位, 那么可能性就有\(95^6\) 种, 大约7350亿种可能,
写脚本穷举好像不太可能...但是,
显卡进行MD5
哈希的速度可是相当快的,
CPU则要慢得多多多多多...
放张hashcat
的benchmark
感受一下,
这还只是个3050Ti, 换做高端一点的那更是快得多.
换算出来差不多就是164亿次哈希每秒,
照这个速度7350亿种可能也不是很多.
确定可行性之后, 就可以用hashcat
来进行穷举
hashcat: Github
N卡 安装好CUDA
套件之后,
在hashcat
目录下打开命令行执行
1 hashcat -a 3 27019e688a4e62a649fd99cadaafdb4e -m 0 ?a?a?a?a?a?a123321@DBApp
-a 3 指定攻击方式, 掩码攻击
-m 0 指定哈希类型, MD5
?a?a?a?a?a?a123321@DBApp 是掩码, 前面6位代表所有可打印ASCII字符
实际的哈希速度要慢一些, 但依然是极快的, 不到半分钟就完成了工作
image-20220126195352794
得出第二段密码是~!3a@0
方法二: 常规方法
老实说我是上来就考虑用hashcat
尝试穷举的, 没想到还有方法,
看了别人的WP才知道.
关键点在这个函数
跟进去找到sub_4014D0()
这个函数,
Windows
的API
有点多,
结合微软的文档分析其用意
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 char __cdecl sub_4014D0 (LPCSTR lpString) { LPCVOID lpBuffer; DWORD NumberOfBytesWritten; DWORD nNumberOfBytesToWrite; HGLOBAL hResData; HRSRC hResInfo; HANDLE hFile; hFile = 0 ; hResData = 0 ; nNumberOfBytesToWrite = 0 ; NumberOfBytesWritten = 0 ; hResInfo = FindResourceA(0 , (LPCSTR)0x65 , "AAA" ); if ( !hResInfo ) return 0 ; nNumberOfBytesToWrite = SizeofResource(0 , hResInfo); hResData = LoadResource(0 , hResInfo); if ( !hResData ) return 0 ; lpBuffer = LockResource(hResData); sub_401005(lpString, (int )lpBuffer, nNumberOfBytesToWrite); hFile = CreateFileA("dbapp.rtf" , 0x10000000 u, 0 , 0 , 2u , 0x80 u, 0 ); if ( hFile == (HANDLE)-1 ) return 0 ; if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0 ) ) return 0 ; CloseHandle(hFile); return 1 ; }
sub_401005()
调用了sub_401420()
,
接着分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned int __cdecl sub_401420 (LPCSTR lpString, int a2, int a3) { unsigned int result; unsigned int i; unsigned int v5; v5 = lstrlenA(lpString); for ( i = 0 ; ; ++i ) { result = i; if ( i >= a3 ) break ; *(_BYTE *)(i + a2) ^= lpString[i % v5]; } return result; }
这个函数将资源文件每个字节与第二段密码+第一段密码+@DBApp
组成密钥进行循环异或从而解密出flag 文件.
突破点就在flag 文件的前6个字节上, 要知道,
RTF
格式不像txt
这种文本格式, 它是有文件头的,
而文件头往往是固定的!
既然flag 是个RTF
格式的文件,
那么它一定符合RTF
的文件头规范. 我们可以通过这已知的6个字节,
倒推出密钥前6字节, 即第二段密码.
找个RTF
文件看看前面6字节
image-20220126211735455
前6字节是0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31
写出解密代码
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 #include <cstdio> #include <Windows.h> using namespace std;int main () { HMODULE hModule = LoadLibraryA ("crack_rtf.exe" ); if (!hModule) return -1 ; HRSRC hResInfo = FindResourceA (hModule, (LPCSTR)0x65 , "AAA" ); if (!hResInfo) return -1 ; HGLOBAL hResData = LoadResource (hModule, hResInfo); if (!hResData) return -1 ; BYTE* lpRes = (BYTE*)LockResource (hResData); if (!lpRes) return -1 ; BYTE lpHeaderByte[6 ] = { 0x7B , 0x5C , 0x72 , 0x74 , 0x66 , 0x31 }; for (DWORD i = 0 ; i < 6 ; i++) for (BYTE c = 32 ; c < 127 ; c++) if ((lpRes[i] ^ c) == lpHeaderByte[i]) putchar (c); }
最终, flag 为flag{N0_M0re_Free_Bugs}