首页
社区
课程
招聘
[原创]cybricsctf-paired
2021-7-30 21:09 9492

[原创]cybricsctf-paired

2021-7-30 21:09
9492

paired

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

app1

图片描述
这几个函数不用再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;
}

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回