-
-
[原创]2023年腾讯游戏安全竞赛安卓决赛题解复现
-
发表于: 2025-8-6 21:28 874
-
2023年腾讯游戏安全竞赛安卓决赛题解复现
flag获取
和初赛一样,还是hook同样的代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | let so_name = "libil2cpp.so"let bases = Module.findBaseAddress(so_name);let module = Process.getModuleByName(so_name);let base_addr = module.baselet base_size = module.sizeconsole.log("[*] base", bases);function hook_coin() { var address = bases.add(0x466A48); // console.log(Memory.readInt(address)) Interceptor.attach(address, { onEnter: function (args) { }, onLeave: function (retval) { retval.replace(1000) console.log('ret', retval) } })} |
libil2cpp.so分析:
通过hook一下小键盘的函数,我们可以发现他的算法不在libil2cpp这个so里面,而是在libsec2023.so里面,下面是对libil2cpp.so的分析,就是一些
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 | let so_name = "libil2cpp.so"let bases = Module.findBaseAddress(so_name);let module = Process.getModuleByName(so_name);let base_addr = module.baselet base_size = module.size//hook小键盘测试,不能直接读取String得值,因为没有String这个类型,他只是一个类型罢了/*struct Il2CppString { void* klass; // 指向类元数据的指针 void* monitor; // 用于线程同步 int32_t length; // 字符串长度(字符数) uint16_t chars[0]; // 字符数组(UTF-16,每个字符占2字节)};*///写一个读取String得函数function getStringValue(addr) { var length = ptr(addr).add(0x10).readU32() var str_real_addr = ptr(addr).add(0x14) var string_val = str_real_addr.readUtf16String(length) return string_val}//其中下面我们要hook的一个函数的参数是结构体,需要解析一下/*public class SmallKeyboard.iII1i // TypeDefIndex: 3311{ // Fields public GameObject KeyObj; // 0x10 public SmallKeyboard.KeyboardType KeyType; // 0x18 public string SValue; // 0x20}*/function parseiII1i(addr) { var types = ptr(addr).add(0x18).readPointer() var iII1i_addr = ptr(addr).add(0x20).readPointer() var iII1i_value = getStringValue(iII1i_addr) return iII1i_value}function hook_smallKeyboard() { var hooklist = [0x465760, 0x465C8C, 0x465990, 0x465B40, 0x465D98] //分析后得到函数一是对输入中的数据进行处理,每次点击数字或者del就会调用 //函数二是组件判断,应该就是组件初始化 //函数三是我们点击ok,就输出我们输入的值 //函数四是生成一个7位的随机数,生成的随机数就是token //函数五应该就是一个赋值操作 for (var i = 0; i < hooklist.length; i++) { var addr = hooklist[i]; const idx = i; Interceptor.attach(bases.add(addr), { onEnter: function (args) { if (idx === 0) { console.log("==========第一个函数开始调用==============") var SmallKeyboardthis = args[0] var strInput = SmallKeyboardthis.add(0x18).readPointer() var oO0o0o0 = SmallKeyboardthis.add(0x38).readPointer() var iIIIi = SmallKeyboardthis.add(0x40).readPointer() var str_strInpt = getStringValue(strInput) var str_oO0o0o0 = getStringValue(oO0o0o0) var str_iIIIi = getStringValue(iIIIi) console.log("str_strInpt", str_strInpt)//没有回显 console.log("str_oO0o0o0", str_oO0o0o0)//已经生成的token console.log("str_iIIIi", str_iIIIi)//按下的字符 } else if (idx == 2) { console.log("==========第三个函数开始调用==============") console.log(args[1]) //这个值就是我们输出的最后一个值 } }, onLeave: function (retval) { if (idx == 2) { console.log('RegisterNatives called from:\\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n'); console.log("==", retval) } } }) }} |
然后我们去看这些小键盘的函数,尤其是第三个,0x465990,毕竟他是涉及ok的,然后就发现他的跳转了

代码如下:
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 | function hook_addr_in_so(addr) { var process_Obj_Module_Arr = Process.enumerateModules(); for (var i = 0; i < process_Obj_Module_Arr.length; i++) { if (addr > process_Obj_Module_Arr[i].base && addr < process_Obj_Module_Arr[i].base.add(process_Obj_Module_Arr[i].size)) { console.log(addr.toString(16), "is in", process_Obj_Module_Arr[i].name, "offset: 0x" + (addr - process_Obj_Module_Arr[i].base).toString(16)); } }}function enc_jump() { const baseAddr = Module.findBaseAddress('libil2cpp.so') var offest = 0x13BCDD4 const func_inp = bases.add(offest) Interceptor.attach(func_inp, { onEnter: function (args) { hook_addr_in_so(ptr(this.context.x2)) }, onLeave: function (retval) { //console.log('RegisterNatives called from:\\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(hook_addr_in_so).join('\\n') + '\\n'); // console.log(retval) // console.log('====', this.context.x2) // var buf = Memory.readByteArray(ptr(retval), 0x10); // console.log(hexdump(buf, { // offset: 0, // length: 0x10, // header: true, // ansi: true // })); }, })} |

到此,我们对libil2cpp.so的分析结束
libsec2023.so分析:

我们发现跳转到这里,但是当我们hook这个so的函数的时候,他就输出被检测出来了
反调试分析:
说明有对frida的检测,我们经过测试是crc检测,因为crc需要不断的扫内存,我们直接使用stackplz随便对一个地址下个硬件断点就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 2|oriole:/data/local/tmp # ./stackplz --pid 22724 --brk 0x744A280CB8:r --stack[*] save maps to maps_22724.txtset breakpoint at kernel:false, addr:0x744a280cb8, type:1start 1 modules[22724|22787] event_addr:0x744a280cb8 hit_count:1, Backtrace: #00 pc 000000000007d934 /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #01 pc 000000000007b2fc /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #02 pc 0000000000075664 /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #03 pc 0000000000074f5c /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #04 pc 0000000000051c50 /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #05 pc 00000000000c226c /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204) #06 pc 0000000000054a30 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)[22724|22787] event_addr:0x744a280cb8 hit_count:2, Backtrace: #00 pc 000000000007d934 /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #01 pc 000000000007b2fc /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #02 pc 0000000000075664 /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #03 pc 0000000000074f5c /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #04 pc 0000000000051c50 /data/app/~~aQKQSDle_3eICEO3gsHPRw==/com.com.sec2023.rocketmouse.mouse-8sttfhilCgPLUR963AOOgQ==/lib/arm64/libsec2023.so #05 pc 00000000000c226c /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204) #06 pc 0000000000054a30 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) |
也就是这几个地址:7d934 7b2fc 75664 74f5c 51c50
看汇编也估计就是从7b2fc这个地址开始变化,往下面跟就行了,就是在下面的cmp比较那,你可以多次hook这个地方看看哪些值不变就修改哪里

我trace了2次,两次不一样的值,直接强制改为相同的值就行,可以改汇编可以改寄存器,修正完就解决了
下面是anti脚本,借鉴我师哥的想法直接hook sleep函数,挺完美的
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 | var soname = 'libsec2023.so';var startaddr = 0x7B300;var endaddr = 0x7B300 + 0x1000var size = endaddr - startaddr;let so_name = "libsec2023.so"let bases = Module.findBaseAddress(so_name);let module = Process.getModuleByName(so_name);let base_addr = module.baselet base_size = module.sizefunction anti_check1() { var addr = base_addr.add(0x7b320) Memory.protect(addr, 4, 'rwx'); addr.writeInt(0x2a0803e0)}function anti_check2() { var addr = base_addr.add(0x7b320) Interceptor.attach(addr, { onEnter: function (args) { this.context.x0 = 0x696a4761 } })}var cnt = 0function anti_check3() { Interceptor.attach(Module.findExportByName(null, "sleep"), { onEnter: function (args) { cnt += 1 if (cnt == 2) { console.log("hook sleep finish") } console.log(`sleep ${args[0]}s`) var thread = Process.getCurrentThreadId() console.log(`the threadid in sleep is: ${thread}`) args[0] = ptr(0x100000) } })}anti_check3() |
继续回到正文,我们过掉反调试以后,就去看他哪里进行了加密

混淆处理:
BR跳转混淆
看汇编知道有许多的br跳转,这种br跳转去除很简单那们就是将br x8修改为 BL xxaddr

有的是已经ida给我们静态计算好的,也就不需要我们再进行trace去找了,对于ida无法静态计算的,我们可以通过stalker去trace 然后手动patch来恢复符号

类似这种

手动patch就行

这是简单patch后的,部分符号我已经修改了
不透明谓词混淆

类似于这种,但是我们可以点进去发现他是有值的,就必须手动将其类型修改,然后ida才能识别并计算出来

ida统一修改如下:
idapthon参看105K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3P5i4c8Z5L8$3&6Q4x3X3g2V1L8$3y4K6i4K6u0W2K9r3g2^5i4K6u0V1M7X3q4&6M7#2)9J5k6h3y4G2L8g2)9J5c8X3&6S2L8h3g2K6M7r3q4U0k6h3W2V1j5g2)9#2k6W2)9#2k6X3W2V1j5h3q4H3K9g2)9J5k6h3S2@1L8h3H3`.
1 2 3 4 5 6 7 8 9 10 11 | # 1、修改变量的参数类型from idc import *def change_type(addr,new_type): SetType(addr,new_type)start_addr=0x1270d0end_addr=0x13d9a0for addr in range(start_addr,end_addr,8): new_type="const unsigned __int64" change_type(addr,new_type)print("finish") |

CSEL+BR混淆
但是看一下内部函数


还是看不了具体的东西,我最开始是直接在内部trace,然后手动在一些br跳转处进行处理的
trace代码:
frida_trace
效果只能说能看,但是免不了有一些函数还是不行

不过第一次而言,对于我分析第一次加密足够了,就是大量的hook+trace分析,下面再说具体的算法,然后就参考了师哥的去混淆代码,正好学习一下unidbg的去混淆代码该如何书写
首先贴一下要去除的格式:
CMP W8, W27 CSEL X9, X22, X21, LT LDR X9, [X19,X9] ADD X9, X9, X26 BR X9
拿上面这个举例,这个就是记录下来 x22 x21的值,然后去动态计算x9的值,肯定会得到两种x9的值,然后将后四句改为
cmp xx xx B.XX true_addr B false_addr NOP NOP
这里的关键就是如何去计算这些寄存器的值,关键就是两个问题?
1.trace还是hook,hook能不能得到两种情况的值?
2.能不能通过trace,在trace的情况下,通过往上翻代码(回溯寄存器),能不能计算出来这个寄存器值
所以我们可以通过hook的情况来解决这个问题
下面贴一下代码
首先先用ida拿到不能正常看的地址,也就是混淆函数的筛选
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 | import idcimport idautilsimport idaapifunc_start=[]def ida_decode(begin,end): global func_start All_func=list(idautils.Functions(begin,end))#获取所有函数的首地址 for i in range(len(All_func)): func=idaapi.get_func(All_func[i])#拿到该函数的操作权 cfunc=str(idaapi.decompile(func.start_ea))#对这个函数进行反编译 if "__asm { BR" in cfunc and "_ReadStatusReg" in cfunc: func_start.append(func.start_ea)begin=0x4f1d0end=0x10526cida_decode(begin,end)for i in func_start: print(hex(i),end=",")#下面是结尾函数func_end=[]for i in range(len(func_start)): Asm_data="" ea=func_start[i] while True: asm_code=str(hex(ea))+": "+idc.generate_disasm_line(ea,0)+"\n" now_ea=ea ea=idc.next_head(ea) if "RET" in asm_code: asm_next_code=str(hex(ea))+": "+idc.generate_disasm_line(ea,0)+"\n" if ".__stack_chk_fail" in asm_next_code: func_end.append(ea) else: func_end.append(now_ea) breakprint(len(func_end))for i in (func_end): print(hex(i),end=",") |

上面会得到已经反编译出来的所有不能正常看的函数的值,下面就是编写unidbg代码来hook这些函数,从而计算这些if跳转的地址
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 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | package com.tengxun2023;import capstone.Capstone;import capstone.api.Instruction;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.arm.backend.Backend;import com.github.unidbg.arm.backend.CodeHook;import com.github.unidbg.arm.backend.UnHook;import com.github.unidbg.debugger.DebuggerType;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.DalvikModule;import com.github.unidbg.linux.android.dvm.DvmClass;import com.github.unidbg.linux.android.dvm.VM;import com.github.unidbg.memory.Memory;import com.github.unidbg.virtualmodule.android.AndroidModule;import com.github.unidbg.Emulator;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.debugger.BreakPointCallback;import com.github.unidbg.debugger.DebuggerType;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.*;import com.github.unidbg.memory.Memory;import com.github.unidbg.virtualmodule.android.AndroidModule;import java.io.*;import com.github.unidbg.arm.context.Arm64RegisterContext;import keystone.Keystone;import keystone.KeystoneArchitecture;import keystone.KeystoneEncoded;import keystone.KeystoneMode;import unicorn.Arm64Const;import javax.swing.*;import java.io.File;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.util.Arrays;import java.util.HashMap;import java.util.HashSet;import java.util.Set;public class fin_2023 { public final AndroidEmulator emulator; public final VM vm; public final Memory memory; public final Module module; DvmClass cNative; //基本的环境配置 public fin_2023(){ emulator = AndroidEmulatorBuilder.for64Bit().build(); memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); emulator.getSyscallHandler().setEnableThreadDispatcher(true); vm = emulator.createDalvikVM(); new AndroidModule(emulator,vm).register(memory); DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/tengxun2023/libsec2023.so"), true); module = dalvikModule.getModule(); vm.callJNI_OnLoad(emulator,module); } public static void padding_reg2val(HashMap<String,Integer> reg2val){ reg2val.put("x0", Arm64Const.UC_ARM64_REG_X0); reg2val.put("x1",Arm64Const.UC_ARM64_REG_X1); reg2val.put("x2",Arm64Const.UC_ARM64_REG_X2); reg2val.put("x3",Arm64Const.UC_ARM64_REG_X3); reg2val.put("x4",Arm64Const.UC_ARM64_REG_X4); reg2val.put("x5",Arm64Const.UC_ARM64_REG_X5); reg2val.put("x6",Arm64Const.UC_ARM64_REG_X6); reg2val.put("x7",Arm64Const.UC_ARM64_REG_X7); reg2val.put("x8",Arm64Const.UC_ARM64_REG_X8); reg2val.put("x9",Arm64Const.UC_ARM64_REG_X9); reg2val.put("x10",Arm64Const.UC_ARM64_REG_X10); reg2val.put("x11",Arm64Const.UC_ARM64_REG_X11); reg2val.put("x12",Arm64Const.UC_ARM64_REG_X12); reg2val.put("x13",Arm64Const.UC_ARM64_REG_X13); reg2val.put("x14",Arm64Const.UC_ARM64_REG_X14); reg2val.put("x15",Arm64Const.UC_ARM64_REG_X15); reg2val.put("x16",Arm64Const.UC_ARM64_REG_X16); reg2val.put("x17",Arm64Const.UC_ARM64_REG_X17); reg2val.put("x18",Arm64Const.UC_ARM64_REG_X18); reg2val.put("x19",Arm64Const.UC_ARM64_REG_X19); reg2val.put("x20",Arm64Const.UC_ARM64_REG_X20); reg2val.put("x21",Arm64Const.UC_ARM64_REG_X21); reg2val.put("x22",Arm64Const.UC_ARM64_REG_X22); reg2val.put("x23",Arm64Const.UC_ARM64_REG_X23); reg2val.put("x24",Arm64Const.UC_ARM64_REG_X24); reg2val.put("x25",Arm64Const.UC_ARM64_REG_X25); reg2val.put("x26",Arm64Const.UC_ARM64_REG_X26); reg2val.put("x27",Arm64Const.UC_ARM64_REG_X27); reg2val.put("x28",Arm64Const.UC_ARM64_REG_X28); reg2val.put("x29",Arm64Const.UC_ARM64_REG_FP); reg2val.put("x30",Arm64Const.UC_ARM64_REG_LR); reg2val.put("sp",Arm64Const.UC_ARM64_REG_SP); reg2val.put("pc",Arm64Const.UC_ARM64_REG_PC); } //将数组转换为long型 public static long byteArrayToLong(byte[] byteArray){ ByteBuffer buffer=ByteBuffer.wrap(byteArray); buffer.order(ByteOrder.LITTLE_ENDIAN); return buffer.getLong(); } //读取寄存器的值 public long read_reg_by_str(Backend backend,String reg){ long true_reg_val=-1; if(reg2val.containsKey(reg)){ true_reg_val=backend.reg_read(reg2val.get(reg)).longValue(); } else if(reg.equals("xzr")){ true_reg_val=0; }else{ assert false; } return true_reg_val; } public HashMap<String,Integer> reg2val=new HashMap<>(); public Set<Long> patch_addr_set=new HashSet<>(); public HashMap<String,Integer> patch_addr2bytes=new HashMap<>(); //计算跳转指令的数量 public void anti_br_by_static(Backend backend,long begin,long end){ try{ //获取区域内所有的汇编指令 Capstone capstone=new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_LITTLE_ENDIAN); byte[] bytes=backend.mem_read(begin+ module.base,end-begin); Instruction[] disasm=capstone.disasm(bytes,0); HashMap<Long,byte[]>reg2val =new HashMap<>(); for(int i=0;i<disasm.length;i++){ if("csel".equals(disasm[i].getMnemonic()) && "ldr".equals(disasm[i+1].getMnemonic()) && "add".equals(disasm[i+2].getMnemonic()) && "br".equals(disasm[i+3].getMnemonic())){ String ins1_reg[] =disasm[i].getOpStr().split(","); String ins1_reg1_name=ins1_reg[0].trim(); String ins1_reg2_name=ins1_reg[1].trim(); String ins1_reg3_name=ins1_reg[2].trim(); String ins1_op_name=ins1_reg[3].trim(); String ins2_regs[]=disasm[i+1].getOpStr().split(","); String ins3_regs[]=disasm[i+2].getOpStr().split(","); String ins2_reg1_name=ins2_regs[0].replaceAll("[\\s\\[]",""); String ins2_reg2_name=ins2_regs[1].replaceAll("[\\s\\[]",""); String ins2_reg3_name=ins2_regs[2].replaceAll("[\\s\\[]",""); String ins3_reg1_name=ins3_regs[0].replaceAll("[\\s\\[]",""); String ins3_reg2_name=ins3_regs[1].replaceAll("[\\s\\[]",""); String ins3_reg3_name=ins3_regs[2].replaceAll("[\\s\\[]",""); long true_op_reg_val=-1; long false_op_reg_val=-1; Boolean true_tag=false; Boolean false_tag=false; //接下来是指令回溯,往上翻代码来静态计算 try{ for(int j=i-1;j>=i-4;j--){ String spl_regs[]=disasm[j].getOpStr().split(","); String src_reg = spl_regs[0].replace('w','x').trim(); if((disasm[j].getMnemonic().contains("mov") && disasm[j].getMnemonic().contains("movk")==false) && (src_reg.contains(ins1_reg2_name) || src_reg.contains(ins1_reg3_name))){ String ins_regs[]=disasm[j].getOpStr().split(","); if(src_reg.contains(ins1_reg2_name)){ if(true_tag){ continue; } String true_op_reg_val_str=ins_regs[1].replaceAll("[\\s\\[#]",""); true_op_reg_val=Long.parseLong(true_op_reg_val_str.substring(2),16); true_tag=true; } else if(src_reg.contains(ins1_reg3_name)){ if(false_tag){ continue; } String false_op_reg_val_str=ins_regs[1].replaceAll("[\\s\\[#]",""); false_op_reg_val=Long.parseLong(false_op_reg_val_str.substring(2),16); false_tag=true; } } } }catch(Exception e){ System.out.printf("%s\n",e); } if(ins1_reg2_name.contains("xzr") || ins1_reg2_name.contains("wzr")){ true_op_reg_val=0; } if(ins1_reg3_name.contains("xzr") || ins1_reg3_name.contains("wzr")){ false_op_reg_val=0; } if(true_op_reg_val==-1){ true_op_reg_val=read_reg_by_str(backend,ins1_reg2_name); } if(false_op_reg_val==-1){ false_op_reg_val=read_reg_by_str(backend,ins1_reg3_name); } long base_reg_val=read_reg_by_str(backend,ins2_reg2_name); long add_reg_val=read_reg_by_str(backend,ins3_reg3_name); byte[] true_jmp_addr_byte=backend.mem_read(base_reg_val+true_op_reg_val,8); long true_jmp_addr=byteArrayToLong(true_jmp_addr_byte); byte[] fals_jmp_addr_byte = backend.mem_read(base_reg_val + false_op_reg_val, 8); long fals_jmp_addr = byteArrayToLong(fals_jmp_addr_byte); long true_fin_jmp = true_jmp_addr +add_reg_val; long fals_fin_jmp = fals_jmp_addr + add_reg_val; String new_asm1=String.format("B.%s\t0x%x",ins1_op_name,true_fin_jmp); String new_asm2=String.format("B\t0x%x",fals_fin_jmp); Keystone keyStone=new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian); KeystoneEncoded assemble1 =keyStone.assemble(new_asm1,(int) begin+4*i); byte[] machineCode1=assemble1.getMachineCode(); KeystoneEncoded assemble2 =keyStone.assemble(new_asm2,(int) begin+4*i+4); byte[] machineCode2=assemble2.getMachineCode(); KeystoneEncoded assemble3=keyStone.assemble("nop"); byte[] machineCode3=assemble3.getMachineCode(); byte[] final_patch=new byte[16]; System.arraycopy(machineCode1, 0, final_patch, 0, 4); System.arraycopy(machineCode2, 0, final_patch, 4, 4); System.arraycopy(machineCode3, 0, final_patch, 8, 4); System.arraycopy(machineCode3, 0, final_patch, 12, 4); reg2val.put(begin + 4 * i,final_patch); } } System.out.printf("begin,end=0x%x,0x%x\n",begin,end); System.out.printf("patcher={}\n"); for(Long address:reg2val.keySet()){ byte[] patch_bytes=reg2val.get(address); System.out.printf("patcher[0x%x] =%s\n",address, Arrays.toString(patch_bytes)); } System.out.printf("patcher()\n"); System.out.printf("\n\n"); }catch (Exception ee){ System.out.printf("==>%s\n",ee); } } public void hook_and_anti(long begin_addr,long end_addr){ int arr[]={0}; emulator.getBackend().hook_add_new(new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { int[] newArr=(int[]) user; if(newArr[0]==1){ return; } Capstone capstone=new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_ARM); byte[] bytes=emulator.getBackend().mem_read(address,4); Instruction[] disasm=capstone.disasm(bytes,0); if("csel".equals(disasm[0].getMnemonic())){ anti_br_by_static(backend,begin_addr,end_addr); newArr[0]=1; } } @Override public void onAttach(UnHook unHook) { } @Override public void detach() { } }, module.base+begin_addr,module.base+end_addr,arr); } public class res_two_regs{ public String reg1_name; public String reg2_name; public String if_op; public long reg1_val; public long reg2_val; public res_two_regs(String reg1_name,String reg2_name,long reg1_val,long reg2_val){ this.reg1_name = reg1_name; this.reg2_name = reg2_name; this.reg1_val = reg1_val; this.reg2_val = reg2_val; this.if_op =if_op; } } //强行绕过第一部分的检查 public void set_hook(){ emulator.getBackend().hook_add_new(new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { if (address==0x94440+module.base){ backend.reg_write(Arm64Const.UC_ARM64_REG_X0,0); } } @Override public void onAttach(UnHook unHook) { } @Override public void detach() { } },0x9443c+ module.base,0x94444+ module.base,null); } public void anti_confuse(){ padding_reg2val(reg2val); // 这里记录了要去混淆的函数收尾地址,需要注意,这个尾地址,用func.end_ea,idapython得到的可能不对 long br_addr[][] = { {0xe2db4,0x0000000000E39FC}, {0xe227c,0x0000000000E2DB0}, {0xcf004,0x0000000000CFAC0}, {0xd2ad0,0x0000000000D31D0}, {0xd6158,0x0000000000D6DF4}, {0xd6e00,0x0000000000D7AC4}, {0xd1cc0,0x0000000000D241C}, {0xe6bf4,0x0000000000E76D8}, {0xe3e18,0x0000000000E497C}, {0xe4c50,0x0000000000E58A4}, {0xe0be8,0x0000000000E1144}, {0xd0b2c,0x0000000000D10A0}, {0xe8054,0x0000000000E8854}, {0xd7f4c,0x0000000000D8B60}, {0xd5518,0x0000000000D5A74}, {0x00000000000EB408,0x0000000000EB8BC} }; set_hook(); for (int i = 0; i < br_addr.length; i++) { long begin = br_addr[i][0]; long end = br_addr[i][1]; hook_and_anti(begin,end); } module.callFunction(emulator,0x000000000094368,114514); } public static void main(String[] args){ fin_2023 mainActivity = new fin_2023(); mainActivity.anti_confuse(); }} |
不过上面的代码是没有自动化patch的,还需要配合idapython进行patch
下面是idapython的代码:
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 | import idcimport idaapidef patchers(): global patcher,begin,end for address in patcher: patch_code=patcher[address] print(patch_code) for addr in range(address,address+16): idc.patch_byte(addr,patch_code[addr-address]) print("finsh") def upc(begin,end): for i in range(begin,end): idc.del_items(i) for i in range(begin,end): idc.create_insn(i) idaapi.add_func(begin,end) print("upc finish") upc(begin,end) begin,end = 0xe4c50,0xe58a4patcher = {} patcher[0xe4d8c] = [32, 87, 0, 84, 3, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4e4c] = [-117, 0, 0, 84, 77, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4fc8] = [-128, 0, 0, 84, 116, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5188] = [-128, 0, 0, 84, 4, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4e84] = [-117, 0, 0, 84, -48, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe53c4] = [-128, 0, 0, 84, 117, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5544] = [-128, 0, 0, 84, 21, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5784] = [-128, 0, 0, 84, -123, -3, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5100] = [-117, 0, 0, 84, -26, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5140] = [-117, 0, 0, 84, -64, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe515c] = [-128, 0, 0, 84, 15, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe565c] = [-128, 0, 0, 84, -49, -3, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4dd8] = [-117, 0, 0, 84, -118, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4e18] = [-128, 0, 0, 84, -32, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5098] = [-128, 0, 0, 84, 64, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe52d8] = [-128, 0, 0, 84, -80, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5418] = [-128, 0, 0, 84, 96, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe51d4] = [-128, 0, 0, 84, -15, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4f10] = [-117, 0, 0, 84, -25, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4f90] = [-117, 0, 0, 84, 104, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5010] = [-117, 0, 0, 84, -30, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5310] = [-117, 0, 0, 84, -119, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5450] = [-117, 0, 0, 84, -55, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5750] = [-128, 0, 0, 84, -110, -3, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4f2c] = [-128, 0, 0, 84, -101, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4fac] = [-117, 0, 0, 84, -43, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe502c] = [-128, 0, 0, 84, 91, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe532c] = [-128, 0, 0, 84, -101, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe546c] = [-128, 0, 0, 84, 75, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4e68] = [-117, 0, 0, 84, -126, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe53a8] = [-117, 0, 0, 84, -87, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe54a8] = [-128, 0, 0, 84, 60, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe50e4] = [-21, -28, -1, 84, 3, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe5124] = [96, 14, 0, 84, 29, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4da0] = [-117, 0, 0, 84, 41, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4ea0] = [-128, 0, 0, 84, -66, -1, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4dbc] = [-117, 0, 0, 84, 74, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe507c] = [-117, 0, 0, 84, -36, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe52bc] = [-117, 0, 0, 84, -116, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe53fc] = [-117, 0, 0, 84, -47, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe54fc] = [-128, 0, 0, 84, 39, -2, -1, 23, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4df4] = [-117, 0, 0, 84, -31, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patcher[0xe4ef4] = [-117, 0, 0, 84, 118, 0, 0, 20, 31, 32, 3, -43, 31, 32, 3, -43]patchers() |
这样可以恢复个七七八八,不过我再搜索的时候发现了unidbg可以自己patcher,学习一下,代码如下:
下面是最终版代码:
(一定要限制范围,不要patch全部的,太容易乱了)
final:
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 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 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 | package com.tengxun2023;import capstone.Capstone;import capstone.api.Instruction;import com.alibaba.fastjson.util.IOUtils;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.arm.backend.Backend;import com.github.unidbg.arm.backend.CodeHook;import com.github.unidbg.arm.backend.UnHook;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.DalvikModule;import com.github.unidbg.linux.android.dvm.DvmClass;import com.github.unidbg.linux.android.dvm.VM;import com.github.unidbg.memory.Memory;import com.github.unidbg.virtualmodule.android.AndroidModule;import keystone.Keystone;import keystone.KeystoneArchitecture;import keystone.KeystoneEncoded;import keystone.KeystoneMode;import unicorn.Arm64Const;import javax.sound.midi.Patch;import java.io.File;import java.io.FileInputStream;import java.nio.file.Files;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.*;import java.util.List;import java.util.Stack;//我要在这个里面写一个不同类型的混淆的去除手段public class anti { //初始化环境 private List<PatchBR> patch_list; private Deque<InsAndCtx> instructions; public final AndroidEmulator emulator; public final VM vm; public final Memory memory; public final Module module; private Set<Long> executedAddresses = new HashSet<>(); public String inname="unidbg-android/src/test/java/com/tengxun2023/input.so"; public String outname="unidbg-android/src/test/java/com/tengxun2023/output.so"; DvmClass cNative; public anti(){ instructions = new ArrayDeque<>(); patch_list = new ArrayList<>(); emulator = AndroidEmulatorBuilder.for64Bit().build(); memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); emulator.getSyscallHandler().setEnableThreadDispatcher(true); vm = emulator.createDalvikVM(); new AndroidModule(emulator,vm).register(memory); DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/tengxun2023/libsec2023.so"), true); module = dalvikModule.getModule(); long br_addr[][] = { {0xe2db4,0x0000000000E39FC}, {0xe227c,0x0000000000E2DB0}, {0xcf004,0x0000000000CFAC0}, {0xd2ad0,0x0000000000D31D0}, {0xd6158,0x0000000000D6DF4}, {0xd6e00,0x0000000000D7AC4}, {0xd1cc0,0x0000000000D241C}, {0xe6bf4,0x0000000000E76D8}, {0xe3e18,0x0000000000E497C}, {0xe4c50,0x0000000000E58A4}, {0xe0be8,0x0000000000E1144}, {0xd0b2c,0x0000000000D10A0}, {0xe8054,0x0000000000E8854}, {0xd7f4c,0x0000000000D8B60}, {0xd5518,0x0000000000D5A74}, {0xEB408,0x0000000000EB8BC}, {0xE4C50,0xE58A0} }; set_hook(); for (int i = 0; i < br_addr.length; i++) { long begin = br_addr[i][0]; long end = br_addr[i][1]; save_code(begin,end); } //vm.callJNI_OnLoad(emulator,module); module.callFunction(emulator,0x000000000094368,114514); } public static void main(String[] args){ anti anti_ob=new anti(); anti_ob.destory(); } //首先先定义一些类 //保存指令和寄存器类 class InsAndCtx{ long addr; Instruction ins; List<Number> regs; public long getAddr(){ return addr; } public void setAddr(long addr){ this.addr=addr; } public void setIns(Instruction ins){ this.ins=ins; } public Instruction getIns(){ return ins; } public void setRegs(List<Number> regs){ this.regs=regs; } public List<Number> getRegs(){ return regs; } } //patch类 class PatchIns{ long addr;//patch的地址 String ins;//patch的指令 public long getAddr(){ return addr; } public void setAddr(long addr){ this.addr=addr; } public String getIns(){ return ins; } public void setIns(String ins){ this.ins=ins; } } //混淆的类型 enum br_type{ direct_br,direct_blr,indirect_br; } enum error_type{ ok,conflict_br_type,conflict_b_jump_address } class PatchBR{ List<PatchIns>patchs; List<Long> br_jump_to; long br_addr; br_type type; error_type errorno; String error_info; PatchBR(){ //List<PatchIns> patchs = new ArrayList<>(); patchs =new ArrayList<>(); br_jump_to=new ArrayList<>(); errorno=error_type.ok; } } //指令栈// private Stack<Instruction> instructions; //所有需要patch的指令 private List<PatchIns> patchs; //保存指令寄存器环境 public List<Number> saveRegs(Backend backend){ List<Number> regs=new ArrayList<>(); for(int i=0;i<29;i++){ regs.add(backend.reg_read(i+ Arm64Const.UC_ARM64_REG_X0)); } regs.add(backend.reg_read(Arm64Const.UC_ARM64_REG_FP)); regs.add(backend.reg_read(Arm64Const.UC_ARM64_REG_LR)); return regs; } //从寄存器中取值 private Number getRegValue(String reg, List<Number> saved) { reg = reg.toLowerCase(Locale.ROOT); if (reg.equals("xzr") || reg.equals("wzr")) return 0; boolean is32 = reg.startsWith("w"); int idx = Integer.parseInt(reg.substring(1)); long val = saved.get(idx).longValue(); return is32 ? (val & 0xFFFFFFFFL) : val; } //这里借鉴大佬设置一个错误处理 public boolean identify_br(PatchBR pb){ for(PatchBR _pb :patch_list){ if(pb.br_addr== _pb.br_addr){ if(pb.type!=_pb.type){ pb.errorno = error_type.conflict_br_type; pb.error_info = "[error]confict br jump type at 0x"+Integer.toHexString((int) pb.br_addr)+"type->"+pb.type+" "+_pb.type; } else if (pb.br_jump_to!=_pb.br_jump_to) { pb.errorno = error_type.conflict_br_type; pb.error_info = "[error]confict br jump address at 0x"+Integer.toHexString((int) pb.br_addr)+"type->"+pb.br_jump_to+" "+_pb.br_jump_to; } return false; } } return true; } //读取地址的字节 public long readInt64(Backend backend,long addr){ byte[] bytes=backend.mem_read(addr,8); long res=0; for (int i=0;i<bytes.length;i++){ res=((bytes[i]&0xffL)<<(8*i))+res; } return res; } //对每条环境都保存环境 public void save_code(long begin, long end) { emulator.getBackend().hook_add_new(new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { // 如果该地址已经执行过,直接跳过 if (executedAddresses.contains(address)) { return; // 跳过重复执行的地址 } // 标记地址已执行 executedAddresses.add(address); // 执行钩子逻辑 if (address == 0x94440 + module.base) { backend.reg_write(Arm64Const.UC_ARM64_REG_X0, 0); System.out.println("修改cmp"); } // 执行指令分析 Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64, Capstone.CS_MODE_ARM); byte[] bytes = emulator.getBackend().mem_read(address, 4); Instruction[] disasm = capstone.disasm(bytes, 0); InsAndCtx iac = new InsAndCtx(); iac.setIns(disasm[0]); iac.setRegs(saveRegs(backend)); iac.setAddr(address); if (instructions.size() > 10) { instructions.removeFirst(); } instructions.addLast(iac); execute(backend); } @Override public void onAttach(UnHook unHook) { System.out.printf("attach"); } @Override public void detach() { System.out.printf("detach"); } }, module.base + begin, module.base + end, null); } //在这里进行执行判断逻辑,无非就是几种混淆,后面的混淆也可以在下面进行添加 //在这里进行执行判断逻辑,无非就是几种混淆,后面的混淆也可以在下面进行添加 public void execute(Backend backend) { if (instructions.isEmpty()) { return; } List<InsAndCtx> instructionList = new ArrayList<>(instructions); // 规则匹配 for (int i = instructionList.size() - 1; i >= 0; i--) { InsAndCtx curr_iac = instructionList.get(i); String mnemonic = curr_iac.ins.getMnemonic(); String opStr = curr_iac.ins.getOpStr(); try{ // 匹配 br 或 blr 指令 if (mnemonic.equals("br") || mnemonic.equals("blr")) { boolean csel_ldr_add_br = false; // 检查是否为复杂的间接跳转模式 (csel, ldr, add, br) if (mnemonic.equals("br") && opStr.trim().equals("x9") && i >= 3) { InsAndCtx addIns = instructionList.get(i - 1); InsAndCtx ldrIns = instructionList.get(i - 2); InsAndCtx cselIns = instructionList.get(i - 3); if (addIns.ins.getMnemonic().equals("add") && ldrIns.ins.getMnemonic().equals("ldr") && cselIns.ins.getMnemonic().equals("csel")) { csel_ldr_add_br = true; } } // --- 处理复杂的间接跳转 (csel/ldr/add/br) --- if (csel_ldr_add_br) { InsAndCtx br = curr_iac; InsAndCtx add = instructionList.get(i - 1); InsAndCtx ldr = instructionList.get(i - 2); InsAndCtx csel = instructionList.get(i - 3); // 1. 从 CSEL 指令中获取信息 String[] csel_operands = csel.ins.getOpStr().toLowerCase(Locale.ROOT).split(","); // csel xd, xn, xm, cond => csel_operands = [" xd", " xn", " xm", " cond"] String reg1 = csel_operands[1].trim(); // source register if true String reg2 = csel_operands[2].trim(); // source register if false String condition = csel_operands[3].trim(); // condition // FIX: Correctly get values from both reg1 and reg2 long branch1 = getRegValue(reg1, csel.getRegs()).longValue(); long branch2 = getRegValue(reg2, csel.getRegs()).longValue(); // 2. 从 LDR 指令中获取信息 // Assuming ldr xd, [xn] format String[] ldr_operands = ldr.ins.getOpStr().split(","); String base_reg = ldr_operands[1].replaceAll("[\\[\\]]", "").trim(); long base_reg_val = getRegValue(base_reg, ldr.getRegs()).longValue(); // 3. 从 ADD 指令中获取信息 String[] add_operands = add.ins.getOpStr().split(","); String reg_add = add_operands[2].trim(); long add_reg_val = getRegValue(reg_add, add.getRegs()).longValue(); // 4. 计算两个可能的跳转目标地址 // Simulating: read from jump table -> add offset long true_addr = readInt64(backend, base_reg_val + branch1) + add_reg_val; long false_addr = readInt64(backend, base_reg_val + branch2) + add_reg_val; // 5. FIX: 创建正确的 Patch 序列 PatchBR pb = new PatchBR(); pb.type = br_type.indirect_br; pb.br_addr = br.addr - module.base; // Patch 1: csel操作 PatchIns pi1 = new PatchIns(); pi1.setAddr(csel.addr - module.base); pi1.setIns(String.format("b.%s 0x%x", condition, true_addr - module.base)); pb.patchs.add(pi1); // Patch 2: Replace ldr操作 PatchIns pi2 = new PatchIns(); pi2.setAddr(ldr.addr - module.base); pi2.setIns(String.format("b 0x%x", false_addr - module.base)); pb.patchs.add(pi2); // Patch 3 & 4: NOP PatchIns pi3 = new PatchIns(); pi3.setAddr(add.addr - module.base); pi3.setIns("nop"); pb.patchs.add(pi3); PatchIns pi4 = new PatchIns(); pi4.setAddr(br.addr - module.base); pi4.setIns("nop"); pb.patchs.add(pi4); pb.br_jump_to.add(true_addr - module.base); pb.br_jump_to.add(false_addr - module.base); if (identify_br(pb)) { System.out.println("[info] find indirect br jump at 0x" + Long.toHexString(pb.br_addr) +" -> true: 0x" + Long.toHexString(true_addr - module.base)+" ("+condition+"), false: 0x" + Long.toHexString(false_addr - module.base) + ", type:" + pb.type); patch_list.add(pb); doPatch(); } return; // --- 处理简单的直接跳转 (br/blr) --- } else { InsAndCtx br = curr_iac; PatchBR pb = new PatchBR(); pb.br_addr = br.addr - module.base; String jump_reg = br.ins.getOpStr().trim(); long target_addr = getRegValue(jump_reg, br.getRegs()).longValue(); pb.br_jump_to.add(target_addr - module.base); PatchIns pi1 = new PatchIns(); pi1.setAddr(br.addr - module.base); if (mnemonic.equals("br")) { pb.type = br_type.direct_br; pi1.setIns(String.format("b 0x%x", target_addr - module.base)); } else { // blr pb.type = br_type.direct_blr; pi1.setIns(String.format("bl 0x%x", target_addr - module.base)); } pb.patchs.add(pi1); if (identify_br(pb)) { System.out.println("[info] find "+mnemonic+" jump at 0x" + Long.toHexString(pb.br_addr) + " -> to 0x" + Long.toHexString(target_addr - module.base) + ", type:" + pb.type); patch_list.add(pb); doPatch(); } return; } } } catch (Exception e){ System.err.println("Error processing instruction at address 0x" + Long.toHexString(curr_iac.addr)); e.printStackTrace(); } } } public void doPatch() { try { // 1. 读原 so File f = new File(inname); byte[] data = Files.readAllBytes(f.toPath()); if (patch_list.isEmpty()) { System.out.println("No patches to apply."); return; } // 2. 遍历每个 br-patch 组 for (PatchBR br : patch_list) { if (br.errorno != error_type.ok) { System.out.println(br.error_info); // 有冲突时仅日志提示 continue; } for (PatchIns pi : br.patchs) { // ← 这里才是真正的 patch 指令 System.out.printf("Processing addr: 0x%X, code: %s%n", pi.getAddr(), pi.getIns()); // 使用 Keystone 编译指令为机器码 Keystone ks = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian); KeystoneEncoded enc = ks.assemble(pi.getIns(), (int) pi.getAddr()); byte[] mc = enc.getMachineCode(); // 将机器码写入原文件的数据中 System.arraycopy(mc, 0, data, (int) pi.addr, mc.length); } } // 3. 写出修补后的 so Files.write(new File(outname).toPath(), data); System.out.println("Patch applied successfully."); } catch (Exception e) { e.printStackTrace(); } } void destory(){ IOUtils.close(emulator); System.out.println("destory"); } public void set_hook(){ emulator.getBackend().hook_add_new(new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { if (address==0x94440+module.base){ backend.reg_write(Arm64Const.UC_ARM64_REG_X0,0); } } @Override public void onAttach(UnHook unHook) { } @Override public void detach() { } },0x9443c+ module.base,0x94444+ module.base,null); }} |
其余的有没执行到的,直接写idapython脚本匹配一下去除
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 | #使用idapython匹配字节然后进行nopimport ida_bytesimport idcimport idaapidef find_and_patch(start,end): pattern=['CMP', 'MOV', 'MOV', 'CSEL', 'LDR', 'ADD', 'BR'] res=[] while start<end: ea=start cnt=0 for i in range(len(pattern)): if pattern[i] != idc.print_insn_mnem(ea): break else: cnt+=1 ea=idc.next_head(ea,end) if cnt==7: res.append((start,start+4*7)) start+=4*7 else: start+=4 return resdef set_color(start,end): for addr in range(start,end): idc.set_color(addr,idc.CIC_ITEM,0xd0ffc0)def do_patch(start,end): nop_bytes = (0x1F2003D5).to_bytes(4, "big") while start<end: ida_bytes.patch_bytes(start,nop_bytes) start+=4begin=0xE2DB4end=0xE39F8patchs=find_and_patch(begin,end)print(patchs)for i in range(len(patchs)): set_color(patchs[i][0],patchs[i][1]) do_patch(patchs[i][0],patchs[i][1]) |
算法分析:
第一段加密:
第一段加密对应着最开始的BR混淆以及不透明谓词混淆吧,反正挺清晰的应该

都是通过frida 一点点hook出来的

然后可以发现有一个encode函数,我们可以进行发现有混淆,不过能看
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 | // The function seems has been flattenedvoid __fastcall encode(__int64 input, __int64 len_17, __int64 a3, _QWORD *inp){ int n0x3B9A031E; // w8 int v9; // w0 int n111673319; // w9 bool v11; // zf int n1288343544; // w9 _BYTE v13[128]; // [xsp+30h] [xbp-1E0h] BYREF _BYTE v14[128]; // [xsp+B0h] [xbp-160h] BYREF _BYTE inp_1[128]; // [xsp+130h] [xbp-E0h] BYREF __int64 x25____________x21____x19_1024; // [xsp+1B0h] [xbp-60h] x25____________x21____x19_1024 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);// x25应该是提前写好的一个值 // x21是输入 // x19是1024 copy1(inp, 1LL); // 这里有一个固定的参数一,是一个长数 // // // 左移16位,然后与原来的值进行或操作 // 这里竟然吧x21的值给覆盖了 // 也就是把输入的值给覆盖了 // 上面这些初始化了一些值,低216位 // 这里处理高16位,同时跟上面的赋值操作并运算 // 这里跳转了,跳到了0x9f4c4,也就是刚开始定义为密钥初始化 // 但是这个函数好像不是密钥初始化,是用于函数分发的 // 而且调用了很多次,一直在循环读取某一块内存,不知道是干什么的,跳转的太多了 copy2(inp_1, input); copy2(v14, len_17); // 这里再次跳转,跳到0xa1314 n0x3B9A031E = 0x3B9A031E;LABEL_9: // 这里跳转了 // 再次跳转,0xa040c // 应该还是和刚才哪个相似,都是为了对数值进行处理 // 跳转了0xa2c38 // 没跳转 while ( n0x3B9A031E >= 0x6A7FFE7 ) { // 跳转了 if ( n0x3B9A031E < 999949086 ) { if ( n0x3B9A031E == 0x6A7FFE7 ) { muls(inp_1, inp_1, v13); mods(v13, a3, inp_1); n1288343544 = 1288343544; // 这里应该就是来处理平方的 // ,一个是大整数平方,一个是大整数取模 // 就是熟知的快速幂算法 goto LABEL_13; } goto LABEL_3; } // 没跳转 if ( n0x3B9A031E == 999949086 ) { v11 = (v14[0] & 1) == 0; n0x3B9A031E = -778487121; n111673319 = -850153581; goto LABEL_17; }LABEL_3: if ( n0x3B9A031E < -251900671 ) { do { while ( n0x3B9A031E < -778487121 ) { if ( n0x3B9A031E != -850153581 ) goto LABEL_3; sub_A236C(v14, v13); copy2(v14, v13); v9 = sub_A1AE0(v14); n0x3B9A031E = -251900671; n111673319 = 111673319; v11 = v9 == 0;LABEL_17: // 再次跳转,0xa040c // 应该还是和刚才哪个相似,都是为了对数值进行处理 // 跳转了0xa2c38 // 没跳转 // 跳转了 // 没跳转 if ( v11 ) n0x3B9A031E = n111673319; // 跳转了 if ( n0x3B9A031E >= -251900671 ) goto LABEL_9; } if ( n0x3B9A031E != -778487121 ) goto LABEL_3; muls(inp, inp_1, v13); // 再次跳转,0xa040c // 应该还是和刚才哪个相似,都是为了对数值进行处理 mods(v13, a3, inp); // 跳转了0xa2c38 n1288343544 = -561759123;LABEL_13: n0x3B9A031E = n1288343544 - 288394458; // 没跳转 // 跳转了 } while ( n1288343544 - 288394458 < -251900671 ); } } if ( n0x3B9A031E != -251900671 ) goto LABEL_3;} |
注释是trace的时候看的流程,没啥用,其实也能看到关键逻辑
1 2 | muls(inp_1, inp_1, v13);mods(v13, a3, inp_1); |
直接frida trace或者frida hook就行
我是线trace看逻辑猜,在frida hook看数据变化
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 | function encry1() { const baseAddr = Module.findBaseAddress('libsec2023.so'); let tar = 0 const offset = 0xA040C; const func_inp = baseAddr.add(offset); Interceptor.attach(func_inp, { onEnter: function (args) { console.log("函数一 参数一"); const buf = Memory.readByteArray(args[0], 0x30); console.log(hexdump(buf, { offset: 0, length: 0x30, header: true, ansi: true })); console.log("函数一 参数二"); var buf2 = Memory.readByteArray(args[1], 0x30); console.log(hexdump(buf2, { offset: 0, length: 0x30, header: true, ansi: true })); console.log("函数一 参数三"); var buf2 = Memory.readByteArray(args[2], 0x30); console.log(hexdump(buf2, { offset: 0, length: 0x30, header: true, ansi: true })); }, onLeave: function (retval) { console.log("函数一 返回值"); var buf3 = Memory.readByteArray(retval, 0x30); console.log(hexdump(buf3, { offset: 0, length: 0x30, header: true, ansi: true })); } });}function encry2() { const baseAddr = Module.findBaseAddress('libsec2023.so'); let tar = 0 const offset = 0xA2C38; const func_inp = baseAddr.add(offset); Interceptor.attach(func_inp, { onEnter: function (args) { console.log("函数二 参数一"); const buf = Memory.readByteArray(args[0], 0x30); console.log(hexdump(buf, { offset: 0, length: 0x30, header: true, ansi: true })); console.log("函数二 参数二"); var buf2 = Memory.readByteArray(args[1], 0x30); console.log(hexdump(buf2, { offset: 0, length: 0x30, header: true, ansi: true })); console.log("函数二 参数三"); var buf2 = Memory.readByteArray(args[2], 0x30); console.log(hexdump(buf2, { offset: 0, length: 0x30, header: true, ansi: true })); }, onLeave: function (retval) { // 可选:你也可以打印返回值或回溯栈 //console.log("===", this.context.x1) console.log("函数二 返回值"); var buf3 = Memory.readByteArray(retval, 0x30); console.log(hexdump(buf3, { offset: 0, length: 0x30, header: true, ansi: true })); } });} |
就能看到数据变化了,尽量取值大一点才能看到变化

可以看到运行了多少次
通过一点点观察就是一个快速幂算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def encry_1(base,ord,mod): res=1 while ord>0: if ord%2==1: res=res*base%mod base=base*base%mod ord//=2 return res%moda=0x4d2ord=17mods=0x0028a831a5bf4b902e95318e50c2075259f91094d08d84409e1b76eadfa0865d1278acc90fa7c6cf6acb375tar=0x25f6b048b4f32e3ce9175bb64930f65101a706ae74988a4ec87b4d5ec7feb9223ab782bcf1ec9d7fee750print(hex(encry_1(0x4d2,17,mods))) |
经过测试就是求这个a
这个其实就是一个离散对数问题(学到了)
大数能直接被分解成质数,基于这个来写解密脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import gmpy2def decry_1(): cip=0x25f6b048b4f32e3ce9175bb64930f65101a706ae74988a4ec87b4d5ec7feb9223ab782bcf1ec9d7fee750 n=0x28a831a5bf4b902e95318e50c2075259f91094d08d84409e1b76eadfa0865d1278acc90fa7c6cf6acb375 e=17 p = 555183147936225271626794036740589959032732535469347 q = 640704384372038524783151782406101498608483916642951 phi=(p-1)*(q-1) d=gmpy2.invert(e,phi) m=pow(cip,d,n) return mm=decry_1()print(hex(m))#0x7be300df8b2c |
所以第一层加密结果就是0x7be300df8b2c
因为还差2位,就只能看第二段加密了
第二段加密:
第二段加密对应的就是我们上面所作的csel的去混淆
先看函数

我搞得另外一个程序分段了,从这个看逻辑,关键就在
sub_95970 和 sub_98E50这个函数,因为这两个函数有混淆(trace发现这里面有使用到输入的地方)
猜测就是sub_98E50这个函数,毕竟上面那个很像就是来读取压缩包内容的

点进去看看
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 | void __fastcall __noreturn sub_98E50(__int64 a1, int a2){ __int64 v2; // x11 int n1911078787; // w8 bool v4; // zf int n252508079; // w9 unsigned __int64 v6; // x9 int n1911078787_1; // w9 __int64 *v8; // [xsp+8h] [xbp-E8h] unsigned __int64 v9; // [xsp+10h] [xbp-E0h] char v11; // [xsp+20h] [xbp-D0h] unsigned __int64 v12; // [xsp+30h] [xbp-C0h] unsigned __int64 v13; // [xsp+38h] [xbp-B8h] unsigned __int64 v14; // [xsp+40h] [xbp-B0h] int n3; // [xsp+4Ch] [xbp-A4h] __int64 v16; // [xsp+58h] [xbp-98h] __int64 v17; // [xsp+60h] [xbp-90h] __int64 *v18; // [xsp+68h] [xbp-88h] unsigned __int64 v19; // [xsp+70h] [xbp-80h] __int64 v20; // [xsp+78h] [xbp-78h] unsigned __int64 v21; // [xsp+80h] [xbp-70h] unsigned __int64 v22; // [xsp+88h] [xbp-68h] unsigned __int64 v23; // [xsp+90h] [xbp-60h] v2 = a1; _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)); v16 = *(a1 + 376); v8 = (a1 + 264); v9 = a2;LABEL_39: v4 = v16 == 0; n1911078787 = -1356816398; n252508079 = -1966562382;LABEL_52: n1911078787_1 = n252508079 - 288394458; if ( !v4 ) n1911078787 = n1911078787_1; while ( 1 ) { while ( 1 ) { while ( n1911078787 < -35886379 ) { if ( n1911078787 >= -1022605219 ) { if ( n1911078787 >= -799327844 ) { if ( n1911078787 < -738166060 ) { if ( n1911078787 == -799327844 ) {LABEL_30: v11 = 0; v21 = v22 + 1; n1911078787 = -591289714; if ( v22 <= v9 ) goto LABEL_43; } } else { v6 = v21;LABEL_49: v14 = v6; n3 = 3;LABEL_7: n1911078787 = 1911078787; if ( n3 ) n1911078787 = 1565738013; v12 = v14; } } else { n1911078787 = 951268337; } } else if ( n1911078787 >= -1765623770 ) { if ( n1911078787 < -1356816398 ) { if ( n1911078787 == -1765623770 ) {LABEL_22: *v18 = v20 + 4; v13 = v22;LABEL_25: v23 = v13; n1911078787 = 828056523; if ( !*(v2 + 280) ) goto LABEL_34; } } else { n1911078787 = -976886875; } } else if ( n1911078787 >= -2002346238 ) { if ( n1911078787 == -2002346238 ) goto LABEL_36; } else if ( n1911078787 == -2142698382 ) { goto LABEL_7; } } if ( n1911078787 >= 951268337 ) break; if ( n1911078787 >= 725054412 ) { if ( n1911078787 >= 746343868 ) { if ( n1911078787 >= 828056523 ) { v6 = v23; goto LABEL_49; } if ( n1911078787 == 746343868 ) {LABEL_43: v13 = v21; n1911078787 = -738166060; if ( (v11 & 1) == 0 ) goto LABEL_25; } } else if ( n1911078787 == 725054412 ) { goto LABEL_25; } } else if ( n1911078787 >= 724515125 ) { if ( n1911078787 == 724515125 ) goto LABEL_39; } else if ( n1911078787 == -35886379 ) { v12 = 0LL; *(v2 + 392) = 1; goto LABEL_47; } } if ( n1911078787 >= 1842981774 ) { if ( n1911078787 >= 1911078787 ) { if ( n1911078787 >= 2040010456 ) { if ( n1911078787 == 2040010456 ) { v17 = sub_BAE2C( *(v2 + 400), 208LL, 1842981774LL, 1565738013LL, 746343868LL, 725054412LL, 724515125LL, 272LL); v2 = a1; v4 = v17 == 0; n1911078787 = -1022605219; n252508079 = 252508079; goto LABEL_52; } } else if ( n1911078787 == 1911078787 ) {LABEL_47: v22 = v12; v18 = v8; v19 = *v8; if ( *v8 != -1 ) {LABEL_36: *(v2 + 288) = 1; sub_E2DB4( *(v16 + 4 * (v19 >> 2)), v2, 1842981774LL, 1565738013LL, 746343868LL, 725054412LL, 724515125LL, 272LL); v20 = *v18; v2 = a1; if ( *v18 != v19 ) goto LABEL_30; goto LABEL_22; } n1911078787 = 1565738013; } } else if ( n1911078787 == 1842981774 ) {LABEL_34: n3 = 0; v14 = v23; goto LABEL_7; } } else { n1911078787 = -976886875; } }} |
这个是我使用unidbg来patch的,然后会出现几个 _asm{x8 }这样的,因为我unidbg没出现,我就直接全部nop了,可能有误删的,但是程序没跑完,我电脑跑不完了,就直接先这样看
不难发现就是这两处可能是关键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | v17 = sub_BAE2C( *(v2 + 400), 208LL, 1842981774LL, 1565738013LL, 746343868LL, 725054412LL, 724515125LL, 272LL); sub_E2DB4( *(v16 + 4 * (v19 >> 2)), v2, 1842981774LL, 1565738013LL, 746343868LL, 725054412LL, 724515125LL, 272LL); v20 = *v18; v2 = a1; |
直接hook一下这两个地方看看呗
我输入 0x7be37bdf8b2c 136,216,966,040,364 第二段也就是123
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 | let cnt = 0function encry3() { const baseAddr = Module.findBaseAddress('libsec2023.so'); let tar = 0 const offset = 0x99374; const func_inp = baseAddr.add(offset); Interceptor.attach(func_inp, { onEnter: function (args) { //x0是第一个参数(不知道什么作用),x1是第二个参数(是一个地址,里面放着输入),还用到x9(是一个地址,可能是跳转地址),x10(一个固定参数) var x0_val = this.context.x0; var x1_val = this.context.x1; var x1_data = Memory.readU32(x1_val) var x9_data = this.context.x9 - base_addr //var x10_val = this.context.x10 //毕竟是vm,应该就是调用某个地址的函数,然后传参 tar_func(x0,x1) console.log(cnt, "\t", x9_data.toString(16), "\t", x0_val, "\t", x1_data) cnt += 1 }, onLeave: function (retval) { } });} |
得到:

跳转了几个地址看了一下全是混淆,没啥逻辑,只能trace了
本来想用frida 来trace的,直接崩啦,直接上unidbg吧
我之前没用过unidbg的trace的,这次正好学习一下,感觉unidbg的trace还是挺简单的,感觉效率是真快,1g也就1.2min
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 | package com.tengxun2023;import com.github.unidbg.Emulator;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.DalvikModule;import com.github.unidbg.listener.TraceCodeListener;import com.github.unidbg.virtualmodule.android.AndroidModule;import capstone.Capstone;import capstone.api.Instruction;import com.alibaba.fastjson.util.IOUtils;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.arm.backend.Backend;import com.github.unidbg.arm.backend.CodeHook;import com.github.unidbg.arm.backend.UnHook;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.DalvikModule;import com.github.unidbg.linux.android.dvm.DvmClass;import com.github.unidbg.linux.android.dvm.VM;import com.github.unidbg.memory.Memory;import com.github.unidbg.virtualmodule.android.AndroidModule;import keystone.Keystone;import keystone.KeystoneArchitecture;import keystone.KeystoneEncoded;import keystone.KeystoneMode;import unicorn.Arm64Const;import javax.sound.midi.Patch;import java.io.*;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.file.Files;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.*;import java.util.List;import java.util.Stack;import java.io.File;public class tracer_fin_2023 { public final AndroidEmulator emulator; public final VM vm; public final Memory memory; public final Module module; public tracer_fin_2023(){ emulator = AndroidEmulatorBuilder.for64Bit().build(); memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); emulator.getSyscallHandler().setEnableThreadDispatcher(true); vm = emulator.createDalvikVM(); new AndroidModule(emulator,vm).register(memory); DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/java/com/tengxun2023/libsec2023.so"), true); module = dalvikModule.getModule(); vm.callJNI_OnLoad(emulator,module); } public static void main(String[] args) throws FileNotFoundException { tracer_fin_2023 mainActivity = new tracer_fin_2023(); mainActivity.tracer(); } public long byteArrayToLong(byte[] byteArray) { ByteBuffer buffer = ByteBuffer.wrap(byteArray); buffer.order(ByteOrder.LITTLE_ENDIAN); // 设置字节顺序为小端 return buffer.getLong(); } long fun_idx=0; public void set_hook_vm(){ emulator.getBackend().hook_add_new(new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { if(address==0x99374+ module.base){ if(fun_idx>=22){ try{ System.out.printf("%d:\n",fun_idx); Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } } fun_idx+=1; } } @Override public void onAttach(UnHook unHook) { } @Override public void detach() { } },0x000000000099370 + module.base,0x00000000009937C + module.base,null); } public byte[] mapfun =new byte[0x140000]; private void tracer() throws FileNotFoundException{ emulator.traceWrite(0x404f2010L+(0xe<<3),0x404f2010L+(0xe<<3)+0x4); emulator.traceRead(0x404f2010L+(0xe<<3),0x404f2010L+(0xe<<3)+0x4); set_hook(); set_hook_vm(); padding_reg2val(reg2val); long [][]hook_list= { { 0xcf004,0xcfac0 }, { 0xcfac4,0xcfddc }, { 0xd0b2c,0xd10a0 }, { 0xd1ae8,0xd1cbc }, { 0xd1cc0,0xd241c }, { 0xd2420,0xd27bc }, { 0xd2ad0,0xd31d0 }, { 0xd5518,0xd5a74 }, { 0xd5a78,0xd5d8c }, { 0xd6158,0xd6df8 }, { 0xd6e00,0xd7ac4 }, { 0xd7f4c,0xd8b60 }, { 0xd8b64,0xd8e58 }, { 0xdf458,0xdf758 }, { 0xdf75c,0xdfa68 }, { 0xdf75c,0xdfa68 }, { 0xe0be8,0xe1144 }, { 0xe1148,0xe1454 }, { 0xe227c,0xe2db0 }, { 0xe2db4,0xe39fc }, { 0xe3e18,0xe497c }, { 0xe4980,0xe4c4c }, { 0xe4c50,0xe58a4 }, { 0xe6bf4,0xe76d8 }, { 0xe8054,0xe8854 }, { 0xe9564,0xe98b0 }, { 0xd0a88, 0xD0B28}, { 0xe1468, 0xE14DC} }; for(int i=0;i<hook_list.length;i++){ long st=hook_list[i][0]; mapfun[(int) st]=1; } traceStream = new PrintStream(new FileOutputStream(traceFile), true); module.callFunction(emulator,0x94368,0x123412341234L); } String traceFile="unidbg-android/src/test/java/com/tengxun2023/all_trace.txt"; PrintStream traceStream = null; public HashMap<String,Integer> reg2val=new HashMap<>(); public long read_reg_by_str(Backend backend,String reg){ long true_reg_val=-1; if(reg2val.containsKey(reg)){ true_reg_val=backend.reg_read(reg2val.get(reg)).longValue(); } else if(reg.equals("xzr")){ true_reg_val=0; }else{ assert false; } return true_reg_val; } public static void padding_reg2val(HashMap<String,Integer> reg2val){ reg2val.put("x0", Arm64Const.UC_ARM64_REG_X0); reg2val.put("x1",Arm64Const.UC_ARM64_REG_X1); reg2val.put("x2",Arm64Const.UC_ARM64_REG_X2); reg2val.put("x3",Arm64Const.UC_ARM64_REG_X3); reg2val.put("x4",Arm64Const.UC_ARM64_REG_X4); reg2val.put("x5",Arm64Const.UC_ARM64_REG_X5); reg2val.put("x6",Arm64Const.UC_ARM64_REG_X6); reg2val.put("x7",Arm64Const.UC_ARM64_REG_X7); reg2val.put("x8",Arm64Const.UC_ARM64_REG_X8); reg2val.put("x9",Arm64Const.UC_ARM64_REG_X9); reg2val.put("x10",Arm64Const.UC_ARM64_REG_X10); reg2val.put("x11",Arm64Const.UC_ARM64_REG_X11); reg2val.put("x12",Arm64Const.UC_ARM64_REG_X12); reg2val.put("x13",Arm64Const.UC_ARM64_REG_X13); reg2val.put("x14",Arm64Const.UC_ARM64_REG_X14); reg2val.put("x15",Arm64Const.UC_ARM64_REG_X15); reg2val.put("x16",Arm64Const.UC_ARM64_REG_X16); reg2val.put("x17",Arm64Const.UC_ARM64_REG_X17); reg2val.put("x18",Arm64Const.UC_ARM64_REG_X18); reg2val.put("x19",Arm64Const.UC_ARM64_REG_X19); reg2val.put("x20",Arm64Const.UC_ARM64_REG_X20); reg2val.put("x21",Arm64Const.UC_ARM64_REG_X21); reg2val.put("x22",Arm64Const.UC_ARM64_REG_X22); reg2val.put("x23",Arm64Const.UC_ARM64_REG_X23); reg2val.put("x24",Arm64Const.UC_ARM64_REG_X24); reg2val.put("x25",Arm64Const.UC_ARM64_REG_X25); reg2val.put("x26",Arm64Const.UC_ARM64_REG_X26); reg2val.put("x27",Arm64Const.UC_ARM64_REG_X27); reg2val.put("x28",Arm64Const.UC_ARM64_REG_X28); reg2val.put("x29",Arm64Const.UC_ARM64_REG_FP); reg2val.put("x30",Arm64Const.UC_ARM64_REG_LR); reg2val.put("sp",Arm64Const.UC_ARM64_REG_SP); reg2val.put("pc",Arm64Const.UC_ARM64_REG_PC); } public void set_hook(){ emulator.getBackend().hook_add_new(new CodeHook() { @Override public void hook(Backend backend, long address, int size, Object user) { if (address==0x94440+module.base){ backend.reg_write(Arm64Const.UC_ARM64_REG_X0,0); //从这里开始trace代码痕迹 emulator.traceCode(module.base, 0x40150000, new TraceCodeListener() { @Override public void onInstruction(Emulator<?> emulator, long address, Instruction insn) { if(mapfun[(int) address -(int) module.base]==1){ traceStream.printf("\n=================================================================\n"); traceStream.printf("\n\n\n%d\tcall [0x%x]\n\n\n",fun_idx,address- module.base); traceStream.printf("=================================================================\n"); } } }).setRedirect(traceStream); } } @Override public void onAttach(UnHook unHook) { } @Override public void detach() { } },0x9443c+ module.base,0x94444+ module.base,null); }} |
这个跑上2.3min就可以拿到1g的trace记录了
通过这次的trace,学会了在对这种平坦化的代码进行算法还原的时候,一定要当前的值往后先翻一翻,记录下来他的出现的地方,要不然走到后面都不知道哪里会再次调用了,容易搞乱之间的顺序
下面是我的算法还原记录:
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 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 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | [19:05:42 782][libsec2023.so 0x0e1318] [007c4092] 0x120e1318: "and x0, x0, #0xffffffff" x0=0x114514 => x0=0x114514[19:05:42 784][libsec2023.so 0x0eaa24] [0f20c29a] 0x120eaa24: "lsl x15, x0, x2" x0=0x114514 x2=0x0 => x15=0x114514[19:05:42 784][libsec2023.so 0x0eaa2c] [0f24c29a] 0x120eaa2c: "lsr x15, x0, x2" x0=0x114514 x2=0x0 => x15=0x114514[19:05:43 714][libsec2023.so 0x0e1348] [0900142a] 0x120e1348: "orr w9, w0, w20" w0=0x114514 w20=0x0 => w9=0x114514def enc1: #对每个字节循环执行下面的操作 inp=0x114514 inp&=0xffffffff inp<<0 inp>>0 inp | 0 inp=0x114514 inp&=0xffffffff inp<<0 inp>>0 inp | 0 inp=0x114514 inp&=0xffffffff inp<<0 inp>>0 inp | 0 inp=0x114514 inp&=0xffffffff inp1=inp>>13 0x2 inp2=inp<<0xd 0x228a28000 tmp1= inp1 | inp2 0x228a28002 tmp1>>=0x1f tmp1&=0x1 inp=114514 inp&=0xffffffff inp1=inp<<0x5 0x228a280 inp2=inp>>0x5 0x8a28 后面还有对这个的操作,大概再689747行也就是0xeb074处,579250处也再次调用 op5_tmp1=inp1 | 0x2 0x228a282 下面开始对 0x228a282这个值操作===================================================================== op5_tmp1&=0xffffffff op6_1 = op5_tmp1 << 0x10 0x228a2820000 op6_2 = op5_tmp1 >> 0x10 x15=0x228 op6_2&=0xffffffff op7_1=op6_2 ^ 0x10 0x238 后面有用到,大概在565537 op8_1=op7_1 &0xff 0x38 op9_1=op8_1 &0xffffffff 0x38 op9_2=op9_1 >> 0x1e 0x0 op9_3=op9_1 << 0x2 0xe0 op9_4=op9_2 | op9_3 0xe0 op10_1=op9_4 &0xfffffffc 0xe0 op10_2=op10_1 & 0x3fc000003 0xe0000000 op_10_4=op7_1 & 0xffffffff op10_5=op10_4>>0x6 0x8 op10_6=op10_4<<0x1a 0x8e0000000 op10_7=op10_5 | op10_6 0x8e0000008 op10_8=op10_7 & 0x3fc000003 0xe0000000 op10_9=op10_8 | op10_2 0xe00000e0 那看来上面的那个或其实是这里的 op10_10=op10_9 & 0x3 0x0 op10_11=op10_10 &0xfffffffc 0xe0 op10_12=op10 | op10_11 0xe0 跳到566698 0x0cfcbc op11_1=op10_12 &0xffffffff 0xe0 op11_2=op11_1 <<0 0xe0 op11_3=op11_1>>0 0xe0 op11_4=op11_2 +0x6b 0x14b op11_5=op11_4+0x0 0x14b op11_6=op11_3 | 0xffffffff00000000 0xe0 op11_7=op11_6+0x6b 0x14b tmp1>>=0x1f tmp1&=0x1 上面以及下面两句好像不是操作 op11_7>0x20 op11_7&=1 op11_7&=0xffffffff 0x14b 0x14b好像就这样追完了========================================================================== 接下来有一个对上面继续的操作就是0x228a282这个她正好在0x14b结束之后 接下来就是 cip=0x228a282 cip>>8 cip<<8 0x228a2 and 0xffffffff cip^= 0x8 0x228aa and 0xff 0xaa tmp1=0xaa 只是为了后续的表示,没有这一句 然后就是同步63行开始的代码,trace的代码在597849处继续 and 0xffffffff op1_1=tmp >>0x1e 0x0 op1_2=tmp <<0x2 0x2a8 op1_3=op1_1 | op1_2 上面这两句可能还是没有作用 op1_4=op3&0xfffffffc op1_5=op1_4 &0x303fffffc 0x2a8 后续还有一处对0x2a8的或在627788行 op1_6=op1_5 & 0x3fc000003 0x2a8000002 op1_7=op1_6 | op1_5 0x2a80002aa op1_8=op1_7 & 0x3 0x2 op1_9=op1_8 | op1_5 0x2aa op2_1=op1_9<<0 op2_2=op1_9>>0 0x2aa 后续还会使用应该,记住 op2_3=op2_2+0xa2 0x34c 这里肯定有调用 op2_4=op2_3+0x0 0x34c op2_5= op2_4 | 0xffffffff00000000 0x0xffffffff000002aa op2_6=op2_5+0xa2 0x34c 再一次相加 and 0xffffffff 0x34c 最终也追完了,结束在629234行的地方====================================================================== 继续搜 0x228a282 看看是不是还有一次,找到了 在641237行加载 and 0xffffffff <<0 0x228a282 >>0 0x228a282 and 0xffffffff 0x228a282 xor 0x0 0x228a282 后续还有在使用这个值在689747行 and 0xff 0x82 tmp2=0x82 op1_1=tmp2 &0xffffffff 0x82 op1_2=op1_1 >>0x1e 0x0 op1_3=op1_1 <<0x2 0x208 op1_4=op1_2 | op1_3 0x208 op2_1=op1_4 & 0xfffffffc 0x208 op2_2=op2_1 & 0x303fffffc 0x208 后续还会使用, op2_3=op2_1 & 0x3fc000003 0x208000002 op2_4=OP2_2 | op2_3 0x20800020a op2_5=op2_4 & 0x3 0x2 op2_6=op2_5 | op2_1 0x20a op3_1=op2_6 &0xffffffff op3_2=op3_1 <<0 0x20a op3_3=op3_1 >> 0 0x20a op3_4=op3_1 +0x16 0x220 这里肯定也会调用 op4_1=op3_4 +0x0 0x220 >>0x20 &1 这两句我没看懂 op5_1 &0xffffffff op5_2 =op5_1 | 0xffffffff00000000 0xffffffff00000220 得到结果220====================================================================key=[0x6b , 0xa2 , 0x16]没啥思路了,上面循环得到三个值 0x14b 0x34c 0x2200x14b:572192结束0x34c:634230结束0x220:这个倒是很多,但是很多都是取值赋值操作,没有什么加密的痕迹,应该在别的地方搜索0x228a282也没得到什么信息在689808截至看来第一段应该是一个循环,然后有三个小循环,尝试搜索这三个值&0xff的值就是 0x4b 0x4c 0x200x4b:795280行0x4c:750538行0x20:这个太多了,估计在70w行以后我从上往下翻,翻到了 56 call [0xe8054] 估计应该块开始下一次了691784行从这里看上面三个值哪个近直接看0x4c了==============================================================================第二段加密==============================================================================[19:05:51 400][libsec2023.so 0x0d8e00] [011c4092] 0x120d8e00: "and x1, x0, #0xff" x0=0x4c => x1=0x4cdef enc2: 73804行有0x20 cip=0x20 cip&0xff op2_1=cip>>0x1d 0x0 op2_2=cip<<0x3 0x100 op2_3=op2_1 | op2_2 0x100 op3_1=op2_3 & 0x7f8 op3_2=op3_1 | 0x1 0x101 op3_3=op3_2 & 0x7ff op3_4=op3_3 | 0x0 0x101 op3_5=op3_4 ^ 0xa2 0x117 cip=0x4c 750538行 cip & 0xff op1_1=cip>>5 0x2 op1_2=cip<<0x1b 0x260000000 op1_3=op1_1 | op1_2 0x260000002 上面这个应该后续没啥用了,很像上面第一次加密的 直接跳到了783246行 op2_1=cip>>0x1d 0x0 op2_2=cip<<0x3 0x260 op2_3=op2_1 | op2_2 0x260 后续的使用应该是这个了,0x4c很长都不会使用 op3_1=op2_3 & 0x7f8 0x260 op3_2=op3_1 | 0x2 0x262 op3_3=op3_2 & 0x7ff op3_4=op3_3 | 0x0 0x262 op3_5=op3_4 ^ 0xa2 0x2c0 后面就没了,看一下0x4b吧============================================================================= 很像上面的操作 cip2=0x4b cip2&=0xff op1_1=cip2>>5 0x2 op1_2=cip2<<0x1b 0x258000000 op1_3=op1_1 | op1_2 0x258000002 op2_1=cip2>>0x1d 0x0 op2_2=cip2<<0x3 0x258 op2_3=op2_1 | op2_2 0x258 op3_1=op2_3 & 0x7f8 0x258 op3_2=op3_1 | 0x2 0x25a op3_3=op3_2 & 0x7ff op3_4=op3_3 | 0x0 0x25a op3_5=op3_4 ^ 0x6b 0x231看着像倒序def enc2(inp): key=[0x6b , 0xa2 , 0x16] tmp2=[0]*3 for i in range(2,-1,-1): tmp2[i]=inp[i]<<3 | inp[i]>>5 tmp2[i]^=key[i] tmp2[i]&0xff得到结果: [0x17,0xc0,0x31]0x31 0xc0 0x17================================================================================加密三============================================================================840333行 0x31+0x75 0xa6858407行 0xc0^0xfe 0x3e 98w行以后可能会调用876979行 0x0xffa63e17+0xc1 x10=0xffa63ed8 这里竟然不是单纯的0x17,抽象,刚开始搜0x17没搜到,搜的17 才搜到得到结果: [a6,3e,d8]============================================================================加密四:============================================================================923319行搜到0xa6 0xa6 &0xff inc1=0xa6 op1_1=inc1>>0x1f 0x0 op1_2=inc1<<0x1 0x14c op1_3=op1_1 | op1_2 0x14c op1_4=op1_3 | 0x0 0x14c op2_1=op1_4 &0xfffffffe 0x14c op2_2=op2_1 & 0x101fffffe 0x14c 有一个不知道怎么回事的值[19:05:55 122][libsec2023.so 0x0d2654] [080114aa] 0x120d2654: "orr x8, x8, x20" x8=0x14c000001 x20=0x14c => x8=0x14c00014d[19:05:55 122][libsec2023.so 0x0d2658] [2b7d40d2] 0x120d2658: "eor x11, x9, #0xffffffff" x9=0x1 => x11=0xfffffffe[19:05:55 122][libsec2023.so 0x0d265c] [6b01188a] 0x120d265c: "and x11, x11, x24" x11=0xfffffffe x24=0x14c => x11=0x14c 难搞 op3_1=op2_2 | 0x1 0x14d 0x1不知道怎么来的 op3_2=op3_1 ^ 0x2 0x14f 0x2不知道怎么来的· op3_3=op3_2&0xffffffff op3_4=op3_3>>0x10 op3_5=op3_3<<0x10 op3_6=op3_4 | op3_5 0x14f0000 op3_7=op3_6 & 0x0xff0000 0x4f0000 这个不知道是什么------------------------------------------------------------------------1003977行处 cip=0x3e cip&0xffffffff op1_1=cip >> 0x7 0x0 100w处还有一次调用 op1_2=cip << 0x19 0x7c000000 op1_3=op1_1 | op1_2 0x7c000000 op1_4=cip>>0x1f 0x0 op1_5=cip<<0x1 0x7c op1_6=op1_5 | op1_4 0x7c op2_1=op1_6 & 0x1fe 0x7c op2_2 =op2_1 | 0x0 0x7c op2_3=op2_2 &0x1f 0x7c op2_4=op2_3 | 0x0 0x7c op2_5=op2_4 ^ 0x1 0x7d 0x7c后续没啥操作了 op3_1=op2_5 <<0x8 0x7d00 op3_2 =op2_5 >> 0x8 0x0 0x7d00也没啥操作了后续,而且7d00不像是有什么操作的样子结束在1038429处-----------------------------------------------------------------------在10659558处开始操作 cip=0xd8 cip&0xffffffff op1_1=cip>>0x7 0x1 op1_2=cip<<0x19 0x1b0000000 op1_3=op1_1 | op1_2 0x1b0000001 没有后续操作1在1081077行处 op2_1=cip>>0x1f 0x0 op2_2=cip<<0x1 0x1b0 op2_3=op2_1 | op2_2 0x1b0 后续没有对0xd8的操作l,就是看上面这两处位移运算怎么来了 op3_1=op2_3 & 0x1fe 0x1b0 op3_2 =op3_1 | 0x1 0x1b1 op3_3=op3_2 &0x1ff 0x1b1 op3_4=op3_3 | 0x0 0x1b1 op3_5 =op3_4 ^0x0 0x1b1 op3_6=op3_5 &0xff 0xb1 op4_1=op3_6 <<0 op4_2=op3_6<<0 &0xffffffff 后续也没啥操作了----------------------------------------------------------------得到应该是得到 [0x4f0000 , 0x7d00 0xb1 ]追踪这些数据在1042512行处得到:[19:05:57 001][libsec2023.so 0x0e9fc0] [8a02088b] 0x120e9fc0: "add x10, x20, x8" x20=0x4f0000 x8=0x7d00 => x10=0x4f7d00相加了[19:05:58 531][libsec2023.so 0x0e9fc0] [8a02088b] 0x120e9fc0: "add x10, x20, x8" x20=0x4f7d00 x8=0xb1 => x10=0x4f7db1在1104537处再次相加追踪这个值,发现他又开始左移5,右移5,就是第一次加密的算法 |
得到的加密代码:
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 | def encry2(enc): for i in range(256): enc&=0xffffffff enc1=enc<<5 | enc>>0x13 key=[0x6b , 0xa2 , 0x16] fin1=[0]*3 # print("==========") #第一次加密,是一个三层循环 for i in range(3): t=16-8*i tmp1=((enc1>>t) ^ t)&0xff tmp2=(tmp1<<0x2) &0xfffffffc tmp3=((tmp1 >>0x6) | (tmp1 << 0x1a)) tmp4=(((tmp3 & 0x3fc000003) | tmp2) & 0x3) | tmp2 tmp4+=key[i] tmp4&=0xff fin1[i]=tmp4 # print("===第一段加密结果===") # for i in range(3): # print(hex(fin1[i]),end=',') # print() #第二段加密 fin2=[0]*3 for i in range(2,-1,-1): tmp1=(fin1[i]<<3 | fin1[i]>>5) fin2[i]=tmp1^key[i] fin2[i]&=0xff # print("===第二段加密结果====") # for i in range(3): # print(hex(fin2[i]),end=',') # print() fin3=[0]*3 fin3[0]=(fin2[0]+0x75)&0xff fin3[1]=(fin2[1]^0xfe)&0xff fin3[2]=(fin2[2]+0xc1)&0xff # print("===第三段加密结果====") # for i in range(3): # print(hex(fin3[i]), end=',') # print() fin4=[0]*3 for i in range(3): t=16-8*i tmp1=fin3[i]>>7 | fin3[i]<<1 tmp2=tmp1^(2-i) tmp2&=0xff tmp3=tmp2<<t fin4[i]=tmp3 # print("===第四段加密结果====") # for i in range(3): # print(hex(fin4[i]), end=',') # print() enc=sum(fin4) return enc |
解密代码:
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 | def decry2(token): token &= 0xffffffff key = [0x6b, 0xa2, 0x16] for i in range(256): enc1=token cip1=[0]*3 for i in range(3): t=16-8*i tmp1=enc1>>t tmp1&=0xff tmp2=tmp1^(2-i) tmp3=(tmp2<<7) | (tmp2>>1) cip1[i]=tmp3&0xff cip1[0]=(cip1[0]-0x75)&0xff cip1[1]=(cip1[1]^0xfe)&0xff cip1[2]=(cip1[2]-0xc1)&0xff cip2=[0]*3 for i in range(2,-1,-1): cip1[i]^=key[i] tmp1=cip1[i]>>3 | cip1[i]<<5 tmp1&=0xff cip2[i]=tmp1 cip3=[0]*3 for i in range(3): t=16-8*i cip2[i]&=0xff tmp1=cip2[i]-key[i] tmp2=(tmp1&0xff)>>2 | (tmp1&0xff)<<6 tmp3=tmp2^t tmp3&=0xff tmp4=tmp3<<t cip3[i]=tmp4 tmp=sum(cip3) tmp&=0xffffffff token=tmp<<0x13 | tmp >>0x5 token&=0xffffff a = 0x7BE300DF8B2C token_part=(token&0xffff00)<<40 | (token&0xff)<<24 ans = token_part | a print(hex(ans)) print("解密结果为",ans) return anstoken=3153664decry2(token) |
早知道使用C来写解密代码了,移位运算真的难搞
这道题目做了好久,也在做这个题目的时候不断学习着新的东西,这道题应该是我收获最大的一道题目了,学到的东西也是最多的。从反调试到去混淆,unidbg,idapython以及最后的算法还原都收获颇多,也是多感谢师哥以及其他大佬的文章在做题的过程中对我的帮助。
参考:
[原创] 2023腾讯游戏安全大赛-安卓赛道决赛"TVM"分析与还原
赞赏
- [原创]对某某漫画的算法还原测试 8641
- [原创]签名校验攻防对抗基础学习 2883
- [原创]2023年腾讯游戏安全竞赛安卓决赛题解复现 875
- [原创]OLLVM学习 6177