cybricsctf-paired

Posted by marginal on 2021-07-25
Estimated Reading Time 7 Minutes
Words 1.4k In Total
Viewed Times

paired

解包,先得到4个文件,运行app1.exe

app1

image-20210725145959566

这几个函数不用再logic.dll里面分析,动调就可以知道是设置一个有id的储存

WinMain

1
wndclass.lpfnWndProc = (WNDPROC)WndProc;

根据窗口类lpfnWndProc的成员根据这里找到窗口过程

STAGE 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GetWindowTextA(edit_hWnd, String, 128);
v15 = (unsigned __int8 *)&v19;
do
{
v16 = v15[String - (CHAR *)&v19];
v17 = *v15 - v16;
if ( v17 )
break;
++v15;
}
while ( v16 );
if ( !v17 )
{
SetWindowTextW(hWnd[0], L"STAGE 2");
pass_to_stage2 = 1;
}
return 0i64;

这里获取edit控件字符串并检测,进入第二关

STAGE 2

case WM_KEYDOWN(0x100):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
else if ( pass_to_stage2 == 1 )
{
j = i;
if ( wParam != aEnable[i] )
{
i = 0;
return 0i64;
}
++i;
if ( !aEnable[j + 1] )
{
pass_to_stage3 = 1;
SetWindowTextW(_hwnd, L"STAGE 3...");
add_to_storage(1i64, hWnd);
return 0i64;
}
}

按键要等于’ENABLE’

进入第三关

STAGE 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
case 0x400u:                                // WM_USER
if ( !get_flag )
{
GetWindowTextA(edit_hWnd, String, 128);
add_to_storage(2i64, String);
get_flag = 1;
}
v7 = VirtualAlloc(0i64, 0x200ui64, 0x1000u, 0x40u);
get_data_from_storage((unsigned int)(_wParam + 32), v7, 128i64);
v19 = 0i64;
v20 = 0i64;
v21 = 0i64;
v22 = 0i64;
v23 = 0i64;
v24 = 0i64;
v25 = 0i64;
v26 = 0i64;
get_data_from_storage(2i64, &v19, 128i64);
success += ((__int64 (__fastcall *)(_QWORD, __int128 *))v7)((unsigned int)_wParam, &v19);
if ( success == 32 )
{
MessageBoxA(0i64, "Well done!", "WINNER!", 0);
return 0i64;
}

需要把消息WM_USER放入队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ( pass_to_stage3 )
{
if ( wParam == 'F' ) // fake
{
v11 = 0i64;
v12 = 32i64;
while ( 1 )
{
PostMessageA(_hwnd, 0x400u, v11++, 0i64);
if ( !--v12 )
break;
_hwnd = hWnd[0];
}
}
}

这里可以加入WM_USER消息,动调查看v7函数之后,得到输入为this_is_just_some_random_string!,是fake flag,很明显不可能只分析一个文件

app2

直接进入sub_7FF64E911210()函数分析

1
2
3
4
hWnd = HWND_MESSAGE|0x2;
LODWORD(l) = get_data_from_storage(1i64, &hWnd);
v1 = hWnd;
if ( hWnd != HWND_MESSAGE|0x2 )

这里获取id==7的储存赋值给hwnd,再看前面通过第二关时

1
add_to_storage(1i64, hWnd);

只有通过第二关,这里条件才为真, 接下来关注消息的发送, 这里所用的消息发送都将发送wparam==32的消息,执行id==64的代码,

1
2
PostMessageA(v1, 0x400u, 0x20ui64, 0i64);
for ( i = -1i64; i == -1; get_data_from_storage(3i64, &i) );

先动调app1,进入第二关后,把断点下在第三关,然后动调app2, 运行过发送消息

1
((void (__fastcall *)(__int64, void **, __int64))logic_dll_add_to_storage)(3i64, &retaddr, 8i64);

v7函数, 把基地址存为id==3的储存中, 储存完成后 app2的循环停止

1
2
3
4
5
6
get_data_from_storage(2i64, v61);
v3 = -1i64;
do
++v3;
while ( v61[v3] );
if ( (_DWORD)v3 == 32 )

这里是长度检验, id==2是app1的输入框内容, 先判断不成立的情况, 这里的代码由随机数组成, srand(time(0)), 明显有很大问题

判断成立的情况

1
2
3
v10 = _mm_load_si128((const __m128i *)&byte_7FF64E926630);
for ( j = 0i64; j < 32; j += 16i64 )
*(__m128i *)&v61[j] = _mm_xor_si128(v10, _mm_loadu_si128((const __m128i *)&v61[j]));

一段异或加密, 到这里

1
2
3
4
5
6
7
get_data_from_storage(2i64, v62);
do
{
*(__m128i *)&v62[4 * v16] = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&v62[4 * v16]), v17);
v16 += 4i64;
}
while ( v16 < 8 );

又是一段异或加密

1
2
get_data_from_storage(5i64, &i);
while ( (_DWORD)i != 'cybr' );

这里有循环条件,这是其实是rbyc, id==5的由来(app1中):

1
2
3
4
5
6
__int64 sub_1AD49780000()
{
GetWindowTextA((HWND)0xBD04BA, byte_1AD49760000, 128);
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(5i64, 0x1AD49760000i64, 4i64);
return 0i64;
}

输入放入id==5, 检查前面4个字节为’rbyc’, 第二次异或加密

1
2
3
4
5
6
do
{
*(__m128i *)&v62[4 * v16] = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&v62[4 * v16]), v17);
v16 += 4i64;
}
while ( v16 < 8 );

然后来到swith结构, 像极了虚拟机

1
switch ( *((_BYTE *)&unk_7FF64E910000 + index + 100864) ) // byte_7FF64E928A00

然后慢慢单步走, 慢慢会发现先一直都是1, 2, 然后走5, 6, 8, 3, 8, a, 接下来就又是1, 2…, 最后, 2, 3, 4

(地址只是一个临时的值,主要怎么计算得到)

case 1:

app1中的执行的代码

1
2
3
unk_19BE3430010 = 0i64; // unk_19BE3430010 == *(id==6的数据 + byte_7FF64E928A00[index + 1])
// 0 == *(dword *)(byte_7FF64E928A00 + index + 2)
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(10i64, 0x7FF786F70000i64, 8i64);

case 2:

app1中的执行的代码

1
2
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(7i64, 0x19BE3430070i64, 8i64);
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(10i64, 0x7FF786F70000i64, 8i64);

id == 7的数据换为(id==6的数据 + byte_7FF64E928A00[index + 1])

case 3:

1
v25 = *(unsigned int *)((char *)&unk_7FF64E910000 + index + 100865);// *(dword *)(byte_7FF64E928A00 + index + 1)
1
2
v26 = &i;
i = i == v25; //出现了比较
1
LODWORD(l) = add_to_storage(7i64, v26); //比较结果在id == 7数据中

接下来会执行case 8, 将结果(1)加在 *(id==6的数据 + 8)中

这里就和app1中的加到32有点像了

case 4:

这里是最后的指令, 先前的两步2, 3, 分别是把数据给到id == 7(就是每次比较的结果相加的数据)和 将id == 7与32做比较(和app1重合了), 若为真, 则id == 7 变为1

1
if ( l != 1 )

则app1中的执行的代码

1
return 32i64;

这样就正确了

case 5:

将id==7的数据换为*(dword *)(byte_7FF64E928A00 + index + 1)

case 6:

app1中的执行的代码

1
2
unk_27DA7C30008 = 0x81818181i64; // *(id==6的数据 + byte_7FF64E928A00[index + 1]) = (id == 2的数据(input))
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(10i64, 0x7FF786F70000i64, 8i64);

case 8:

app1中的执行的代码:

1
2
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(7i64, 0x27DA7C30008i64, 8i64);
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(10i64, 0x7FF786F70000i64, 8i64);

(id == 7的数据) = *(id==6的数据 + byte_7FF64E928A00[index + 1])

app2:

1
get_data_from_storage(7i64, &i);
1
2
3
for ( ii = -1i64; ii == -1; get_data_from_storage(7i64, &ii) )
;
i += ii;
1
2
remove_data(7i64);
LODWORD(l) = add_to_storage(7i64, &i);

case a:

1
2
unk_27DA7C30010 = 0i64;
((void (__fastcall *)(__int64, __int64, __int64))logic_dll_add_to_storage)(10i64, 0x7FF786F70000i64, 8i64);

将 id==7 赋值给*(id == 6 + 10)

综上可以发现

这里的1, 2指令中的其他一堆赋值貌似没什么用,主要就是每次取字符串的4个字节来加法,加数为指令3的参数(后面一个值), 结果存放在偏移量为0x10的地址中, 然后比较一下是否为32

解密脚本

减法解密直接python敲出来就行了

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

using namespace std;

int main()
{
unsigned char byte_7FF64E926630[16] = {
0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81
};
unsigned char a[] = {0x90,0x9a,0x9a, 0x90,
0x9a, 0x80,
0x8b,0x99,0x80,0x8b,
0xb8,0x90,0x96,0x87,
0xa7,0x91, 0xc0, 0x80,
0x8c, 0xd3, 0x9c, 0x8d,
0xa7, 0x8f, 0xb3,0x84,
0xc9, 0x81, 0xd2, 0xd2,
0xd9, 0x9f};
char key[] = {0x72, 0x62, 0x79, 0x63};
for (int i = 0; i < 32; i++)
{
a[i] ^= key[i % 4];
}
for (int i = 0; i < 32; i++)
{
a[i] ^= 0x81;
}
for (int i = 0; i < 32; i++)
{
printf("%c", a[i]);
}
system("pause");
return 0;
}

如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !