首页
社区
课程
招聘
[原创] KCTF 2023 第六题 wp - 98k
2023-9-14 00:35 8177

[原创] KCTF 2023 第六题 wp - 98k

2023-9-14 00:35
8177

main 之前有两个 TlsCallback 用于反调试,直接 patch 掉;

进入 main ,新开一个线程,执行函数 sub_140001630

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl main(int argc, const char **argv, const char **envp)
{
  _QWORD *v3; // rax
  _Thrd_t ThrdAddr; // [rsp+30h] [rbp-28h] BYREF
  _Thrd_t v6; // [rsp+40h] [rbp-18h] BYREF
 
  v3 = operator new(8ui64);
  *v3 = sub_140001630;
  ThrdAddr._Hnd = (void *)beginthreadex(0i64, 0, (_beginthreadex_proc_type)StartAddress, v3, 0, &ThrdAddr._Id);
  if ( !ThrdAddr._Hnd )
  {
    ThrdAddr._Id = 0;
    std::_Throw_Cpp_error(6);
  }
  if ( !ThrdAddr._Id )
    std::_Throw_Cpp_error(1);
  if ( ThrdAddr._Id == Thrd_id() )
    std::_Throw_Cpp_error(5);
  v6 = ThrdAddr;
  if ( Thrd_join(&v6, 0i64) )
    std::_Throw_Cpp_error(2);
  return 0;
}

sub_140001630 就是主逻辑,字符串都加密了,异或解密之后就能看出来逻辑:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
__int64 sub_140001630()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
 
  std::string::ctor(&input_string, empty);
  std::string::ctor(&Format, s);                // 'Please enter your key:'
  std::string::ctor(&str_kernel32_dll, aJzlOmJnms);// kernel32.dll
  std::string::ctor(&str_RtlFillMemory, aM);
  std::string::ctor(&str_kctf, aAJv);           // kctf
  std::string::append(&str_RtlFillMemory, aD_0);// RtlFillMemory
  std::string::ctor(&Block, "7PK:");
  v0 = (char *)&Format;
  if ( Format.cap >= 0x10 )
    v0 = Format.data.lstr;
  vigenere_decrypt(v0, Format.size, "&x+^x", 5);
  v1 = (char *)&Format;
  if ( Format.cap >= 0x10 )
    v1 = Format.data.lstr;
  printf(v1);
  v2 = input_string.size;
  // set input length to 500
  if ( input_string.size < 500 )
  {
    // ...
  }
  else
  {
    // ...
  }
  v7 = (char *)&input_string;
  if ( input_string.cap >= 0x10 )
    v7 = input_string.data.lstr;
  scanf("%s", v7);
  v8 = (char *)&input_string;
  if ( input_string.cap >= 0x10 )
    v8 = input_string.data.lstr;
  v9 = (char *)&str1;
  std::string::append(&str1, v8);
  v10 = (char *)&str2;
  if ( str2.cap >= 0x10 )
    v10 = str2.data.lstr;
  v11 = str2.size;
  v12 = str1.size;
  str1 += str2; // ...
  v15 = (char *)&str_kctf;
  if ( str_kctf.cap >= 0x10 )
    v15 = str_kctf.data.lstr;
  vigenere_decrypt(v15, str_kctf.size, "*s>0?", 5);
  str1 = str_kctf + str1; // ...
  ProcessHandle = GetCurrentProcess();
  RegionSize = 5500i64;
  NtAllocateVirtualMemory(
    ProcessHandle,
    (PVOID *)&alloced,
    0i64,
    &RegionSize,
    MEM_RESERVE|MEM_COMMIT,
    PAGE_EXECUTE_READWRITE);
  if ( str1.cap >= 0x10 )
    v9 = str1.data.lstr;
  NtWriteVirtualMemory(ProcessHandle, alloced, v9, str1.size, NumberOfBytesWritten);
  v20 = (char *)&str_kernel32_dll;
  if ( str_kernel32_dll.cap >= 0x10 )
    v20 = str_kernel32_dll.data.lstr;
  vigenere_decrypt(v20, str_kernel32_dll.size, "!?>d*", 5);
  v22 = (char *)&str_RtlFillMemory;
  if ( str_RtlFillMemory.cap >= 0x10 )
    v22 = str_RtlFillMemory.data.lstr;
  vigenere_decrypt(v22, str_RtlFillMemory.size, "?x)da", v21);
  v23 = (char *)&str_RtlFillMemory;
  if ( str_RtlFillMemory.cap >= 0x10 )
    v23 = str_RtlFillMemory.data.lstr;
  v24 = (char *)&str_kernel32_dll;
  if ( str_kernel32_dll.cap >= 0x10 )
    v24 = str_kernel32_dll.data.lstr;
  kernel32_dll = GetModuleHandleA(v24);
  RtlFillMemory = (void (__fastcall *)(void *, size_t, int))GetProcAddress(kernel32_dll, v23);
  if ( !RtlFillMemory || write_memory(ProcessHandle, alloced + 500) )
  {
    // string dtor
    return 1i64;
  }
  if ( NtCreateThreadEx(
         &hThread,
         0x1FFFFFu,
         0i64,
         ProcessHandle,
         (LPTHREAD_START_ROUTINE)ExitThread,
         0i64,
         1u,
         0i64,
         0i64,
         0i64,
         0i64) )
  {
    // string dtor
    return 1;
  }
  NtQueueApcThread(hThread, (PIO_APC_ROUTINE)(alloced + 500), 0i64, 0i64, 0i64);
  ResumeThread(hThread);
  NtWaitForSingleObject(hThread, 0, 0i64);
  CloseHandle(hThread);
  *(_DWORD *)Str1 = 0;
  *(_DWORD *)Destination = 0;
  strncpy(Destination, (const char *)alloced, 3ui64);
  strncpy(Str1, (const char *)alloced + 67, 3ui64);
  if ( !strcmp(Str1, "110") )
  {
    v40 = (char *)&unk_140006590;
  }
  else
  {
    if ( strcmp(Str1, "120") )
      ExitProcess(0);
    v40 = (char *)&byte_140006598;
  }
  vigenere_decrypt(Destination, 3, v40, 3);
  printf("\n%s", Destination);
  // string dtor
  return 0i64;
}
 
int __fastcall write_memory(HANDLE ProcessHandle, unsigned __int8 *ptr)
{
  int v4; // edi
  unsigned __int8 *i; // rsi
 
  GetCurrentProcess();
  if ( !NtCreateThreadEx(
          &Handle,
          0x1FFFFFu,
          0i64,
          ProcessHandle,
          (LPTHREAD_START_ROUTINE)ExitThread,
          0i64,
          1u,
          0i64,
          0i64,
          0i64,
          0i64) )
  {
    v4 = 0;
    for ( i = unk_140008050; !NtQueueApcThread(Handle, (PIO_APC_ROUTINE)RtlFillMemory, &ptr[v4], 1i64, *i); ++i )
    {
      if ( (unsigned int)++v4 >= 0x92B )
      {
        ResumeThread(Handle);
        NtWaitForSingleObject(Handle, 0, 0i64);
        CloseHandle(Handle);
        return 0;
      }
    }
    TerminateThread(Handle, 0);
    CloseHandle(Handle);
  }
  return 1;
}

常规流程,提示后输入,之后和 "kctf" 以及两个 main 之前初始化的全局变量字符串拼接 "kctf" + str1 + input + str2 ,之后写入到分配的一段内存上,再调用 write_memory 函数向 +500 的位置写入 shellcode ,再用 NtQueueApcThread 执行这段代码。 write_memory 中还有个反调试,去掉就行(上面是已经去掉的)。

write_memory 中可以看到, shellcode 的位置在 0x140008050 ,直接跳转过去,第一条 call 是花指令,跳转到当前地址 +4 的位置,处理后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.data:0000000140008054                 inc     eax
.data:0000000140008056                 pop     rdi
.data:0000000140008057                 mov     ecx, 1010806h
.data:000000014000805C                 xor     ecx, 1010101h   ; 0x907
.data:0000000140008062                 add     rdi, 1Eh        ; 0x140008073
.data:0000000140008066                 xor     esi, esi
.data:0000000140008068                 cld
.data:0000000140008069
.data:0000000140008069 loc_140008069:                          ; CODE XREF: .data:0000000140008071↓j
.data:0000000140008069                 mov     al, [rdi]
.data:000000014000806B                 cmp     al, 17h
.data:000000014000806D                 cmovz   eax, esi
.data:0000000140008070                 stosb
.data:0000000140008071                 loop    loc_140008069

这一小段循环会将后面指令中的所有 0x17 全部改成 0 ,写个脚本去掉就行,在 0x140008073 处生成函数 F5 :

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
void __fastcall check()
{
  HANDLE (__fastcall *CreateToolhelp32Snapshot)(DWORD, DWORD); // rbx
  BOOL (__stdcall *Process32First)(HANDLE, LPPROCESSENTRY32); // r13
  BOOL (__stdcall *CloseHandle)(HANDLE); // r15
  DWORD (__stdcall *GetCurrentProcessId)(); // r12
  HANDLE v4; // rdi
  bool v5; // si
  HANDLE v6; // rax
  void *v7; // r14
  BOOL i; // eax
  char *v11; // rbx
  DWORD v12; // eax
  _BYTE *v13; // rdx
  _BYTE *v14; // rbx
  struct _MEMORY_BASIC_INFORMATION Buffer; // [rsp+48h] [rbp-2B0h] BYREF
  PROCESSENTRY32 v16[2]; // [rsp+80h] [rbp-278h] BYREF
  BOOL (__stdcall *Process32Next)(HANDLE, LPPROCESSENTRY32); // [rsp+300h] [rbp+8h] MAPDST
  HANDLE (__stdcall *OpenProcess)(DWORD, BOOL, DWORD); // [rsp+308h] [rbp+10h] MAPDST
  SIZE_T (__stdcall *VirtualQueryEx)(HANDLE, LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); // [rsp+310h] [rbp+18h]
 
  CreateToolhelp32Snapshot = (HANDLE (__fastcall *)(DWORD, DWORD))find_proc(0xF88DDF46);
  OpenProcess = (HANDLE (__stdcall *)(DWORD, BOOL, DWORD))find_proc(0xFD0B55A7);
  VirtualQueryEx = (SIZE_T (__stdcall *)(HANDLE, LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T))find_proc(0x242E6FF);
  Process32First = (BOOL (__stdcall *)(HANDLE, LPPROCESSENTRY32))find_proc(0x3F347695);
  Process32Next = (BOOL (__stdcall *)(HANDLE, LPPROCESSENTRY32))find_proc(0x93E12339);
  CloseHandle = (BOOL (__stdcall *)(HANDLE))find_proc(0x1CA655F1);
  GetCurrentProcessId = (DWORD (__stdcall *)())find_proc(0x35634E1);
  v4 = 0i64;
  v16[0].dwSize = 568;
  v5 = 0;
  v6 = CreateToolhelp32Snapshot(2i64, 0i64);
  v7 = v6;
  if ( v6 != (HANDLE)-1i64 )
  {
    for ( i = Process32First(v6, v16); i; i = Process32Next(v7, v16) )
    {
      if ( v16[0].th32ProcessID == GetCurrentProcessId() )
      {
        v4 = OpenProcess(0x2000000u, 0, v16[0].th32ProcessID);
        if ( v4 )
        {
          v11 = 0i64;
          while ( VirtualQueryEx(v4, v11, &Buffer, 0x30ui64) )
          {
            v11 = (char *)Buffer.BaseAddress + Buffer.RegionSize;
            if ( Buffer.State == 4096 && Buffer.AllocationProtect == 0x40 )
            {
              v12 = GetCurrentProcessId();
              v13 = Buffer.BaseAddress;
              if ( v16[0].th32ProcessID == v12 )
                v5 = check_header(*(_DWORD *)Buffer.BaseAddress);
              if ( v5 )
              {
                v14 = v13 + 4;
                if ( check_sudoku(v13 + 4) )
                {
                  *(v14 - 4) = 0x69;
                  *(v14 - 3) = 0x6F;
                  *(v14 - 2) = 0x20;
                  *(v14 - 1) = 0;
                  v14[63] = 0x31;
                  v14[64] = 0x31;
                }
                else
                {
                  *(v14 - 4) = 0x6D;
                  *(v14 - 3) = 0x6A;
                  *(v14 - 2) = 0x29;
                  *(v14 - 1) = 0;
                  v14[63] = 49;
                  v14[64] = 50;
                }
                v14[65] = 48;
                goto LABEL_19;
              }
              *v13 = 109;
              v13[1] = 106;
              v13[2] = 41;
              v13[3] = 0;
              v13[67] = 49;
              v13[68] = 50;
              v13[69] = 48;
            }
          }
        }
      }
    }
LABEL_19:
    CloseHandle(v7);
    CloseHandle(v4);
  }
}

check_header 函数就检测头部的 "kctf" ,通过这种方式找到分配的这段内存,之后进入 flag 的判断(数独的判断)

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
char __fastcall check_sudoku(char *flag)
{
  unsigned int v2; // ebx
  int v3; // ebx
  int v4; // edi
 
  v2 = 0;
  while ( check_line(flag, v2) && check_column(flag, v2) )
  {
    if ( (int)++v2 >= 9 )
    {
      v3 = 0;
LABEL_6:
      v4 = 0;
      while ( check_grid(flag, v3, v4) )
      {
        v4 += 3;
        if ( v4 >= 9 )
        {
          v3 += 3;
          if ( v3 < 9 )
            goto LABEL_6;
          return 1;
        }
      }
      return 0;
    }
  }
  return 0;
}
 
char __fastcall check_line(char *flag, unsigned int y)
{
  int x; // ebx
  unsigned int v5; // eax
  int v7[9]; // [rsp+20h] [rbp-38h]
 
  *(_OWORD *)v7 = 0i64;
  v7[8] = 0;
  x = 0;
  *(_OWORD *)&v7[4] = 0i64;
  while ( 1 )
  {
    v5 = get_value(flag, y, x) - 1;
    if ( v5 > 8 || v7[v5] )
      break;
    ++x;
    v7[v5] = 1;
    if ( x >= 9 )
      return 1;
  }
  return 0;
}
 
char __fastcall check_column(char *flag, unsigned int x)
{
  int y; // ebx
  unsigned int v5; // eax
  int v7[9]; // [rsp+20h] [rbp-38h]
 
  *(_OWORD *)v7 = 0i64;
  v7[8] = 0;
  y = 0;
  *(_OWORD *)&v7[4] = 0i64;
  while ( 1 )
  {
    v5 = get_value(flag, y, x) - 1;
    if ( v5 > 8 || v7[v5] )
      break;
    ++y;
    v7[v5] = 1;
    if ( y >= 9 )
      return 1;
  }
  return 0;
}
 
char __fastcall check_grid(char *flag, int y0, int x0)
{
  int y_offset; // edi
  int x_offset; // ebx
  unsigned int v8; // eax
  int v10[9]; // [rsp+20h] [rbp-38h]
 
  *(_OWORD *)v10 = 0i64;
  v10[8] = 0;
  y_offset = 0;
  *(_OWORD *)&v10[4] = 0i64;
  while ( 2 )
  {
    for ( x_offset = 0; x_offset < 3; ++x_offset )
    {
      v8 = get_value(flag, y_offset + y0, x_offset + x0) - 1;
      if ( v8 > 8 || v10[v8] )
        return 0;
      v10[v8] = 1;
    }
    if ( ++y_offset < 3 )
      continue;
    break;
  }
  return 1;
}

进入 check_sudoku 就很明显的能看到,完全就是数独的判断逻辑。最后就只剩下一个函数 get_value ,这里会得到输入的格式的判断。 get_value 里的逻辑比较乱,简单整理一下函数行为:每次读取输入的 3 个字节,必须是数字或者大写的 A-F ,之后这三个字符计算得到 16 进制的数值,10进制表示的个位、十位、百位分别是数独中的 x, y, sudoku[y][x] 。当取到的 y 和 x 与传入的参数相符合时,就会结束循环。如果当前取到的输入的下标不超过 63 (第 21 组内,即原 str1 的范围内),则直接返回;否则会取后 120 字节进行判断,其中找到与 9 * y + x 相等的 2 字节对应的 10 进制数,并且这个下标和要和前一部分的每 3 字节的下标相差 21 (即跳过前面的 21 组),最后还会判断总长度是 243 + 120 保证没有多余字节。

所以前面的 63 字节是给出数独中已有的值,后 120 字节指定了输入的数独格子的顺序。直接套用 z3 例子求解:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3
 
'''
def decrypt(a, b):
    return bytes(a[i] ^ b[i % len(b)] for i in range(len(a)))
 
print(decrypt(bytes.fromhex('76144E3F0B43584E300C430A0B2717530A0B351D5F42'), b'&x+^x'))
print(decrypt(bytes.fromhex('41104A56'), b'*s>0?'))
print(decrypt(bytes.fromhex('4A5A4C0A4F4D0C0C4A4E4D53'), b'!?>d*'))
print(decrypt(bytes.fromhex('6D0C4522085314' + '64010C500A50'), b'?x)da'))
'''
 
sudoku = []
for i in range(9):
    sudoku.append([])
    for j in range(9):
        sudoku[-1].append(0)
 
known1 = '3201382652D139C0E22132DF1BC2212EA0991650A229B36436823D0B13D51E6'
known2 = '677116575313142309154604431859253431473963507533496829080645035455771774602058076430276921790210013736267644383505517280'
 
for i in range(0, len(known1), 3):
    v = int(known1[i: i + 3], 16)
    x, y, v = v % 10, (v // 10) % 10, v // 100
    # print('(%d, %d) -> %d' % (y, x, v))
    sudoku[y][x] = v
 
 
from z3 import *
 
# 9x9 matrix of integer variables
X = [ [ Int("x_%s_%s" % (i+1, j+1)) for j in range(9) ]
      for i in range(9) ]
# each cell contains a value in {1, ..., 9}
cells_c  = [ And(1 <= X[i][j], X[i][j] <= 9)
             for i in range(9) for j in range(9) ]
# each row contains a digit at most once
rows_c   = [ Distinct(X[i]) for i in range(9) ]
# each column contains a digit at most once
cols_c   = [ Distinct([ X[i][j] for i in range(9) ])
             for j in range(9) ]
# each 3x3 square contains a digit at most once
sq_c     = [ Distinct([ X[3*i0 + i][3*j0 + j]
                        for i in range(3) for j in range(3) ])
             for i0 in range(3) for j0 in range(3) ]
sudoku_c = cells_c + rows_c + cols_c + sq_c
 
'''
# sudoku instance, we use '0' for empty cells
instance = ((0,0,0,0,9,4,0,3,0),
            (0,0,0,5,1,0,0,0,7),
            (0,8,9,0,0,0,0,4,0),
            (0,0,0,0,0,0,2,0,8),
            (0,6,0,2,0,1,0,5,0),
            (1,0,2,0,0,0,0,0,0),
            (0,7,0,0,0,0,5,2,0),
            (9,0,0,0,6,5,0,0,0),
            (0,4,0,9,7,0,0,0,0))
'''
 
instance = sudoku
 
instance_c = [ If(instance[i][j] == 0,
                  True,
                  X[i][j] == instance[i][j])
               for i in range(9) for j in range(9) ]
 
s = Solver()
s.add(sudoku_c + instance_c)
# print('checking')
if s.check() == sat:
    # print('OK')
    m = s.model()
    r = [ [ m.evaluate(X[i][j]).as_long() for j in range(9) ]
          for i in range(9) ]
else:
    print("failed to solve")
 
flag = ''
for i in range(0, len(known2), 2):
    v = int(known2[i: i + 2])
    y, x = v // 9, v % 9
    # print(y, x)
    flag += hex(r[y][x] * 100 + y * 10 + x)[2: ].rjust(3, '0')
 
print(flag.upper())
# 11230A2CD3C31CA32E0D707D38E0743531F80F726C1D133B3A914E2F034B1D63BB17F34428E2A31B038C25E0FA2BF2301053752062AA16E20A2FC1971730E90823D01A724B0CA19B0652811541480B80943AE27E13122C30C120

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

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