首页
社区
课程
招聘
10
[原创] 2025吾愛解題領紅包活動(Android題解)
发表于: 2025-2-13 10:00 20689

[原创] 2025吾愛解題領紅包活動(Android題解)

2025-2-13 10:00
20689

前言

簡單寫一下Android部份的解題思路。

第三題:Android初級題

明顯的xxtea特徵。

解密後直接得到flag

第四題:Android中級題

目標是找到秘鑰。

Java層關鍵邏輯如下,調用了Check函數來檢查密鑰。

是個native函數。

嘗試直接hook RegisterNatives,發現Check果然是動態注冊的,在0xe8c54

Check一開始是一些反調試邏輯。

先看anti1,它調用decrypt_str解密字符串,但奇怪的是解密出來的字符串不是以\x00結尾,導致opendir直接失敗,使得後面的反調試邏輯形同虛設?( 不知是故意的還是不小心的 )

anti2do_something1也同理,皆因為decrypt_str的問題導致後續的邏輯失效。

繼續向下跟,看到它動態計算出一個函數地址,大概率就是加密函數,最後與密文進行對比。

一開始以為動態計算的那個函數地址是固定的,後來才發現有兩個不同的地址,會隨著上面anti1anti2do_something1getenv等函數返回的結果而改變。

類似蜜罐的概念,當觸發anti邏輯後,不主動殺死APP,而是改變程序的執行流,導向錯誤的分支。

func1func2如下,前者是錯誤的分支,後者是正確的,我的環境默認會走func1

可以看到兩者的加密方式都是相同的異或加密,不同的只有異或的值。

經測試發現,手動hook getenvdo_something1修改其參數、返回值後,程序才會走向func2。這時再hook encrypt,將正確的異或值dump下來。

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
function hook_dlopen(soName) {
    Interceptor.attach(Module.findExportByName(null, "dlopen"),
        {
            onEnter: function (args) {
                var pathptr = args[0];
                if (pathptr !== undefined && pathptr != null) {
                    var path = ptr(pathptr).readCString();
                    if (path.indexOf(soName) >= 0) {
                        this.is_can_hook = true;
                    }
                }
            },
            onLeave: function (retval) {
                if (this.is_can_hook) {
                    console.log("hook start...");
                    hook_func(soName)
                }
            }
        }
    );
  
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
        {
            onEnter: function (args) {
                var pathptr = args[0];
                if (pathptr !== undefined && pathptr != null) {
                    var path = ptr(pathptr).readCString();
                    if (path.indexOf(soName) >= 0) {
                        this.is_can_hook = true;
                    }
                }
            },
            onLeave: function (retval) {
                if (this.is_can_hook) {
                    console.log("hook start...");
                    hook_func(soName)
                }
            }
        }
    );
}
 
function hook_func(soName) {
    function hook_xorkey(base) {
        Interceptor.attach(base.add(0xE9954), {
            onLeave: function(retval) {
                console.log("[xor_key] ", hexdump(retval))
            }
        })
    }
     
    function hook_test2(base) {
        Interceptor.attach(base.add(0xE98A0), {
            onEnter: function(args) {
                console.log("[call func2] ")
            }
        })
         
        // do_something1
        Interceptor.attach(base.add(0xE74E8), {
            onEnter: function(args) {
                console.log("[call dosomething1] ")
            },
            onLeave: function(retval) {
                console.log("[dosomething1] retval: ", retval)
                retval.replace(0);
                console.log("[dosomething1] retval: ", retval)
            }
        })
         
        Interceptor.attach(Module.findExportByName(null, "getenv"), {
            onEnter: function(args) {
                let a0 = args[0].readCString();
                if (a0.indexOf("name") != -1) {
                    Memory.writeUtf8String(args[0], "name");
                    this.flag = true
                    console.log("[getenv] a0: ", args[0].readCString())
                }
            },
            onLeave: function(retval) {
                if (this.flag) {
                    console.log("retval: ", retval.readCString())
                }
            }
        })
    }
 
    var base = Module.findBaseAddress(soName);
 
    hook_xorkey(base);
    hook_test2(base);
}
 
function main() {
    hook_dlopen("libwuaipojie2025_game.so")
}
 
setImmediate(main)

最終解密腳本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xor_key1 = [0x2E, 0x4B, 0xEE, 0xC8, 0xE0, 0x95, 0x88, 0x47, 0xB0, 0x72, 0x1B, 0x68, 0x40, 0xD0, 0x0A, 0x84]
# xor_key2 = [0x27, 0xAF, 0xF3, 0xA7, 0xA1, 0x64, 0x51, 0xC3, 0x67, 0x6D, 0x19, 0x04, 0xE9, 0x58, 0xE9, 0x6F]
xor_key2 = [0x77, 0x70, 0x8a]
 
xor_key_list = [xor_key1, xor_key2]
 
data1 = 0x72ECF89BAF8F2748
data2 = 0xB63AE26B0C720798
data3 = 0xF75942
enc = data1.to_bytes(8, 'little') + data2.to_bytes(8, 'little') + data3.to_bytes(3, 'little')
enc = bytearray(enc)
 
xor_keylist_idx = 0
xor_key_idx = 0
flag = ""
for i in range(len(enc)):
    if (i & 0xf) == 0:
        xor_key = xor_key_list[xor_keylist_idx]
        xor_keylist_idx += 1
        xor_key_idx = 0
    flag += chr(xor_key[xor_key_idx] ^ enc[i])
    xor_key_idx += 1
 
print("flag: ", flag)

輸出:flag: flag{md5(uid+2025)}

第六題:Windows & Android高級題

Java層分析

先看看題目描述,要幾個重點:

  1. flag格式為flag{XXXXX-XXXXX-XXXXX-XXXXX},其中X要麼是大寫字母,要麼是數字。
  2. 不同UID對應不同的Flag,可能有多個解。
  3. SISC中的S意為堆棧。

再看看APP,要求輸入UID和Flag。

用新版jeb查看Java層邏輯( Java層有混淆,jeb能忽略部份混淆,方便分析 ),發現調用check函數來檢查,參考分別是UID和Flag。

check是Native函數。

vm初始化

native層的check是靜態注冊的,能直接搜到。

繼續深入分析( 配合動調來遂一分析每個函數的作用 )。

init_some_data函數如下,結合後面的分析可以知道,這裡是在初始化vm虛擬機的opcodes,存放在a1[0xC000 ~ 0xC200]

a1記為vm_ctx,意指vm虛擬機的上下文空間。

start_vm

初始化完成後便會調用start_vm正式啟動虛擬機進行計算。

一開始會通過一些運算獲取_opcodearg,前者是操作碼、後者是一些固定的參數( 在不同的操作碼中都有不同的含義 )。

接著就是vm最經典的一大段switch,每個case對應不同的handler,實現了不同的功能。

每個handler裡基本上都會用到vm_ctx[0x10002],一些參數、中間值、計算結果都會存放在vm_ctx[0x10002]指向的位置。

而且可以看到vm_ctx[0x10002] + 4vm_ctx[0x10002] - 4等等的運算,再結合題目的描述,可以猜測vm_ctx[0x10002]相當於sp( 棧指針 ),該虛擬機的所有運算操作都會在它自己維護的棧中進行( 沒有寄存器的概念 )。

vm handler分析與還原

大部份handler的實現都比較簡單,配合動調很容易就可以分析出來。

記錄幾個沒那麼容易看出來的handler。

handler7:&v26[-arg]相當於&v26 - arg,這裡是在將棧頂元素與棧頂後arg個元素交換。

handler22:注意_pc += (char)arg,對應匯編是ADD W11, W11, W12,SXTB,其中SXTB是對W12的修飾符,表示將W12的最低8位進行符號擴展,在還原handler時要特別留意這一點。

花億點時間,還原所有handler,實現一個簡單的vm解釋器:

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
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
def write_mem_str(addr, content):
    global vm_ctx
    if type(content) == str:
        for i in range(len(content)):
            vm_ctx[addr + i] = ord(content[i])
    else:
        raise Exception("TODO")
    return addr
 
def write_mem_word(addr, content):
    global vm_ctx
    for i in range(2):
        vm_ctx[addr + i] = content & 0xFF
        content >>= 8
 
def write_mem_arr(addr, arr):
    global vm_ctx
    for i in range(len(arr)):
        vm_ctx[addr + i] = arr[i]
 
def write_mem_dword(addr, content):
    global vm_ctx
    for i in range(4):
        vm_ctx[addr + i] = content & 0xFF
        content >>= 8
 
def read_mem_dword(addr):
    global vm_ctx
    return vm_ctx[addr] | (vm_ctx[addr + 1] << 8) | (vm_ctx[addr + 2] << 16) | (vm_ctx[addr + 3] << 24)
 
def read_mem_word(addr):
    global vm_ctx
    return vm_ctx[addr] | (vm_ctx[addr + 1] << 8)
 
def read_mem_byte(addr):
    global vm_ctx
    return vm_ctx[addr]
 
def push_data(data):
    global vm_ctx
    sp = read_mem_word(0x10002)
    tmp = sp + 4
    write_mem_word(0x10002, tmp)
    write_mem_dword(tmp, data)
 
def pop_data():
    global vm_ctx
    sp = read_mem_word(0x10002)
    data = read_mem_dword(sp)
    write_mem_word(0x10002, sp - 4)
    return data
 
def read_sp_data():
    sp = read_mem_word(0x10002)
    data = read_mem_dword(sp)
    return data
 
def set_sp_data(data):
    sp = read_mem_word(0x10002)
    write_mem_dword(sp, data)
 
def load_opcodes():
    global vm_ctx
    with open("./dump/opcodes", mode = "rb") as f:
        opcodes = bytearray(f.read())
        for i in range(len(opcodes)):
            vm_ctx[0xC000 + i] = opcodes[i]
 
def hex_to_negative(value, bits = 8):
    # 檢查符號位
    if value & (1 << (bits - 1)):
        # 如果是負數,計算其補碼
        value = value - (1 << bits)
    return value
 
def start_vm():
    global vm_ctx, pc, arg, v13
 
    pc = None
    arg = None
    v13 = None
 
    def handler_0_xor():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n1 ^ n2
        set_sp_data(res)
        print(f"[h0_xor]\t pop, *sp = {hex(n2)} ^ {hex(n1)} = {hex(res)}")
 
    def handler_1_opposite():
        n = read_sp_data()
        set_sp_data(-n)
        print(f"[h1_opposite]\t *sp = -{hex(n)}")
     
    def handler_2_subsp():
        sp = read_mem_word(0x10002)
        write_mem_word(0x10002, sp - 4 * arg)
        print(f"[h2_subsp]\t sp -= {4 * arg}")
     
    def handler_4_orr():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n1 | n2
        set_sp_data(res)
        print(f"[h4_orr]\t pop, *sp = {hex(n2)} | {hex(n1)} = {hex(res)}")
     
    def handler_5_(): # nglog: maybe some problem
        global pc
        sp = read_mem_word(0x10002)
        v23 = read_sp_data()
        v24 = sp - 8 - 4 * arg + 4
        pc = read_mem_dword(sp - 4)
        write_mem_word(0x10002, v24)
        write_mem_dword(v24, v23)
        print(f"[h5_]\t sp = {hex(v24)}, [{hex(v24)}] = {hex(v23)}, pc = {hex(pc)}")
     
    def handler_6_noeq(): # nglog
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n1 != n2
        set_sp_data(res)
        print(f"[h6_noeq]\t pop, *sp = {hex(n2)} != {hex(n1)} = {hex(res)}")
     
    def handler_7_swap(): # nglog: some problem
        global arg
        sp = read_mem_word(0x10002)
        n1 = read_mem_dword(sp)     # sp
        n2 = read_mem_dword(sp - 4 * arg) # sp - arg
 
        write_mem_dword(sp, n2)
        write_mem_dword(sp - 4 * arg, n1)
 
        print(f"[h7_swap]\t swap(sp, sp - {arg}) -> swap({hex(n1), hex(n2)})")
     
    def handler_8_and():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n1 & n2
        set_sp_data(res)
        print(f"[h8_and]\t pop, *sp = {hex(n2)} & {hex(n1)} = {hex(res)}")
     
    def handler_9_lsl():
        sp_data = read_sp_data()
        set_sp_data(sp_data << arg)
        print(f"[h9_lsl]\t *sp = *sp << arg = {hex(sp_data)} << {arg} = {hex(sp_data << arg)}")
 
    def handler_10_not():
        sp_data = read_sp_data()
        set_sp_data(~sp_data)
        print(f"[h10_not]\t *sp = ~(*sp) = ~{hex(sp_data)} = {hex(~sp_data & 0xffffffff)}")
     
    def handler_12_add():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n1 + n2
        set_sp_data(res)
        print(f"[h12_add]\t pop, *sp = {hex(n2)} + {hex(n1)} = {hex(res)}")
    def handler_14_():
        global pc
        pc += hex_to_negative(arg)
        print(f"[h14_]\t pc += {hex_to_negative(arg)}")
    def handler_15_():
        write_mem_word(0x10004, 257)
        print("[h15_]\t write_mem_word(0x10004, 257)")
     
    def handler_17_lsr():
        sp_data = read_sp_data()
        set_sp_data(sp_data >> arg)
        print(f"[h17_lsr]\t *sp = *sp >> arg = {hex(sp_data)} >> {arg} = {hex(sp_data >> arg)}")
 
    def handler_18_mod():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n2 % n1
        set_sp_data(res)
        print(f"[h18_mod]\t pop, *sp = {hex(n2)} % {hex(n1)} = {hex(res)}")
     
    def handler_20_dword2byte():
        sp = read_mem_word(0x10002)
        sp_data = read_mem_byte(sp)
        set_sp_data(sp_data)
        print(f"[h20_dword2byte]\t *(dword*)sp = *(byte*)sp = {hex(sp_data)}")
 
    def handler_21_mul():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = n1 * n2
        set_sp_data(res)
        print(f"[h21_mul]\t pop, *sp = {hex(n2)} * {hex(n1)} = {hex(res)}")
 
    def handler_22_pushpc(): # nglog
        global pc
        sp = read_mem_word(0x10002)
        pc_ = pc
        pc += hex_to_negative(arg)
        v34 = sp + 4
        write_mem_word(0x10002, v34)
        write_mem_dword(v34, pc_)
        print(f"[h22_pushpc]\t push(pc) -> push({hex(pc_)}), pc += {hex_to_negative(arg)}")
     
    def handler_23_eq(): # nglog
        global pc
        sp = read_mem_word(0x10002)
        v16 = sp - 4
        v15 = sp - 8
        n1 = read_mem_dword(sp)
        n2 = read_mem_dword(sp - 4)
        write_mem_word(0x10002, v15)
        if (v13 == 25) == (n1 == n2):
            print(f"[h23_eq]\t sp = sp - 8")
            return
         
        if arg & 0xFFFFFF00 != 0:
            raise Exception("TODO")
 
        pc += hex_to_negative(arg)
        print(f"[h23_eq]\t sp = sp - 8, pc += {hex_to_negative(arg)} ({hex(arg)})")
 
    def handler_26_getinput():
        n1 = pop_data()     # *sp
        n2 = read_sp_data() # *(sp - 1)
        res = read_mem_byte(n1 + n2)
        set_sp_data(res)
        print(f"[h26_getinput]\t pop, *sp = vm_ctx[{hex(n2)} + {hex(n1)}] = {hex(res)}")
     
    def handler_27_pusharg():
        global arg
        sp = read_mem_word(0x10002)
        orig_arg = arg
        arg = read_mem_dword(sp - 4 * arg)
        push_data(arg)
        print(f"[h27_pusharg]\t push({hex(arg)})  arg == [sp - 4 * {orig_arg}]")
     
    def handler_29_pusharg2(): # nglog
        push_data(arg)
        print(f"[h29_pusharg2]\t push({hex(arg)})")
 
    def handler_30_sub1():
        sp_data = read_sp_data()
        set_sp_data(sp_data - 1)
        print(f"[h30_sub1]\t *sp = *sp - 1 = {hex(sp_data)} - 1 = {hex(sp_data - 1)}")
     
    pc = read_mem_word(0x10000)
     
    while True:
        pc_1 = pc + 1
        cur_opcode = read_mem_byte(pc)
        arg = cur_opcode & 7
        if arg != 7:
            pc += 1
            v13 = cur_opcode >> 3
            _opcode = v13 - 1
        else:
            pc += 2
            arg = read_mem_byte(pc_1)
            v13 = cur_opcode >> 3
            _opcode = v13 - 1
            if v13 - 1 > 0x1E:
                raise Exception("TODO")
                break
        if _opcode == 0:
            handler_0_xor()
        elif _opcode == 1:
            handler_1_opposite()
        elif _opcode == 2:
            handler_2_subsp()
        elif _opcode == 3 or _opcode == 25:
            continue
        elif _opcode == 4:
            handler_4_orr()
        elif _opcode == 5:
            handler_5_()
        elif _opcode == 6:
            handler_6_noeq()
        elif _opcode == 7:
            handler_7_swap()
        elif _opcode == 8:
            handler_8_and()
        elif _opcode == 9:
            handler_9_lsl()
        elif _opcode == 10:
            handler_10_not()
        elif _opcode == 12:
            handler_12_add()
        elif _opcode == 14:
            handler_14_()
        elif _opcode == 15:
            handler_15_()
            break
        elif _opcode == 17:
            handler_17_lsr()
        elif _opcode == 18:
            handler_18_mod()
        elif _opcode == 20:
            handler_20_dword2byte()
        elif _opcode == 21:
            handler_21_mul()
        elif _opcode == 22:
            handler_22_pushpc()
        elif _opcode == 23 or _opcode == 24:
            handler_23_eq()
        elif _opcode == 26:
            handler_26_getinput()
        elif _opcode == 27:
            handler_27_pusharg()
        elif _opcode == 29:
            handler_29_pusharg2()
        elif _opcode == 30:
            handler_30_sub1()
        else:
            print("else _opcode: ", _opcode)
            raise Exception("TODO")
            break
     
    write_mem_word(0x10000, pc)
    res = read_sp_data()
    return res
 
# init vm_ctx
vm_ctx = [0] * 0x10006
 
load_opcodes()
 
write_mem_dword(0x10000, 0x8000C000)
write_mem_word(0x10004, 0)
 
write_mem_arr(0x204 * 0x10, [0x00, 0x03, 0x0F, 0x20, 0x0D, 0x02, 0x23, 0x06, 0x1B, 0x14,0x0E, 0x01, 0x16, 0x19, 0x08, 0x12])
write_mem_arr(0x205 * 0x10, [0x1F, 0x17, 0x24, 0x0B, 0x1E, 0x07, 0x1A, 0x05, 0x18, 0x1D, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00])
write_mem_arr(0x203 * 0x10, [0x09, 0x0A, 0x10, 0x15, 0x21, 0x13, 0x0C, 0x04, 0x11, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
 
write_mem_str(0x1000, "flag{44444-44444-44444-44444}") # input flag
 
push_data(1898208) # uid
push_data(0x1000)
push_data(0x2000)
 
res = start_vm()
print("[res]: ", hex(res))

提醒:flag格式為flag{XXXXX-XXXXX-XXXXX-XXXXX},其中X要麼是大寫字母,要麼是數字。


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

收藏
免费 10
支持
分享
赞赏记录
参与人
雪币
留言
时间
云水泱泱
为你点赞!
2025-3-3 11:13
iamshy
+1
感谢你的积极参与,期待更多精彩内容!
2025-2-19 10:48
gal2xy
你的帖子非常有用,感谢分享!
2025-2-19 10:10
KXXTEST
感谢你的贡献,论坛因你而更加精彩!
2025-2-18 11:07
CIH_D39
+1
感谢你的积极参与,期待更多精彩内容!
2025-2-17 18:14
jiyuren
谢谢你的细致分析,受益匪浅!
2025-2-14 14:11
cxbcxb
这个讨论对我很有帮助,谢谢!
2025-2-13 20:48
TubituX
你的帖子非常有用,感谢分享!
2025-2-13 15:18
Shangwendada
+3
期待更多优质内容的分享,论坛有你更精彩!
2025-2-13 11:33
.KK
期待更多优质内容的分享,论坛有你更精彩!
2025-2-13 10:13
最新回复 (4)
雪    币: 475
活跃值: (640)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
强!
2025-2-13 10:18
0
雪    币: 3811
活跃值: (3393)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
很强,但是不懂!
2025-2-15 09:34
0
雪    币: 2435
活跃值: (2099)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4
jiaoxiake 很强,但是不懂!
2025-2-15 11:30
0
雪    币: 48
活跃值: (2248)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
大佬太强了
2025-2-17 21:50
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册