吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2393|回复: 21
上一主题 下一主题
收起左侧

[Android CTF] 【2025春节】解题领红包之六(安卓版)——Writeup

[复制链接]
跳转到指定楼层
楼主
jackyyue_cn 发表于 2025-2-13 17:14 回帖奖励
本帖最后由 jackyyue_cn 于 2025-2-13 17:14 编辑

各位坛友们新年快乐!

相信大家都在紧张刺激的抢红包吧

前面的二三四题目我跟着论坛中大佬们的教学,花点时间基本上都能出来。
到第五题就蒙了EXE加壳 还运行不了 (可能是要WIN10以上)

第六题是我解的最费精力的一道题
2/3上线,2/10才解出 前后花了一星期 所以主要来说说这个过程
我的基础不足 解题过程比较费力, 期待大佬们的更简洁明了的解法

【01】入手
拿到题目,分为Win版和安卓版。看了一下Win版,好像是加壳了的(最害怕这种),安卓只是加混淆和so,
所以选择从安卓版入手


【02】APK调试准备
首先,使用NP或MP管理器,将AndroidManifest.xml中加上调试标志
[Asm] 纯文本查看 复制代码
<android:debuggable="true"/>


其次,是我踩到的一个坑,在IDA调试so的时候 apk始终没有加载出so 后来发现是xml里禁用了解包出so,还要改xml,找到extractNativeLibs,改成true
[Asm] 纯文本查看 复制代码
<android:extractNativeLibs="true"/>


第三,将ida里的dbgsvr\android_server上传到安卓设备,设置好端口转发 adb forward tcp:23946 tcp:23946
用adb启动调试程序 adb shell am start -D -n cn.afdm_52pojie.cm2025_1/cn.afdm_52pojie.cm2025_1.MainActivity
选择ida里的ARM android/linux调试服务器,附加到进程,记住进程id
第四,启动jdb。先进行端口转发 adb forward tcp:8700 jwdp:<进程id>
再启动 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
在ida里点击运行,安卓上的程序就能运行了


【03】代码的初探索
1.首先用jadx查看了一下apk,发现有个NativeLib类,里面有个check函数,参数是一个整数和一个字符串,猜测应该是uid和flag 比较可疑



2.进ida,打开从apk中lib/armeabi-v7a下面解压出来的libnativelib.so,直接到Exports里面找
按Ctrl+F搜索check ,果然找到了这个函数


3.双击进去,按一下Tab键,出现伪C代码,不过还是不怎么清晰

4.按照教程,将函数的参数改成JNIEnv *env\ jobject \jint \jstring,可以看到是进入函数sub_BB10进行验证的

5.进入sub_BB10函数,对相关变量进行推测并改名,大致了解函数的流程
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
int __fastcall sub_BB10(int uid, char *flag)
{
  char *v4; // r0
  _QWORD *v5; // r4
  _BYTE *v6; // r8
  char *v7; // r6
  __int64 v8; // d16
  __int64 v9; // d17
  __int64 v10; // d19
  int v11; // r5
  int v12; // r6
  int result; // r0
 
  if ( strlen(flag) != 29 )  //flag长度29
    return 1;
  v4 = (char *)malloc(0x10006u); //v4申请了一块内存
  if ( !v4 )
    return 2;
  v5 = v4; //v5也指向内存
  v6 = v4 + 65540;
  sub_B81C(v4);  //处理v4 应该是初始化
  j_memcpy(v5 + 1024, &unk_9088, 0x100u); //向内存(1024*8=8192)0x2000处拷贝数据unk_9088
  v8 = *(_QWORD *)flag;
  v9 = *((_QWORD *)flag + 1);
  v7 = flag + 13;
  v5[512] = v8; //512*8=4096, 0x1000处放flag
  v5[513] = v9;
  v10 = *((_QWORD *)v7 + 1);
  *(_QWORD *)((char *)v5 + 4109) = *(_QWORD *)v7;
  *(_QWORD *)((char *)v5 + 4117) = v10;  //这几句都是向内存中拷贝flag
  sub_B80A(v5, uid); //向内存复制uid?
  sub_B80A(v5, 4096); //
  sub_B80A(v5, 0x2000); //
  v11 = sub_B858(v5); //验证flag,返回v11
  if ( !*v6 ) //v6不能为0
  {
    free(v5);
    return 4;
  }
  v12 = (unsigned __int8)v6[1];
  free(v5);
  if ( !v12 ) //v12不能为0
    return 4;
  result = v11 - 1051524100;
  if ( v11 != 1051524100 ) //v11必须等于这个数 0x3EACFC04
    return 3;
  return result;
}
//几个关键函数
int __fastcall sub_B81C(int a1)
{
  int result; // r0
 
  sub_1F50C(a1, 0x10000u); //这里查看是一个调用memset全部置0的函数
  j_memcpy((void *)(a1 + 49152), &unk_8E88, 0x200u);//又向内存0xC000处拷贝数据unk_8E88
  *(_DWORD *)(a1 + 0x10000) = -2147434496; // 0x8000C000
  result = a1 + 0x10000;
  *(_WORD *)(a1 + 65540) = 0;
  return result;
}
int __fastcall sub_B80A(int result, int a2) //此处result应该是v5 内存指针
{
  unsigned __int16 v2; // r3
 
  v2 = *(_WORD *)(result + 65538) + 4; //0x10002处增加4
  *(_WORD *)(result + 65538) = v2;
  *(_DWORD *)(result + v2) = a2; //再放数据a2 是不是很像 PUSH a2
  return result;
}


最后是关键的sub_B858函数,流程基本上还算清楚,是一个select case分支结构,看上去像是根据读取的数据执行不同的语句
再根据作者的提示 S = Stack  怀疑是使用模拟栈操作 实现的虚拟机

[Java] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
int __fastcall sub_B858(char *p_mem)
{
  char *p_m10000; // r9
  unsigned __int16 *pint16; // r6
  char *v4; // r8
  unsigned __int16 v5; // r2
  unsigned int v6; // r1
  int v7; // r0
  unsigned int v8; // r1
  unsigned int v9; // r2
  __int16 v10; // r0
  unsigned __int16 v11; // r1
  unsigned __int16 v12; // r2
  unsigned __int16 v13; // r3
  __int16 v14; // r0
  unsigned __int16 v15; // r2
  unsigned __int16 v16; // r0
  char *v17; // r5
  int v18; // r1
  __int16 v19; // r1
  unsigned __int16 v20; // r0
  __int16 v21; // r0
  int v22; // r1
  char *v23; // r0
  int v24; // r2
  int v25; // r1
  char *v26; // r2
  __int16 v27; // r0
  int v28; // r2
  unsigned __int16 v29; // r1
  __int16 v30; // r0
  __int16 v31; // r0
  __int16 v32; // r0
  __int16 v33; // r0
  __int16 v34; // r0
 
  p_m10000 = p_mem + 0x10000;
  if ( p_mem[65540] )
    goto LABEL_37;
  pint16 = (unsigned __int16 *)*(unsigned __int16 *)p_m10000;
  v4 = p_mem + 4;
  while ( 1 )
  {
    v5 = (_WORD)pint16 + 1;
    v6 = (unsigned __int8)p_mem[(unsigned __int16)pint16];
    v7 = v6 & 7;
    if ( v7 != 7 )
    {
      pint16 = (unsigned __int16 *)((char *)pint16 + 1);
      v8 = v6 >> 3;
      v9 = v8 - 1;
      goto LABEL_8;
    }
    ++pint16;
    v7 = (unsigned __int8)p_mem[v5];
    v8 = v6 >> 3;
    v9 = v8 - 1;
    if ( v8 - 1 > 0x1E )
      break;
LABEL_8:
    switch ( v9 )
    {
      case 0u:
        v21 = *((_WORD *)p_m10000 + 1);
        v11 = v21 - 4;
        v7 = *(_DWORD *)&v4[(unsigned __int16)(v21 - 8)] ^ *(_DWORD *)&p_mem[(unsigned __int16)(v21 - 4) + 4];
        goto LABEL_3;
      case 1u:
        *(_DWORD *)&p_mem[*((unsigned __int16 *)p_m10000 + 1)] = -*(_DWORD *)&v4[(unsigned __int16)(*((_WORD *)p_m10000 + 1)
                                                                                                  - 4)];
        continue;
      case 2u:
        *((_WORD *)p_m10000 + 1) -= 4 * v7;
        continue;
      case 3u:
      case 0x19u:
        continue;
      case 4u:
        v10 = *((_WORD *)p_m10000 + 1);
        v11 = v10 - 4;
        v7 = *(_DWORD *)&v4[(unsigned __int16)(v10 - 8)] | *(_DWORD *)&p_mem[(unsigned __int16)(v10 - 4) + 4];
        goto LABEL_3;
      case 5u:
        v19 = *((_WORD *)p_m10000 + 1);
        v20 = v19 - 8 - 4 * v7 + 4;
        pint16 = *(unsigned __int16 **)&v4[(unsigned __int16)(v19 - 8)];
        *(_DWORD *)&p_mem[v20] = *(_DWORD *)&v4[(unsigned __int16)(v19 - 4)];
        *((_WORD *)p_m10000 + 1) = v20;
        continue;
      case 6u:
        v30 = *((_WORD *)p_m10000 + 1);
        v11 = v30 - 4;
        v7 = *(_DWORD *)&p_mem[(unsigned __int16)(v30 - 4) + 4] != *(_DWORD *)&v4[(unsigned __int16)(v30 - 8)];
        goto LABEL_3;
      case 7u:
        v22 = *((unsigned __int16 *)p_m10000 + 1);
        v23 = &p_mem[v22 + -4 * v7];
        v24 = *(_DWORD *)v23;
        *(_DWORD *)v23 = *(_DWORD *)&p_mem[v22];
        *(_DWORD *)&p_mem[v22] = v24;
        continue;
      case 8u:
        v31 = *((_WORD *)p_m10000 + 1);
        v11 = v31 - 4;
        v7 = *(_DWORD *)&v4[(unsigned __int16)(v31 - 8)] & *(_DWORD *)&p_mem[(unsigned __int16)(v31 - 4) + 4];
        goto LABEL_3;
      case 9u:
        *(_DWORD *)&p_mem[*((unsigned __int16 *)p_m10000 + 1)] = *(_DWORD *)&v4[(unsigned __int16)(*((_WORD *)p_m10000 + 1)
                                                                                                 - 4)] << v7;
        continue;
      case 0xAu:
        *(_DWORD *)&p_mem[*((unsigned __int16 *)p_m10000 + 1)] = ~*(_DWORD *)&v4[(unsigned __int16)(*((_WORD *)p_m10000 + 1)
                                                                                                  - 4)];
        continue;
      case 0xCu:
        v32 = *((_WORD *)p_m10000 + 1);
        v11 = v32 - 4;
        v7 = *(_DWORD *)&v4[(unsigned __int16)(v32 - 8)] + *(_DWORD *)&p_mem[(unsigned __int16)(v32 - 4) + 4];
        goto LABEL_3;
      case 0xEu:
        pint16 = (unsigned __int16 *)((char *)pint16 + (char)v7);
        continue;
      case 0xFu:
        *((_WORD *)p_m10000 + 2) = 257;
        goto LABEL_36;
      case 0x11u:
        *(_DWORD *)&p_mem[*((unsigned __int16 *)p_m10000 + 1)] = *(_DWORD *)&v4[(unsigned __int16)(*((_WORD *)p_m10000 + 1)
                                                                                                 - 4)] >> v7;
        continue;
      case 0x12u:
        v14 = *((_WORD *)p_m10000 + 1);
        v15 = v14 - 4;
        v16 = v14 - 8;
        *((_WORD *)p_m10000 + 1) = v16;
        v17 = &p_mem[v15];
        if ( !*((_DWORD *)v17 + 1) )
          goto LABEL_34;
        *((_WORD *)p_m10000 + 1) = v15;
        sub_1F518(*(_DWORD *)&v4[v16]);
        *(_DWORD *)v17 = v18;
        continue;
      case 0x14u:
        *(_DWORD *)&p_mem[*((unsigned __int16 *)p_m10000 + 1)] = (unsigned __int8)v4[(unsigned __int16)(*((_WORD *)p_m10000 + 1) - 4)];
        continue;
      case 0x15u:
        v33 = *((_WORD *)p_m10000 + 1);
        v11 = v33 - 4;
        v7 = *(_DWORD *)&v4[(unsigned __int16)(v33 - 8)] * *(_DWORD *)&p_mem[(unsigned __int16)(v33 - 4) + 4];
        goto LABEL_3;
      case 0x16u:
        v28 = (unsigned __int16)pint16;
        pint16 = (unsigned __int16 *)((char *)pint16 + (char)v7);
        v29 = *((_WORD *)p_m10000 + 1) + 4;
        *((_WORD *)p_m10000 + 1) = v29;
        *(_DWORD *)&p_mem[v29] = v28;
        continue;
      case 0x17u:
      case 0x18u:
        v12 = *((_WORD *)p_m10000 + 1);
        v13 = v12 - 4;
        v12 -= 8;
        *((_WORD *)p_m10000 + 1) = v12;
        if ( (v8 == 25) != (*(_DWORD *)&v4[v13] == *(_DWORD *)&v4[v12]) )
          pint16 = (unsigned __int16 *)((char *)pint16 + (char)v7);
        continue;
      case 0x1Au:
        v27 = *((_WORD *)p_m10000 + 1);
        v11 = v27 - 4;
        v7 = (unsigned __int8)p_mem[(unsigned __int16)(*(_DWORD *)&v4[(unsigned __int16)(v27 - 8)]
                                                     + *(_DWORD *)&p_mem[(unsigned __int16)(v27 - 4) + 4])];
        goto LABEL_3;
      case 0x1Bu:
        v25 = *((unsigned __int16 *)p_m10000 + 1);
        v26 = &p_mem[v25];
        v11 = v25 + 4;
        v7 = *(_DWORD *)&v26[-4 * v7];
        goto LABEL_3;
      case 0x1Du:
        v11 = *((_WORD *)p_m10000 + 1) + 4;
LABEL_3:
        *(_DWORD *)&p_mem[v11] = v7;
        *((_WORD *)p_m10000 + 1) = v11;
        break;
      case 0x1Eu:
        *(_DWORD *)&p_mem[*((unsigned __int16 *)p_m10000 + 1)] = *(_DWORD *)&v4[(unsigned __int16)(*((_WORD *)p_m10000 + 1)
                                                                                                 - 4)]
                                                               - 1;
        continue;
      default:
        goto LABEL_34;
    }
  }
LABEL_34:
  p_m10000[4] = 1;
LABEL_36:
  *(_WORD *)p_m10000 = (_WORD)pint16;
LABEL_37:
  v34 = *((_WORD *)p_m10000 + 1);
  *((_WORD *)p_m10000 + 1) = v34 - 4;
  return *(_DWORD *)&p_mem[(unsigned __int16)(v34 - 4) + 4];
}




【04】认清虚拟机
通过反复阅读代码、ida动态调试so,在几个函数处下断点跟进,确认是虚拟机模式(或者有更专业的名字?)
虚拟机的内存共0x10006字节,在0x1000处存放一个256字节的映射表(后面发现的,将0-9和A-Z分别映射到一个1-36的数字,其余为0)
虚拟机的指令代码就是unk_8E88的0x200字节,被存放在申请的内存0xC000处
虚拟机的堆栈SP在0x8000,[sp+4]=uid, [sp+8]=0x1000=映射表 [sp+C]=0x2000=flag


注意红框处在ASCII模式下 是不是有一些奇怪的字符?



到这里又卡住了 看来只有把所有指令功能都搞清楚 再分析出代码流程,才能分析虚拟机的操作。
先分析出每一个case指令的功能,大概像这样子:

[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
指令    功能
----------------------------
0:  xor [sp-4], [sp]; sp-=4
1:  neg [sp]
2:  sub [sp], [sp]-4*v7
3:  nop
4:  or [sp-4], [sp]; sp-=4
5:  mov [sp-4-4*v7], [sp]; sp = sp-4-4*v7; jmp [sp-4]
6:  setne [sp-4], [sp]; sp-=4
7:  xchg [sp-4*v7], [sp]
8:  and [sp-4], [sp]; sp-=4
9:  shl [sp], v7
A:  not [sp]
Cadd [sp-4], [sp]; sp-=4
E:  jmp v7
F:  exit
11: shr [sp], v7
12: ??
14: mov [sp], byte ptr [sp]
15: multi [sp-4], [sp]; sp-=4
16: push pc, call func_v7
17: cmp [sp], [sp-4]; sp-=8; je v7
18: cmp [sp], [sp-4]; sp-=8; jne v7
19: nop
1A: mov [sp-4], [sp+word([sp-4]+[sp])]; sp-=4
1B: push [sp-4*v7]
1D: push v7
1E: sub [sp], 1


其中指令12的功能比较复杂 还调用了一个子程序 费了很大功夫才勉强弄懂

接下来,现学了一下idapython,在所有select case处下断点并用脚本记录运行日志。脚本如下:

[Python] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
import idaapi
import idc
import idautils
import ida_dbg
import ida_funcs
import datetime
 
class MyDbgHook(ida_dbg.DBG_Hooks):
    # 是否正在进行指令跟踪
    is_tracing = False
 
    # 标记是否刚刚到达过结束地址
    was_at_end_addr = False
 
    def __init__(self, start_addr, end_addr):
        super().__init__()
        self.start_addr = start_addr
        self.end_addr = end_addr
        self.rva = start_addr & 0x00000FFF
        now = datetime.datetime.now()
        timestr = now.strftime("%Y%m%d_%H%M%S")
        self.f = open('i:\\crack\\2025spring\\06\\tracevmlog4_'+timestr +'.txt','w')
         
        self.rawdata = 0
        self.opdata = 0
        self.opcode = 0
        self.opaddr = 0
        self.sp = 0x800c
    def get_byte(int a):
        b=a&0xff
        if b&0x80:
            return b-256
        else:
            return b
 
    def dbg_bpt(self, tid, ea):
        """处理断点事件"""
        current_addr = idc.get_reg_value("pc"# ARM64 使用 PC 寄存器
         
        # print(f"断点触发在: {hex(current_addr)}")
 
        # 如果是在开始地址处的断点,开启指令跟踪
        if current_addr == self.start_addr:
            # 开启指令跟踪
            self.enable_tracing()
            # 单步跟踪
            # 保存上下文
            r0 = idc.get_reg_value("r0") #v7
            print(f"Start trace vm at {hex(self.start_addr)}, a1={hex(r0)},flag={hex(r0+0x1000)},opcode={hex(r0+0x10000)},stack={hex(r0+0xC000)}\n",file=self.f)
            self.step_and_trace(self.start_addr, self.end_addr)
             
        # 如果是在结束地址处的断点,关闭指令跟踪
        elif current_addr == self.end_addr:
            print(f"Exit trace vm at {hex(current_addr)}\n",file=self.f)
            # 关闭指令跟踪
            self.disable_tracing()
            # 单步跟踪
            #self.step_and_trace(self.start_addr, self.end_addr)
            # 恢复运行
            idaapi.continue_process()
        # 通过偏移确定断点功能
        else:
            rva = current_addr & 0x00000FFF
            if rva == 0x8AC: # switch
                r0 = idc.get_reg_value("r0") #v7
                r2 = idc.get_reg_value("r2") #v9
                self.opdata = r0
                self.opcode = r2
                print(f"****Case {hex(r2)}: SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} {hex(self.rawdata)} OPDATA={hex(self.opdata)}\n",file=self.f)
            elif rva == 0x888: # v3 addr
                #r0 = idc.get_reg_value("r0") #v3
                r1 = idc.get_reg_value("r1") #v6
                r6 = idc.get_reg_value("r6") #v3
                self.opaddr = r6
                self.rawdata = r1
                #print(f"[v3addr:{hex(r0)},opcode:{hex(r1)}] ",file=self.f)
            elif rva == 0x9D8: # 0 v7 XOR
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [0] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} xor [sp-4], [sp]; sp-=4  // v7=*(a1+SP-4)^*(a1+*SP) [SP={hex(r0)}] ; *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp - 4
            elif rva == 0x992: # 1 negative
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [1] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} neg [sp] //*(a1+*SP)= -*(a1+*SP) [SP={hex(r0)},val={hex(r1)}]\n",file=self.f)
            elif rva == 0x980: # 2 *SP = *SP - 4 * v7
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                rr = (r1-r0)//4
                print(f"    [2] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} sub [sp], [sp]-4*v7 //*SP=*SP-4*v7 [SP={hex(r1)},v7={hex(rr)}]\n",file=self.f)
            elif rva == 0x8F2: # 4 v7 OR
                r0 = idc.get_reg_value("r0")
                print(f"    [4] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} or [sp-4], [sp]; sp-=4  //v7 = *(a1+*SP-4) | *(a1+*SP); [SP={hex(r0)}] *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp - 4
            elif rva == 0x99C: # 5 *(a1+*SP-4-4*v7) = *(a1+*SP), *SP = *SP-4-4*v7
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [5] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} mov [sp-4-4*v7], [sp]; sp = sp-4-4*v7; jmp [sp-4] //v3 = *(a1+*SP-4); *(a1+*SP-4-4*v7) = *(a1+*SP) ;   *SP = *SP-4-4*v7 [SP={hex(r1)},v7={hex(r0)}]\n",file=self.f)
            elif rva == 0xA6C: # 6 v7= ( *(a1+*SP) != *(a1+*SP-4) )
                r0 = idc.get_reg_value("r0")
                print(f"    [6] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} setne [sp-4], [sp]; sp-=4 //v7= ( *(a1+*SP) != *(a1+*SP-4) ) ; [SP={hex(r0)}] *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp - 4
            elif rva == 0xA00: # 7 SWAP
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                r2 = idc.get_reg_value("r2")
                r3 = idc.get_reg_value("r3")
                print(f"    [7] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} xchg [sp-4*v7], [sp]  //*(a1+*SP-4*v7) = *(a1+*SP) ; *(a1+*SP) = *(a1+*SP-4*v7) [SWAP:a1+SP-4*v7({hex(r0)})={hex(r3)},a1+SP({hex(r1)})={hex(r2)}]\n",file=self.f)
            elif rva == 0xA9A: # 8 v7 AND
                r1 = idc.get_reg_value("r1")
                print(f"    [8] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} and [sp-4], [sp]; sp-=4 //v7 = *(a1+*SP-4) & *(a1+*SP); [SP={hex(r1+4)}] *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp - 4
            elif rva == 0x9C4: # 9 SHIFT LEFT
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [9] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} shl [sp], v7 //*(a1+*SP) = *(a1+*SP) << v7 [SP={hex(r1)},v7={hex(r0)}]\n",file=self.f)
            elif rva == 0xA5A: # A NOT
                r0 = idc.get_reg_value("r0")
                print(f"    [A] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} not [sp] //*(a1+*SP) = ~ *(a1+*SP) [SP={hex(r0)}]\n",file=self.f)
            elif rva == 0xAA8: # C v7 ADD
                r0 = idc.get_reg_value("r0")
                print(f"    [C] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} add [sp-4], [sp]; sp-=4 //v7 = *(a1+*SP-4) + *(a1+*SP); [SP={hex(r0)}] *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp - 4
            elif rva == 0x9EA: # E v3
                r0 = idc.get_reg_value("r0") #v7
                r6 = idc.get_reg_value("r6") #v3
                print(f"    [E] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} jmp {hex(self.opaddr)}+{hex(r0)}  //OPADDR_v3[{hex(r6)}] += v7[{hex(r0)}]\n",file=self.f)
            elif rva == 0xAF2: # F EXIT
                print(f"    [F] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} exit //!!!!EXIT HERE\n",file=self.f)
            elif rva == 0x968: # 11 SHIFT RIGHT
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [11] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} shr [sp], v7 //*(a1+*SP) = *(a1+*SP)>>v7 [SP={hex(r1)},v7={hex(r0)}]\n",file=self.f)
            elif rva == 0x93E: # 12
                r0 = idc.get_reg_value("r0")
                print(f"    [12] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} ???? //if [*(a1+*SP-4)==0] goto LABEL34; *SP=*SP-4; [SP={hex(r0)}] call sub_518(*(a1+*SP-8)) ",file=self.f)
            elif rva == 0x538: # 12-sub_534
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                r2 = idc.get_reg_value("r2")
                r3 = idc.get_reg_value("r3")
                print(f"    [12*] sub_534 [p1={hex(r0)},p2={hex(r1)},p3={hex(r2)},p4={hex(r3)}] ",file=self.f)
            elif rva == 0x550: # 12-sub_534
                r3 = idc.get_reg_value("r3")
                r12 = idc.get_reg_value("r12")
                print(f"    [12**] sub_534 [_clz1={hex(r12)},_clz2={hex(r3)},final-funcaddr=6DC-{hex(12*(r3-r12))}] ",file=self.f)
            elif rva == 0xA10: # 14 COMPRESS
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [14] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} mov [sp], byte ptr [sp]  //*(a1+*SP)= byte*(a1+*SP) [SP={hex(r0)},val={hex(r1)}]\n",file=self.f)
            elif rva == 0xAC0: # 15 v7 multiple
                r0 = idc.get_reg_value("r0")
                print(f"    [15] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} multi [sp-4], [sp]; sp-=4 //v7 = *(a1+*SP-4) * *(a1+*SP); [SP={hex(r0)}]  *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp-4
            elif rva == 0xA44: # 16
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                print(f"    [16] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} call func_{hex(self.opaddr)}+{hex(r0)}+2 //*SP=*SP+4 ;  *(a1+*SP+4)=old_OPADDR_v3 ; OPADDR_v3+=v7 [SP={hex(r1)},v7={hex(r0)}]\n",file=self.f)
                self.sp = self.sp + 4
            elif rva == 0x90C: # 17 JE / 18 JNE
                r0 = idc.get_reg_value("r0")
                r2 = idc.get_reg_value("r2")  
                print(f"    [{hex(self.opcode)}] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} cmp [sp], [sp-4]; sp-=8; je {hex(self.opaddr)}+{hex(r0)} //*SP = *SP-8 ; [SP={hex(r2)},v7={hex(r0)}] if [v9==0x18 && (*(a1+*SP) != *(a1+*SP-4))] OPADDR += v7\n",file=self.f)
                self.sp = self.sp - 8
            elif rva == 0xA28: # 1A
                r0 = idc.get_reg_value("r0")
                print(f"    [1A] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} mov [sp-4], [sp+word([sp-4]+[sp])]; sp-=4  //v7=*(a1+word*(a1+*SP-4)+word*(a1+*SP)) ; [SP={hex(r0)}]  *(a1+*SP-4) = v7; *SP=*SP-4\n",file=self.f)
                self.sp = self.sp - 4
            elif rva == 0xA1C: # 1B
                r0 = idc.get_reg_value("r0")
                r1 = idc.get_reg_value("r1")
                r2 = idc.get_reg_value("r2")
                print(f"    [1B] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} push [sp-4*v7] //v7=*(a1+*SP-4*v7) ; [SP={hex(r1)},v7={hex(r0)},addr={hex(r2)}] *(a1+*SP+4) = v7; *SP=*SP+4\n",file=self.f)
                self.sp = self.sp + 4
            elif rva == 0xA88: # 1D push v7
                r1 = idc.get_reg_value("r1")
                r0 = idc.get_reg_value("r0")
                print(f"    [1D] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} push {hex(r0)} //*(a1+*SP+4) = v7; *SP=*SP+4 [push v7. SP={hex(r1)},v7={hex(r0)}]\n",file=self.f)
                self.sp = self.sp + 4
            elif rva == 0xAE2: # 1E
                r0 = idc.get_reg_value("r0")
                print(f"    [1E] SP:{hex(self.sp)} ADDR:{hex(self.opaddr)} OPCODE:{hex(self.opcode)} sub [sp], 1 //*(a1+*SP) = *(a1+*SP) - 1 [SP={hex(r0)}]\n",file=self.f)
         
        idaapi.continue_process()
        return 0
 
    def dbg_step_into(self) -> bool:
        """处理单步事件"""
        self.step_and_trace(self.start_addr, self.end_addr)
        return True
 
    def dbg_step_over(self):
        """处理单步事件"""
        self.step_and_trace(self.start_addr, self.end_addr)
        return True
 
    def enable_tracing(self):
        """开启指令跟踪"""
        if not self.is_tracing:
            self.is_tracing = True
            # 清除所有 tracing 数据
            ida_dbg.clear_trace()
            # 开启指令跟踪
            ida_dbg.enable_insn_trace()
            # 挂起其他线程
            #suspend_other_threads()
            print("清除所有 tracing 数据,开启指令跟踪,挂起其他线程")
 
    def disable_tracing(self):
        """关闭指令跟踪"""
        self.is_tracing = False
        # 关闭指令跟踪
        ida_dbg.disable_insn_trace()
        print("关闭指令跟踪")
 
        # 移除开始和结束地址的断点
        idaapi.del_bpt(self.start_addr)
        idaapi.del_bpt(self.end_addr)
        print(f"移除断点: {hex(self.start_addr)}, {hex(self.end_addr)}")
         
        self.f.close()
 
        # 解除 hook
        self.unhook()
        print("解除hook")
 
        # 恢复线程
        #resume_all_threads()
 
    def step_and_trace(self, start_addr, end_addr):
        """单步跟踪指令,遇到函数调用时步入"""
        if self.is_tracing:
            current_addr = idc.get_reg_value("pc"# ARM64 用 pc 寄存器
             
            # 检查地址
            off = current_addr - self.start_addr
             
            #if off == 0x00:
 
            # 检查当前地址是否为结束地址
            if current_addr == end_addr:
                print(f"[{hex(start_addr)}] 已到达结束地址,执行结束指令")
                ida_dbg.request_step_over()  # 执行完结束地址指令
                self.was_at_end_addr = True
                return
 
            # 检查是否刚刚执行完结束地址指令
            if current_addr != end_addr and self.was_at_end_addr:
                print(f"[{hex(start_addr)}] 已执行完结束地址指令,关闭 tracing")
                self.disable_tracing()
                idaapi.continue_process()
                return
 
            print(f"[{hex(current_addr)}] step over")
            ida_dbg.request_step_over()  # 单步跟踪
 
 
def get_module_by_addr(addr):
    """根据地址获取所属的模块(段)起始地址"""
    for seg in idautils.Segments():
        if seg <= addr < idc.get_segm_end(seg):
            return seg
    return None
 
 
def is_in_current_module(addr, start_addr):
    """判断当前地址是否与开始地址处于相同的模块"""
    start_module = get_module_by_addr(start_addr)
    current_module = get_module_by_addr(addr)
 
    # 比较当前地址和开始地址是否在相同的模块(段)中
    if start_module == current_module:
        return True
    return False
 
 
def is_call_instruction(ea):
    """判断当前地址是否是一个调用函数的指令(包括间接跳转)"""
    mnem = idc.print_insn_mnem(ea)
    # ARM64 中的函数调用指令包括 BL, BLR, BR
    return mnem in ["BL", "BLR", "BR"]
 
 
def get_call_target(ea):
    """获取调用指令的目标地址,如果是间接调用则返回寄存器中的值"""
    if is_call_instruction(ea):
        mnem = idc.print_insn_mnem(ea)
 
        # 对于 BL/BLR 指令,获取目标地址
        if mnem in ["BL", "BLR"]:
            return idc.get_operand_value(ea, 0)
 
        # 对于 BR 指令,目标地址存储在寄存器中
        if mnem == "BR":
            reg_name = idc.print_operand(ea, 0# 获取寄存器名,例如 X17
            reg_val = idc.get_reg_value(reg_name)  # 获取寄存器中的值(目标地址)
            return reg_val
    return None
 
 
def set_breakpoints(start_addr, end_addr):
    """设置断点"""
    # 在开始地址设置断点
    idaapi.add_bpt(start_addr)
    print(f"在 {hex(start_addr)} 设置断点")
 
    # 在结束地址设置断点
    idaapi.add_bpt(end_addr)
    print(f"在 {hex(end_addr)} 设置断点")
 
 
def suspend_other_threads():
    """
    挂起除当前线程外的其他线程
    """
    # 获取当前线程ID
    current_tid = ida_dbg.get_current_thread()
 
    # 获取线程数量
    thread_qty = ida_dbg.get_thread_qty()
 
    # 遍历所有线程并获取线程 ID
    for i in range(thread_qty):
        tid = ida_dbg.getn_thread(i)
        if tid != current_tid:
            # 挂起所有非当前线程
            success = ida_dbg.suspend_thread(tid)
            if success:
                print(f"Suspended thread {tid}")
            else:
                print(f"Failed to suspend thread {tid}")
 
    print(f"Current thread {current_tid} remains active.")
 
 
def resume_all_threads():
    """
    恢复所有线程的运行状态
    """
    # 获取线程数量
    thread_qty = ida_dbg.get_thread_qty()
 
    # 遍历所有线程并获取线程 ID
    for i in range(thread_qty):
        tid = ida_dbg.getn_thread(i)
        # 恢复线程
        success = ida_dbg.resume_thread(tid)
        if success:
            print(f"Resumed thread {tid}")
 
print("All threads resumed.")
 
if __name__ == "__main__":
    # 开始和结束地址
    start_addr = 0xCB422860 # 看一下ADD.W R9,R0,#0x10000 处的地址
    end_addr = start_addr + 0x2AE
 
    print(f"ida trace start[{hex(start_addr)}] end[{hex(end_addr)}]")
 
    # 设置断点
    set_breakpoints(start_addr, end_addr)
 
    # 创建并启用调试钩子
    hook = MyDbgHook(start_addr, end_addr)
    hook.hook()  # 激活钩子



使用ida运行脚本 几分钟后得到一个txt日志文件(这个脚本不知道为什么 一个断点会记录两次……)
[Plain Text] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Start trace vm at 0xcb458860, a1=0xca4bce40,flag=0xca4bde40,opcode=0xca4cce40,stack=0xca4c8e40
 
****Case 0x16: SP:0x800c ADDR:0xc000 0xbf OPDATA=0x7f
 
    [16] SP:0x8010 ADDR:0xc000 OPCODE:0x16 call func_0xc000+0x7f //*SP=*SP+4 ;  *(a1+*SP+4)=old_OPADDR_v3 ; OPADDR_v3+=v7 [SP=0x800c,v7=0x7f]
 
****Case 0x1b: SP:0x8014 ADDR:0xc081 0xe3 OPDATA=0x3
 
    [1B] SP:0x8018 ADDR:0xc081 OPCODE:0x1b push [sp-4*v7] //v7=*(a1+*SP-4*v7) ; [SP=0x8014,v7=0x3,addr=0xca4c4e50] *(a1+*SP+4) = v7; *SP=*SP+4
 
****Case 0x16: SP:0x801c ADDR:0xc082 0xbf OPDATA=0x32
 
    [16] SP:0x8020 ADDR:0xc082 OPCODE:0x16 call func_0xc082+0x32 //*SP=*SP+4 ;  *(a1+*SP+4)=old_OPADDR_v3 ; OPADDR_v3+=v7 [SP=0x8014,v7=0x32]
…………此处省略几千行
****Case 0x5: SP:0x850c ADDR:0xc080 0x33 OPDATA=0x3
 
    [5] SP:0x850c ADDR:0xc080 OPCODE:0x5 mov [sp-4-4*v7], [sp]; sp = sp-4-4*v7; jmp [sp-4] //v3 = *(a1+*SP-4); *(a1+*SP-4-4*v7) = *(a1+*SP) ;   *SP = *SP-4-4*v7 [SP=0x8024,v7=0x3]
 
****Case 0xf: SP:0x850c ADDR:0xc088 0x80 OPDATA=0x0
 
    [F] SP:0x850c ADDR:0xc088 OPCODE:0xf exit //!!!!EXIT HERE
 
Exit trace vm at 0xcb458b0e


所以txt经过去重最终得到三千多条指令的执行过程
然后导入到Excel,前面加上序号,再提取出每条记录的ADDR指令地址,最后再按指令地址排序,复制一份副本,去掉重复项,得到比较干净的虚拟机汇编代码
从C000到C151共一百多条 我用红色标出了有跳转的指令




最后,就是梳理程序逻辑了。花了不少时间,逐条分析,得到程序逻辑大致如下:
1、将uid的十六进制(假设为0xAABBCCDD)与给定的字符串混合,得到 data = "2025AA52pojieBBafdmCC2025DD"的数据 (字符串就是在上面8E88ASCII中看到的那些)
2、利用CRC算法,得到上面数据的CRC值  crc = crc32(data)
3、将a置0,从{后面开始,读取flag的每个字符,到映射表中找对应的值,将a乘以36再加上这个值 ,重复5次(后来发现,这个像是36进制?)
4、取crc的低1字节乘以0x13541加上5,放在d中(flag第一段5字符的起点为5,后面依次为0xB\0x11\0x17)
5、对a、d进行指令12的操作 d = opcode12(a, d); (这个函数也分析了好久,静态分析只能到最后几条指令,动态分析才能跳到真正执行的地方)就是当a>=d时 a循环减去d 最后返回
6、将返回结果与上一次操作结果进行或操作
7、最后将结果减1,再异或0xC15303FB,返回



【05】挑战解密算法
既然知道了虚拟机执行的过程,后面的任务就是逆向出flag中的那些字符了。推理过程如下:
最终结果要等于 0x3EACFC04
0x3EACFC04 ^ 0xC15303FB = 0xFFFFFFFF

0xFFFFFFFF + 1 = 0

也就是说,每一次对flag字符串中5字符的运算结果必须全为0


设计出用C代码实现的虚拟机检查过程如下:

[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
int main() {
        char flag[0x1E] = "flag{QS3CF-1X9JG-YL7O4-LM3GU}";
        char real_flag2[0x1E] = {0};
        int i;
        int pos = 0;
        uint32_t v;
         
    unsigned char ch;
    uint32_t v8014, v8024, v8028, v802c, var1;
    uint32_t a, b, d, tmp;
        ch = 0x00;
 
       //crc 处理结果
        v8014 = 0xE6F0CBF7;
        printf("v8014: %08X\n", v8014);
         
        v8024 = var1 = 0;
        //check flag
        v8028 = 5;
        while( v8028 < 0x1C){
                v802c = ( v8014 >> (pos * 8) ) & 0xFF; // crc 1 byte
                a = b = 0;
                for( i = 0; i < 5; i++ ) {
                        a = a * 0x24;
                        ch = flag[v8028+i];
                        v = unk_9088[ch];
                        if( v == 0 ) {
                                v = v | a;
                                v = v | 1;
                                v = v | i;
                                printf("Error flag character.");
                                return -1;
                        } else {
                                a = a + v;
                                a = a - 1;
                        }
                }
                if( (a >> 0x19 != 0) ) { //SHOULD ENTER THIS
                        a = a + 1;
                        d = 0x13541 * v802c; // crc 1 byte
                        d = d + (v8028); // d = 5, B, 11, 17;  d1 =
                        if( d ) {
                                d = opcode12(a, d); // a SHOULD == d
                        }
                        var1 = d; //var1 SHOULD BE 0
                } else { //DON'T' ENTER THIS
                        a = a | 1;
                        var1 = a;
                }
                 
                var1 = var1 | v8024; // V8024 SHOULD BE 0
                tmp = var1; var1 = v8024; v8024 = tmp;
                 
                pos++;
                v8028+=5;
                if( flag[v8028] != '-' && flag[v8028] != '}'  ) {
                        printf("Error flag string format!");
                        return -1;
                }
                v8028++;
        }
        v8024 = v8024 -1;
        v8024 = v8024 ^ 0xc15303fb;
         
        if(v8024 == 0x3EACFC04 )
                printf("Success! Result is: %08X\n", v8024);
        else
                printf("Error flag str %s, result is: %08X\n", flag, v8024);
         
    return 0;
}


最终得出结论,由5字符组成的36进制算出的数字a +1,要么等于d,要么必须是d的倍数,通过循环相减才能得到0
因此再次设计反向计算的代码,通过每一个crc字节和flag位置,算出d,再找到合适的a,再通过a去得到5个1-36的数字,并查映射表找到字符
代码如下:

[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
void get_flag(uint32_t crc, char* dst)
{
        uint32_t d[4], v, a, b;
        uint8_t pos[4] = {5, 0xB, 0x11, 0x17};
        int i, j, k;
        unsigned char ch, f[7]={0};
        char flag[0x1E]={0};
         
        srand((uint32_t)time(NULL));
        strcpy(flag, "flag{");
        for (i = 0; i < 4; i++ ) {
                v = (crc >> (i*8)) & 0xFF;
                d[i] = 0x13541*v;
                d[i] += pos[i];
                k=0;
                b = d[i];
                //while( b < 0x2000000 ) { b *= 2; k++; }
                while( b < 0x2000000 ) { b += d[i]; }
                //
                a = b - 1;
                do{
                        a = b - 1;
                        for( j = 4; j >=0; j--) {
                                a = a + 1;
                                if(j > 0 ){
                                        //v = 1+rand()%36;
                                        v = a % 36;
                                        if( v==0 ) v = 36;
                                        f[j] = vtochar(v);
                                        //printf("%02X:%c,",v,f[j]);
                                        a = a - v;
                                        a = a / 36;
                                }else{
                                        v = a;
                                        if( v >=1 && v <= 36) {
                                                f[j] = vtochar(v);
                                                //printf("%02X:%c,",v,f[j]);
                                                a = a - v;
                                        } else {
                                                // error
                                        }
                                }
                        }
                        b += d[i];
                } while( a != 0 );
                if(i==3) f[5] = '}';
                else f[5]='-';
                printf("%d:%s\n",i+1,f);
                strcat(flag, (const char*)f);
                 
        }
        strcpy(dst, flag);
         
}



最终算出:
我的uid=1581363 的情况下
得到的flag= flag{XS1AC-YVBWO-QQOCL-LM3GG}

后来还发现,当a取d的更高倍数也可以,得到不同的flag也能通过。所以作者提示了答案可能不唯一。

以上就是我在任务六的解答过程,虽然解答出来了,但对其中的算法、实现原理等并不了解,期待大佬出更精准的WP。
感谢!


免费评分

参与人数 4吾爱币 +5 热心值 +4 收起 理由
GsunX1 + 1 我很赞同!
soyiC + 1 + 1 用心讨论,共获提升!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
Coxxs + 3 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
爱飞的猫 发表于 2025-2-13 19:26
本帖最后由 爱飞的猫 于 2025-2-13 19:50 编辑

这题 Windows 版套了个原版的 upx,直接用 upx -d 就能脱掉了,没有坑;Win 和安卓版理论上都是一样的难度。

opcode 0 其实是未定义,被编译器优化了。实际的 opcode 要加 1。

文中的 opcode12 对应的其实是虚拟机的 19 (十进制) 求模指令,不知道为什么安卓端给优化成这个鬼样子了 32 位 arm 处理器不一定有求模指令,所以编译器自己补了一段代码…

// 定义
constexpr uint8_t SMVM_MOD = 19;

// 实现
        case SMVM_MOD: {
            const auto a = POP(); // divisor
            const auto b = POP(); // dividend
            if (a == 0)
                vm->halt = true; // division by zero
            else
                PUSH(b % a);
            break;
        }

对比了下 aarch64 和 arm 的反编译代码,可以发现 aarch64 下生成的伪码是正常的:

      case 0x12u:
        v17 = *(_WORD *)(a1 + 65538);
        v18 = v17 - 4;
        v19 = v17 - 8;
        v20 = (int *)(a1 + v18);
        *(_WORD *)(a1 + 65538) = v19;
        v21 = v20[1];
        if ( !v21 )
          goto LABEL_34;
        *(_WORD *)(a1 + 65538) = v18;
        *v20 = *(_DWORD *)(v5 + v19) % v21;
        continue;

将a乘以36再加上这个值 ,重复5次(后来发现,这个像是36进制?)

是的,base36 编码的数字。码表打乱过。

const char vm_chars_table[37] = "KEA7WGUN01S6DJB28O5I3LQXMVH9YTPC4ZFR";

void encode_base36(char *str, uint32_t value)
{
    char buffer[20] = {};
    int i = 0;
    do {
        buffer[i++] = vm_chars_table[value % 36];
        value /= 36;
    } while (value);

    int j = 0;
    for (; i > 0; j++, i--)
        str[j] = buffer[i - 1];
    str[j] = '\0';
}

第五题就蒙了EXE加壳 还运行不了 (可能是要WIN10以上)

第五题因为 Win7 下不太好勾反调试要用的函数,所以屏蔽了下。本来应该有弹窗提示的,但是在那之前就崩了

推荐
抱薪风雪雾 发表于 2025-2-14 10:11
沙发
WangWhereGo 发表于 2025-2-13 18:17
3#
Miracle0927 发表于 2025-2-13 18:30
6666  过年干这个才是正事啊
5#
 楼主| jackyyue_cn 发表于 2025-2-13 21:00 |楼主
爱飞的猫 发表于 2025-2-13 19:26
[md]这题 Windows 版套了个原版的 upx,直接用 `upx -d` 就能脱掉了,没有坑;Win 和安卓版理论上都是一样 ...

感谢版主答疑

看了一下 还真是64位libso的可读性更好一些

当时觉得32位应该要好处理一些 就没去看64位了 没想到被ARM/THUMB编译器给绕了好大一圈的路
6#
twl288 发表于 2025-2-14 00:27
这技术可以啊
7#
love657902 发表于 2025-2-14 08:41
东西不错
9#
伤城幻化 发表于 2025-2-14 10:29
很强,看到那么大一串switch 我都头皮发麻了,下不去嘴
10#
89507982 发表于 2025-2-14 10:50
虽然不会,但是给大佬们点赞
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-3-18 13:10

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表