x2658y's Blog

杂七杂八的记事本

无壳, 拖进IDA, F5查看伪代码

可以看到两个关键函数

image-20220126164811383

两个函数结构区别不大, 里面调用了很多WidnwosCryptoAPI

这里以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 hashlib

for 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

所以第一段密码就是123321

第二段密码, 同样长度为6位, 但是第二段密码并没有限制是纯数字.

第二次验证的算法改成了MD5, 进行哈希的内容就是第二段密码+第一段密码+@DBApp组成的18字节字符.

方法一: 真·暴力破解法

所有ASCII可打印字符从32~126共95个, 第二段密码长6位, 那么可能性就有\(95^6\)种, 大约7350亿种可能, 写脚本穷举好像不太可能...但是, 显卡进行MD5哈希的速度可是相当快的, CPU则要慢得多多多多多...

放张hashcatbenchmark感受一下, 这还只是个3050Ti, 换做高端一点的那更是快得多.image-20220126193440960

换算出来差不多就是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才知道.

关键点在这个函数image-20220126203837733

跟进去找到sub_4014D0()这个函数, WindowsAPI有点多, 结合微软的文档分析其用意

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; // [esp+50h] [ebp-1Ch]
DWORD NumberOfBytesWritten; // [esp+58h] [ebp-14h] BYREF
DWORD nNumberOfBytesToWrite; // [esp+5Ch] [ebp-10h]
HGLOBAL hResData; // [esp+60h] [ebp-Ch]
HRSRC hResInfo; // [esp+64h] [ebp-8h]
HANDLE hFile; // [esp+68h] [ebp-4h]

hFile = 0;
hResData = 0;
nNumberOfBytesToWrite = 0;
NumberOfBytesWritten = 0;
hResInfo = FindResourceA(0, (LPCSTR)0x65, "AAA");
//从载入为当前进程的PE文件,找到一个名为"e"(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", 0x10000000u, 0, 0, 2u, 0x80u, 0);
//创建名为"dbapp.rtf"的文件,储存解密后的rtf格式flag文件
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; // eax
unsigned int i; // [esp+4Ch] [ebp-Ch]
unsigned int v5; // [esp+54h] [ebp-4h]

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 };
//RTF文件头前6字节
for (DWORD i = 0; i < 6; i++)
for (BYTE c = 32; c < 127; c++) //穷举所有ASCII可打印字符
if ((lpRes[i] ^ c) == lpHeaderByte[i]) //验证字符
putchar(c);
}
//~!3a@0

最终, flagflag{N0_M0re_Free_Bugs}

无壳, 拖进IDA, F5查看伪代码, 主函数里面主要就func()函数, 跟到func()

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
int func()
{
int result; // eax
int v1[4]; // [esp+14h] [ebp-44h]
unsigned __int8 v2; // [esp+24h] [ebp-34h] BYREF
unsigned __int8 v3; // [esp+25h] [ebp-33h]
unsigned __int8 v4; // [esp+26h] [ebp-32h]
unsigned __int8 v5; // [esp+27h] [ebp-31h]
unsigned __int8 v6; // [esp+28h] [ebp-30h]
int v7; // [esp+29h] [ebp-2Fh]
int v8; // [esp+2Dh] [ebp-2Bh]
int v9; // [esp+31h] [ebp-27h]
int v10; // [esp+35h] [ebp-23h]
unsigned __int8 v11; // [esp+39h] [ebp-1Fh]
char v12[29]; // [esp+3Bh] [ebp-1Dh] BYREF

strcpy(v12, "Qsw3sj_lz4_Ujw@l");
printf("Please input:");
scanf("%s", &v2);
result = v2;
if ( v2 == 'A' )
{
result = v3;
if ( v3 == 'C' )
{
result = v4;
if ( v4 == 'T' )
{
result = v5;
if ( v5 == 'F' )
{
result = v6;
if ( v6 == '{' )
{
result = v11;
if ( v11 == '}' )
{
v1[0] = v7;
v1[1] = v8;
v1[2] = v9;
v1[3] = v10;
*(_DWORD *)&v12[17] = 0;
while ( *(int *)&v12[17] <= 15 )
{
if ( *((char *)v1 + *(_DWORD *)&v12[17]) > 64 && *((char *)v1 + *(_DWORD *)&v12[17]) <= 90 )
*((_BYTE *)v1 + *(_DWORD *)&v12[17]) = (*((char *)v1 + *(_DWORD *)&v12[17]) - 51) % 26 + 65;
if ( *((char *)v1 + *(_DWORD *)&v12[17]) > 96 && *((char *)v1 + *(_DWORD *)&v12[17]) <= 122 )
*((_BYTE *)v1 + *(_DWORD *)&v12[17]) = (*((char *)v1 + *(_DWORD *)&v12[17]) - 79) % 26 + 97;
++*(_DWORD *)&v12[17];
}
*(_DWORD *)&v12[17] = 0;
while ( *(int *)&v12[17] <= 15 )
{
result = (unsigned __int8)v12[*(_DWORD *)&v12[17]];
if ( *((_BYTE *)v1 + *(_DWORD *)&v12[17]) != (_BYTE)result )
return result;
++*(_DWORD *)&v12[17];
}
result = printf("You are correct!");
}
}
}
}
}
}
return result;
}

解法很常规, 分析算法, 写出解密程序

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

using namespace std;

int main()
{

char ciphertext[32] = "Qsw3sj_lz4_Ujw@l";
char plaintext[32] = { 0 };
for (int i = 0; i < 16; i++)
{
for (char c = 32; c < 127; c++) //逐位穷举所有ASCII可打印字符
{
char temp = c;
if (c > 64 && c <= 90)
temp = (c - 51) % 26 + 65;
else if (c > 96 && c <= 122)
temp = (c - 79) % 26 + 97;
if (temp == ciphertext[i])
plaintext[i] = c;
}
}
printf("ACTF{%s}", plaintext);
}
//ACTF{Cae3ar_th4_Gre@t}

给了两个文件, 一个加密后的文件, 一个公钥, 开始我以为是用公钥去解密私钥加密的文件, 但是很多加密库都没有提供这样的功能, 比如OpenSSL, 只有用公钥验签的功能. 看了别人的WP才知道这个题是分解公钥的来实现算出私钥再进行解密.

OpenSSL可以查看公钥的一些信息

image-20220125132123596

我们可以得知 \[ \begin{aligned} &e=65537\\ &n=C0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD \end{aligned} \]

而且公钥比常见的要短很多, 只有256Bit, 这为攻击提供了方便

一些分解的工具:

RsaCtfTool: Github //这个工具不支持Windows

yafu: Sourceforge

Msieve: Sourceforge

yafu方式, 运行后进入yafu命令行, 输入

1
factor(0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD)

即可开始分解

image-20220125140140434

RsaCtfTool方式, 安装好后输入

1
./RsaCtfTool.py -n 0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD -e 65537 --private --dumpkey

工具就会综合判断自动采用合适的方式得出私钥(也可以直接用这个工具解密文件, 详细用法见Github)

image-202201251408520

RsaCtfTool已经帮我们生成好了私钥, 如果是用yafu的话还需要计算一下d

我们已经得出 \[ \begin{aligned} &p=285960468890451637935629440372639283459 \\ &q=304008741604601924494328155975272418463 \end{aligned} \]

有了\(p\)\(q\)就可以轻松求出欧拉函数的值

\[\phi(n)=\phi(p)\times\phi(q)=(p-1)\times(q-1)\]

根据公式 \(ed\equiv1(\mod\phi(n))\), 则de\(\phi(n)\)模逆元, Python可以通过gmpy2这个库来求得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
#此题的密文并非PKCS1_OAEP填充方式
import gmpy2

n = 0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD
e = 65537
p = 285960468890451637935629440372639283459
q = 304008741604601924494328155975272418463
phi_n = (p-1)*(q-1) #求欧拉函数值
d = int(gmpy2.invert(e, phi_n)) #求模逆元d

private_key = RSA.construct((n,e,d,p,q)) #用参数构造私钥
cipher = PKCS1_v1_5.new(private_key) #用私钥生成一个Cipher
ciphertext = open("flag.enc","rb").read()
plaintext = cipher.decrypt(ciphertext, None).decode()
print(plaintext)
#flag{decrypt_256}
话说BUU为什么把这个题放到Reverse板块里面呢🤨
0%