首页
社区
课程
招聘
[原创]KCTF2023 第六题 至暗时刻
2023-9-14 11:23 8728

[原创]KCTF2023 第六题 至暗时刻

2023-9-14 11:23
8728
  1. 反调试1
1
2
3
.text:0000000140001324 FF 15 2E 4D 00 00                          call    cs:IsDebuggerPresent
.text:000000014000132A 85 C0                                      test    eax, eax
.text:000000014000132C 74 05                                      jz      short loc_140001333
  1. 反调试2
1
2
3
4
.text:0000000140001324 FF 15 2E 4D 00 00                          call    cs:IsDebuggerPresent
.text:000000014000132A | 31C0                     | xor eax,eax                             |
.text:000000014000132C | EB 05                    | jmp blackclient_.140001333              |
.text:000000014000132E | E8 65FFFFFF              | call blackclient_.140001298             |
  1. 反调试3
1
2
3
4
.text:0000000140001484 FF 15 DE 4B 00 00                          call    cs:CheckRemoteDebuggerPresent
.text:000000014000148A 83 7D 38 00                                cmp     [rbp+pbDebuggerPresent], 0
.text:000000014000148E 0F 84 95 00 00 00                          jz      loc_140001529
.text:0000000140001494 B9 08 00 00 00                             mov     ecx, 8          ; Size
  1. 然后就是跟踪关键操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  fun_scanf((__int64)"%s", input_1, v64);
  fun_decode(v15, v52, (__int64)"*s>0?", 5);    // kctf
  sub_1400026AC(&Src, v16, v52, v17, qword_140009A80);// kctf拼接到字符串前面
  sub_140002BBA((__int64)v19, (__int64)&Source, 0i64, (__int64)&v69, Size);// NtAllocateVirtualMemory
  sub_140002DA9((__int64)v19);                  // NtWriteVirtualMemory
  fun_decode(v20, v58, (__int64)"!?>d*", 5);    // kernel32.dll
  fun_decode(v22, v55, (__int64)"?x)da", v21);  // RtlFillMemory(地址,大小,值)
( (unsigned int)sub_140003529((__int64)&hThread) )// NtCreateThreadEx
  strncpy((char *)&Str1, Source + 67, 3ui64);
  if ( !strcmp((const char *)&Str1, "110") )    // 此时Str1=="110"
  {
    v43 = &unk_140006590;
  }
  else
  {
    if ( strcmp((const char *)&Str1, "120") )   // 不等于120,就退出
      ExitProcess(0);
    v43 = &unk_140006598;
  }
  fun_decode(&Dest, 3, (__int64)v43, 3);
  fun_printf((__int64)&unk_14000659C, &Dest);   // 这里肯定就是打印成功了
  1. 代码中有很多syscall,win11调试进不去,需要布置win10虚拟机调试,才能看到函数名

  2. 创建的第一个线程,每次调用RtlFillMemory拷贝1字节,跟踪发现拷贝的就是 unk_140008050 中的数据,而且看着像jmp指令:

  3. 用IDA加载能识别为代码,但是有很多错误。需要x64dbg调试shellcode。
    先内存断,再执行断。shellcode运行后会将所有17替换成00,重新拿到shellcode,IDA 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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
__int64 sub_20217()
{
  __int64 (__fastcall *CreateToolhelp32Snapshot)(signed __int64, _QWORD); // rbx
  __int64 (__fastcall *Process32First)(__int64, int *); // r13
  void (__fastcall *CloseHandle)(__int64); // r15
  __int64 (*GetCurrentProcessId)(void); // r12
  __int64 process; // rdi
  bool v5; // si
  __int64 handle; // rax
  __int64 v7; // r14
  int v9; // eax
  __int64 (__fastcall *Process32Next_1)(__int64, int *); // rbx
  __int64 (__fastcall *OpenProcess_1)(signed __int64, _QWORD); // r13
  __int64 address; // rbx
  int v13; // eax
  int *v14; // rdx
  _BYTE *v15; // rbx
  int *lpBuffer; // [rsp+48h] [rbp-2B0h]
  int v17; // [rsp+58h] [rbp-2A0h]
  __int64 v18; // [rsp+60h] [rbp-298h]
  int v19; // [rsp+68h] [rbp-290h]
  int v20; // [rsp+80h] [rbp-278h]
  int v21; // [rsp+88h] [rbp-270h]
  __int64 Process32Next; // [rsp+300h] [rbp+8h]
  __int64 OpenProcess; // [rsp+308h] [rbp+10h]
  __int64 (__fastcall *VirtualQueryEx)(__int64, __int64, int **, signed __int64); // [rsp+310h] [rbp+18h]
 
  CreateToolhelp32Snapshot = (__int64 (__fastcall *)(signed __int64, _QWORD))sub_20563(0xF88DDF46);
  OpenProcess = sub_20563(0xFD0B55A7);
  VirtualQueryEx = (__int64 (__fastcall *)(__int64, __int64, int **, signed __int64))sub_20563(0x242E6FF);
  Process32First = (__int64 (__fastcall *)(__int64, int *))sub_20563(0x3F347695);
  Process32Next = sub_20563(-1813961927);
  CloseHandle = (void (__fastcall *)(__int64))sub_20563(0x1CA655F1);
  GetCurrentProcessId = (__int64 (*)(void))sub_20563(55981281);
  process = 0i64;
  v20 = 0x238;
  v5 = 0;
  handle = CreateToolhelp32Snapshot(2i64, 0i64);
  v7 = handle;
  if ( handle == -1 )
    return 0xFFFFFFFFi64;
  v9 = Process32First(handle, &v20);
  Process32Next_1 = (__int64 (__fastcall *)(__int64, int *))Process32Next;
  OpenProcess_1 = (__int64 (__fastcall *)(signed __int64, _QWORD))OpenProcess;
  while ( v9 )
  {
    if ( v21 == (unsigned int)GetCurrentProcessId() )
    {
      process = OpenProcess_1(0x2000000i64, 0i64);
      if ( process )
      {
        address = 0i64;
        while ( 1 )
        {
          do
          {
            if ( !VirtualQueryEx(process, address, &lpBuffer, 48i64) )
            {
              Process32Next_1 = (__int64 (__fastcall *)(__int64, int *))Process32Next;
              OpenProcess_1 = (__int64 (__fastcall *)(signed __int64, _QWORD))OpenProcess;
              goto LABEL_20;
            }
            address = (__int64)lpBuffer + v18;
          }
          while ( v19 != 4096 || v17 != 64 );
          v13 = GetCurrentProcessId();
          v14 = lpBuffer;
          if ( v21 == v13 )
            v5 = sub_2062F(*lpBuffer);
          if ( v5 )
            break;
          *(_BYTE *)v14 = 'm';                  // 第一层判断,kctf头
          *((_BYTE *)v14 + 1) = 'j';
          *((_BYTE *)v14 + 2) = ')';
          *((_BYTE *)v14 + 3) = 0;
          *((_BYTE *)v14 + 67) = '1';
          *((_BYTE *)v14 + 68) = '2';
          *((_BYTE *)v14 + 69) = '0';
        }
        v15 = v14 + 1;
        if ( sub_20AA3((__int64)(v14 + 1)) )    // 所有判断,都在这里呗 传入kctf后面的字符串
        {
          *(v15 - 4) = 'i';                     // 正确答案!!!!!!
          *(v15 - 3) = 'o';
          *(v15 - 2) = ' ';
          *(v15 - 1) = 0;
          v15[63] = '1';
          v15[64] = '1';
        }
        else
        {
          *(v15 - 4) = 'm';
          *(v15 - 3) = 'j';
          *(v15 - 2) = ')';
          *(v15 - 1) = 0;
          v15[63] = '1';
          v15[64] = '2';
        }
        v15[65] = '0';
        break;
      }
    }
LABEL_20:
    v9 = Process32Next_1(v7, &v20);
  }
  CloseHandle(v7);
  return ((__int64 (__fastcall *)(__int64))CloseHandle)(process);
}
  1. 然后就是反复调试,发现字符串分为3块:
    3201382652D139C0E22132DF1BC2212EA0991650A229B36436823D0B13D51E6 //前缀,3位一个16进制数
    input //必须是3的倍数,后续分析长度必须是180
    677116575313142309154604431859253431473963507533496829080645035455771774602058076430276921790210013736267644383505517280 //后缀 2位一个10进制数

  2. 核心函数是2063B,要求input每3位,是一个十六进制
    比如 input[0]="112",0x112=274,十位*9+个位=67,要跟后缀[0]相等
    十位和个位就确定了。

  3. 同时十位和个位要作为下标,充当数独的坐标。百位就是数独的内容。
    数独一共是9*9个格子,其中前缀是21个数,input是60个数。所以先转换已有的21个数,更新到棋盘

    再求解出答案:

  4. 编程反算数独下标,

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
74,[7][4]的数字=2274=0x112input[0]="112"
78,[7][8]的数字=7778=0x30a, input[2]="30A" ,有限制是大写字母
17,以此类推,
63
58
14
15
25
10
16
51
04
47
20
65
27
37
34
52
43
70
55
83
36
54
75
32
08
06
50
03
60
61
85
18
82
66
22
64
07
71
33
30
76
23
87
02
11
01
41
40
28
84
48
42
38
05
56
80
88
  1. 得到flag,好玩的题
    11230A2CD3C31CA32E0D707D38E0743531F80F726C1D133B3A914E2F034B1D63BB17F34428E2A31B038C25E0FA2BF2301053752062AA16E20A2FC1971730E90823D01A724B0CA19B0652811541480B80943AE27E13122C30C120

[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

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