2023年腾讯游戏安全竞赛安卓决赛题解复现
flag获取
和初赛一样,还是hook同样的代码,
let so_name = "libil2cpp.so"
let bases = Module.findBaseAddress(so_name);
let module = Process.getModuleByName(so_name);
let base_addr = module.base
let base_size = module.size
console.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的分析,就是一些
let so_name = "libil2cpp.so"
let bases = Module.findBaseAddress(so_name);
let module = Process.getModuleByName(so_name);
let base_addr = module.base
let 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的,然后就发现他的跳转了

然后就是去看这个跳转到哪里了
代码如下:
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随便对一个地址下个硬件断点就行
2|oriole:/data/local/tmp # ./stackplz --pid 22724 --brk 0x744A280CB8:r --stack
[*] save maps to maps_22724.txt
set breakpoint at kernel:false, addr:0x744a280cb8, type:1
start 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函数,挺完美的
var soname = 'libsec2023.so';
var startaddr = 0x7B300;
var endaddr = 0x7B300 + 0x1000
var size = endaddr - startaddr;
let so_name = "libsec2023.so"
let bases = Module.findBaseAddress(so_name);
let module = Process.getModuleByName(so_name);
let base_addr = module.base
let base_size = module.size
function 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 = 0
function 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()
继续回到正文,我们过掉反调试以后,就去看他哪里进行了加密

通过hook,我们发现就个地方进行了加密
混淆处理:
BR跳转混淆
看汇编知道有许多的br跳转,这种br跳转去除很简单那们就是将br x8修改为 BL xxaddr

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

类似这种

手动patch就行

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

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

ida统一修改如下:
idapthon参看https://python.docs.hex-rays.com/namespaceida__idaapi.html
# 1、修改变量的参数类型
from idc import *
def change_type(addr,new_type):
SetType(addr,new_type)
start_addr=0x1270d0
end_addr=0x13d9a0
for 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代码: https://github.com/XiaoWaaay/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拿到不能正常看的地址,也就是混淆函数的筛选
import idc
import idautils
import idaapi
func_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=0x4f1d0
end=0x10526c
ida_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)
break
print(len(func_end))
for i in (func_end):
print(hex(i),end=",")

上面会得到已经反编译出来的所有不能正常看的函数的值,下面就是编写unidbg代码来hook这些函数,从而计算这些if跳转的地址
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的代码:
import idc
import idaapi
def 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,0xe58a4
patcher = {}
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:
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脚本匹配一下去除
#使用idapython匹配字节然后进行nop
import ida_bytes
import idc
import idaapi
def 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 res
def 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+=4
begin=0xE2DB4
end=0xE39F8
patchs=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函数,我们可以进行发现有混淆,不过能看
// The function seems has been flattened
void __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的时候看的流程,没啥用,其实也能看到关键逻辑
muls(inp_1, inp_1, v13);
mods(v13, a3, inp_1);
直接frida trace或者frida hook就行
我是线trace看逻辑猜,在frida hook看数据变化
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
}));
}
});
}
就能看到数据变化了,尽量取值大一点才能看到变化

可以看到运行了多少次
通过一点点观察就是一个快速幂算法
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%mod
a=0x4d2
ord=17
mods=0x0028a831a5bf4b902e95318e50c2075259f91094d08d84409e1b76eadfa0865d1278acc90fa7c6cf6acb375
tar=0x25f6b048b4f32e3ce9175bb64930f65101a706ae74988a4ec87b4d5ec7feb9223ab782bcf1ec9d7fee750
print(hex(encry_1(0x4d2,17,mods)))
经过测试就是求这个a
这个其实就是一个离散对数问题(学到了)
大数能直接被分解成质数,基于这个来写解密脚本
import gmpy2
def 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 m
m=decry_1()
print(hex(m))
#0x7be300df8b2c
所以第一层加密结果就是0x7be300df8b2c
因为还差2位,就只能看第二段加密了
第二段加密:
第二段加密对应的就是我们上面所作的csel的去混淆
先看函数

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

点进去看看
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了,可能有误删的,但是程序没跑完,我电脑跑不完了,就直接先这样看
不难发现就是这两处可能是关键
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
let cnt = 0
function 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
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,学会了在对这种平坦化的代码进行算法还原的时候,一定要当前的值往后先翻一翻,记录下来他的出现的地方,要不然走到后面都不知道哪里会再次调用了,容易搞乱之间的顺序
下面是我的算法还原记录:
[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=0x114514
def 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 0x220
0x14b:572192结束
0x34c:634230结束
0x220:这个倒是很多,但是很多都是取值赋值操作,没有什么加密的痕迹,应该在别的地方
搜索0x228a282也没得到什么信息在689808截至
看来第一段应该是一个循环,然后有三个小循环,
尝试搜索这三个值&0xff的值
就是 0x4b 0x4c 0x20
0x4b: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=0x4c
def 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 0xa6
858407行
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,就是第一次加密的算法
得到的加密代码:
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
解密代码:
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 ans
token=3153664
decry2(token)
早知道使用C来写解密代码了,移位运算真的难搞
这道题目做了好久,也在做这个题目的时候不断学习着新的东西,这道题应该是我收获最大的一道题目了,学到的东西也是最多的。从反调试到去混淆,unidbg,idapython以及最后的算法还原都收获颇多,也是多感谢师哥以及其他大佬的文章在做题的过程中对我的帮助。
参考:
https://www.cnblogs.com/lordtianqiyi/p/18745518
https://python.docs.hex-rays.com/namespaceida__idaapi.html
https://oacia.dev/unidbg-anti-br/

浙公网安备 33010602011771号