代码混淆是逆向分析中最令人头疼的问题之一。
面对混淆,通常的做法是直接硬着头皮调试,因为混淆只能混淆代码的形,但是代码的神——执行逻辑是不会变的,单步调试下去往往能梳理出程序本身的执行逻辑。但是面对非常复杂的混淆,小弟我也是经常调试数十小时最终招架不住选择放弃。。。有位伟人曾经说过“安全保护的目的不在于让代码固若金汤,无法攻破,而是在于让破解者望而生畏,主动放弃”,正所谓“不战而屈人之兵,善之善也~”,小弟我深以为然。
面对混淆更好的做法是去混淆,或者说反混淆,就是将混淆过的代码还原回去。还原后的代码通常逻辑清晰,通俗易懂,可以很简单的进行分析。但是反混淆往往比较难,抛开各种定制的混淆不说,即使是最基本的ollvm混淆,还原起来也不是那么容易。因为还原混淆通常要依赖于符号执行,模拟执行,指令trace等一系列高级的逆向手法。小弟我最近刚好遇到一个混淆的样本,趁着五一放假,研究了一下反混淆的方法,最终成功还原出了真实逻辑,故借此机会和看雪各位安全爱好者做一分享。

混淆前的程序,可以看到在 br x9 后ida无法分析了,因为x9是一个寄存器,ida并不知道里面是什么值,所以无法继续向下分析。F5后可以看到,函数调用jni->GetEnv函数后就jumpout了。

往下查看汇编代码,可以看到函数里有许多根据寄存器跳转的指令:

去混淆通常要对代码进行模拟执行。
模拟执行届扛把子的是unicorn引擎。不过unicorn引擎相对比较底层,需要手动的开辟内存,将指令写入,设置堆栈的位置,设置寄存器初始值,然后执行。unidbg是基于unicorn引擎的模拟执行框架,使用java语言编写,并且可以直接加载so,进行调用(unidbg在加载动态库时自动帮你完成了文件解析,映射,重定位,写入unicorn引擎等一系列底层操作),同时unidbg内置了Jni相关的环境,而unicorn本身没有相关环境,需要手动补充。同时,unidbg还内置了hookZz,xHook等一系列hook框架,可以很方便的进行函数级,指令级的各种hook。总之,unidbg大大方便了逆向人员进行模拟执行的流程,所以我们选用unidbg来进行模拟执行。
unidbg的工作模式简单粗暴,直接git clone unidbg的项目,然后在项目里添加自己的模拟执行类,最终运行的时候是带着整个项目的源码一起运行的,这样做的好处是方便了定制化修改和源码调试。

下载好后使用idea进行打开。
直接在项目的unidbg-android/src/test/java目录下建立我们的模拟执行类:AntiOllvm
在进行模拟执行之前,需要做以下准备工作:
1.创建一个模拟器。
2.设置android系统库的版本
3.创建android虚拟机
4.加载动态库
这样,动态库就被unidbg加载了。
加载后需要先执行jni_onload,而DalvikModule这个类已经实现了callJNI_OnLoad方法,我们直接调用即可。
调用后,出现了报错和许多warning:

一方面,有许多libc的函数没有找到,另一方面,JNI返回了-1。
我们先补充所需的libc函数,只需要把手机里/system/lib64/libc.so等系统库交给unidbg加载即可。
将下列代码添加到加载目标so之前:
再次执行,这次运行成功了,并且unidbg hook 了所有的jni掉用,也将他们打印了出来:

可以看到,在0x5e900处调用了registernative,注册的函数名叫:initialize,签名是:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)I,函数的地址在0x5e1e8处。
至此,我们成功使用unidbg加载了android的动态库,并且正常运行了动态库的jni_onload函数。
我们已经成功运行了目标动态库,接下来可以看看还能做些什么。我们做一个最基本的操作,使用指令hook,在每条指令执行之前将其打印出来。
使用到的是unicorn引擎提供的codehook,即指令hook。我们在指令执行前进行hook,然后初始化Capstone引擎(反汇编引擎),接着通过unicorn的接口从当前地址读取4字节机器码,再将机器码反汇编,然后打印输出。我们可以将hook的范围指定在一个函数中,这样就不会打印超出该函数范围的指令了:

可以看到,打印的指令和ida中解析出的jnionload函数指令一致。当然,我们也看到了br x9之后的那条指令:

跳转到0x5e480,又执行了若干组间接跳转。
至此,我们已经可以通过unidbg的接口,进行指令trace。同时我们还可以使用unidbg提供的其他接口,在hook的同时读取,写入寄存器,查看内存,修改内存,设置断点等等。接下来我们就可以开始着手去混淆了。
首先要解决的是 br x9 的问题,至少让ida能F5吧。
通过阅读汇编代码,可以看到该函数的混淆是这样做的:
1.比较 w8 和 wk(另外一个寄存器,不固定)寄存器。
2.根据比较的结果,选择xA或者xB寄存器的值赋值给X9
3.从x9 + x19的地址处读取一个值,赋值给x9
4.x9 = x9 + x24
5.跳转到x9指向的位置。
其中,x19和x24始终是固定的,我们看到,x19和x24在程序开始时被初始化了:


可以看到,X19是0x140080,而0x140080是一个数组

x24则为固定的值0x3a2cd438
这里就理解这个间接跳转是怎么做的了,每次根据比较结果在数组中选择一个偏移,然后用偏移加上基数,得到真实的跳转地址。
所以我们可以在每次执行到br x9的时候获取x9的真实值,然后将根据寄存器跳转的指令br 换成直接跳转b,这样ida就可以继续分析了。
直接这样做有一个问题,因为偏移值是根据比较结果不同而不同,如果我们直接将最终的跳转地址写死,那就相当于将原来的条件跳转改为了直接跳转,改变了逻辑。
所以正确的做法是保留条件,计算出条件成立与不成立时对应的跳转地址,然后将br x9改为一条条件跳转和一条直接跳转,这样做就和原来的逻辑完全一致了,并且所有的跳转都是有确定的地址,ida可以分析。
于是我们用这样的算法进行还原:
1.建立一个指令栈,每条指令执行前,保留该指令的地址,指令内容,当前所有寄存器的值。然后将当前指令的信息push进指令栈。
2.对指令进行回溯,如果栈顶指令是br x9,则进行3,否则执行下一条指令。
3.pop跳转指令,再次获取栈顶指令,检查是否为ADD x9,x9,x24指令,如果是,进行4,否则执行下一条指令。
4.pop add指令,再次获取栈顶指令,检查是否为LDR x9,[x19,x9]指令,如果是,则进行5,否则执行下一条指令。
5.pop 加载指令,再次获取栈顶指令,检查是否为条件选择指令。如果是,记录选择条件,记录为条件成立与不成立时对应的选择的值(通过保留的寄存器值获取)。否则执行下一条指令。
6.pop 条件选择指令,获取到上一次 比较指令,检查是否是cmp x8,类型的指令,如果是,进行7,否则执行下一条指令。
7.通过前六步,说明我们已经定位到了正确的间接跳转的位置。然后分别计算条件成立与不成立时对应的真实跳转地址,将Add 指令替换为 条件跳转->成立时地址,将br x9替换为直接跳转->条件不成立地址。
以以下指令为例:
在csel 一步,当条件LT成立时,x9 = x28。真实的跳转地址为
当条件不成立时,x9 = x23。真实的跳转地址为:
然后将add X9, X9, X24指令替换为b.lt addrT,
将br x9 指令替换为 b addrF。
将条件选择和加载指令nop掉。
替换后的指令为:
这样所有的间接跳转都变成了直接跳转,ida是可以分析的,并且逻辑与之前完全相同,我们所做的只不过是提前计算出目标地址,然后直接跳转罢了。
对应的java代码如下:
patch完成后,再次使用ida打开原so,发现Jni_onLoad函数已经可以F5了:

但是依然有一些jumpout。
原因是因为在模拟执行的是后并没有走遍所有的分支,我们只是将走过的分支处理了。
接下来需要在jumpout的地方手动修改寄存器的值,来控制他的走势,让unidbg最终走完所有分支。
比如对于这一处br,在blt时才会跳转过去,

可以看到这一处的跳转和w12有关,我们找到给w8赋值为w12的地方,通过修改寄存器来给其赋值:


重新跑一边代码,这一处的br x9 就修复了。将没有走到的分支处理完后,F5结果如下:

可以看到是标准的ollvm的控制流平坦化。
所以br x9的混淆其实是在ollvm控制流平坦化的基础上,将所有的跳转地址都加了一步运算。
间接跳转去除完后,需要去除控制流平坦化。根据控制流平坦化的工作原理,原来的代码会被打碎成代码片段,然后装进一个大的switch case里,最外层包一个while true循环,根据索引变量来按照原来的顺序执行代码。

考虑到编译的结果要在逻辑上和原来的代码完全一致,所以一个重要的结论是——真实的代码块一定在cmp eq 之后。(switch case 的一个case)
控制流平坦化对于顺序执行的处理比较简单,直接在前一个真实块的末尾,将索引值改为下一个真实块的索引,然后跳转到主分发器即可。
对于分支执行(判断,循环),在条件的部分会有一个条件选择指令CSEl,将索引寄存器的值根据条件结果设置为不同的索引,然后跳转到主分发器。
所以我们可以按照以下算法对控制流平坦化进行还原:
1.建立一个指令栈,每条指令执行前,保留该指令的地址,指令内容,当前所有寄存器的值。然后将当前指令的信息push进指令栈。
2.对指令进行回溯,如果栈顶指令是b.eq,则进行3(收集真实块),如果栈顶指令是直接跳转指令b,则进行5(处理分支块),否则继续执行下一条指令。
3.向上回溯指令栈,找到第一条cmp 指令,判断是否为与索引寄存器w8 比较,如果是,则进行4,否则继续执行下一条指令。
4.获取与w8比较的另一个寄存器的值,获取b.eq的目标地址,组成一个(索引,真实块)对。继续执行下一条指令。
5.判断上一条指令是否为CSEL W8。如果是,则记录一个(条件成立块索引,条件不成立块索引)对。否则,继续执行下一条指令。
同时,我们在主分发器处hook 指令,记录每次经过主分发器时的索引寄存器的值为索引值顺序。
在模拟执行收集完成信息之后,做以下处理:
1.去除索引值顺序中为条件块的索引。
2.获取索引值顺序中的第一个值,找到对应的真实块。将主分发器处patch为跳转向第一个真实块的跳转指令。
3.根据真实块结束时的新的索引值,寻找到对应的真实块,将结尾处patch为跳转到下一个真实块。
4.在CSEL指令处,根据条件成立块索引和条件不成立索引找到对应的真实块。将CSEL指令和后面的跳转向主分发器的指令patch为两条指令:
第一条为条件成立时的条件跳转,第二条为条件不成立时的直接跳转。
对应的代码如下:
在模拟执行完后,发现有一些索引值没有找到对应的块:

查看他们的索引,发现刚好是条件块中的索引。这个也好理解,因为在模拟执行的时候我们只走了条件的一个分支,没有走另一个分支,所以就没有记录下对应的真实块。这里我们手动根据b.eq和寄存器的值,添加对应的真实块:

添加完成后,再次运行程序,发现已经patch成功了。打开ida查看Jni_onload:

发现函数已经被完美的还原了。
借着五一假期简单研究了一下模拟执行和去混淆,感慨unidbg是真的强。我认为我使用的方法应该是没有任何逻辑漏洞,可以完全等效的还原的。
间接跳转的还原比较简单,控制流平坦化的还原则比较难。
控制流平坦化还原的最大难点在与找到真实块与索引之间的关系。由于我这次使用的样本对于跳转做了混淆,虽然难住了ida,但是也可能难住了编译器,导致对控制流平坦化部分没有做复杂的编译优化——所有的真实块都在b.eq后面,并且只有一个主分发器,没有次分发器。同时真实块执行结束都很乖巧的跳转到了分发器(有的编译优化会直接将两个真实块连在一起)。
总体来说,本次反混淆不是特别完美,有手工的部分,同时也不能通杀所有混淆,只能说提供一种反混淆思路和经验吧。
ollvm反混淆感觉是永无止境的,目前还没有见到有什么非常强大,通杀,可以完美还原的方案,只能针对目标混淆器定制反混淆策略。
安全攻防不息,吾辈仍需努力!
public AntiOllvm()
{
//创建模拟器
emulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.example.antiollvm")
.build();
Memory memory = emulator.getMemory();
//设置andorid系统库版本
memory.setLibraryResolver(new AndroidResolver(26));
//创建虚拟机
vm = emulator.createDalvikVM();
vm.setVerbose(true);
//加载动态库
dm = vm.loadLibrary(new File("f:\\kshs\\libtprt.so"), false);
module = dm.getModule();
}
public AntiOllvm()
{
//创建模拟器
emulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.example.antiollvm")
.build();
Memory memory = emulator.getMemory();
//设置andorid系统库版本
memory.setLibraryResolver(new AndroidResolver(26));
//创建虚拟机
vm = emulator.createDalvikVM();
vm.setVerbose(true);
//加载动态库
dm = vm.loadLibrary(new File("f:\\kshs\\libtprt.so"), false);
module = dm.getModule();
}
public static void main(String[] args) {
AntiOllvm ao = new AntiOllvm();
ao.callJniOnload();
}
public void callJniOnload()
{
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
AntiOllvm ao = new AntiOllvm();
ao.callJniOnload();
}
public void callJniOnload()
{
dm.callJNI_OnLoad(emulator);
}
vm.loadLibrary(new File("f:\\androidlib\\libc.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libm.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libstdc++.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\ld-android.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libdl.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libc.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libm.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libstdc++.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\ld-android.so"),false);
vm.loadLibrary(new File("f:\\androidlib\\libdl.so"),false);
public void logIns()
{
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
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);
System.out.printf("%x:%s %s\n",address-module.base ,disasm[0].getMnemonic(),disasm[0].getOpStr());
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base, module.base+module.size, null);
}
public void logIns()
{
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
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);
System.out.printf("%x:%s %s\n",address-module.base ,disasm[0].getMnemonic(),disasm[0].getOpStr());
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base, module.base+module.size, null);
}
CMP W8, W27
CSEL X9, X28, X23, LT
LDR X9, [X19,X9]
ADD X9, X9, X24
BR X9
CMP W8, W27
CSEL X9, X28, X23, LT
LDR X9, [X19,X9]
ADD X9, X9, X24
BR X9
*(x19+x28)+x24 //记为addrT。
*(x19+x28)+x24 //记为addrT。
*(x19+x23)+x24 //记为addrF。
*(x19+x23)+x24 //记为addrF。
CMP W8, W27
NOP
NOP
BLT addrT
B addrF
CMP W8, W27
NOP
NOP
BLT addrT
B addrF
//保存指令和寄存器环境类:
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;
}
}
// 指令栈
private Stack<InsAndCtx> instructions;
//所有需要patch的指令
private List<PatchIns> patchs;
//保存指令寄存器环境
public List<Number> saveRegs(Backend bk)
{
List<Number> nb = new ArrayList<>();
for(int i=0;i<29;i++)
{
nb.add(bk.reg_read(i+Arm64Const.UC_ARM64_REG_X0));
}
nb.add(bk.reg_read(Arm64Const.UC_ARM64_REG_FP));
nb.add(bk.reg_read(Arm64Const.UC_ARM64_REG_LR));
return nb;
}
//指令hook,每条指令执行前保存环境
public void processBr()
{
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
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);
instructions.push(iac);
do_processbr();
}
@Override
public void onAttach(UnHook unHook) {
System.out.println("attach");
}
@Override
public void detach() {
System.out.println("detach");
}
},module.base+start, module.base+end,null);
}
//指令栈回溯,根据处理结果,生成patchIns,供最后统一patch
public void do_processbr()
{
Instruction ins = instructions.peek().getIns();
if(ins.getMnemonic().equals("br") && ins.getOpStr().equals("x9"))
{
boolean finish = false;
long base = -1;
long listoffset = -1;
long cond1 = -1;
long cond2 = -1;
String cond = "";
long addinstaddr = -1;
long brinsaddr = instructions.peek().getAddr() - module.base;
long selectaddr = -1;
long ldaaddr = -1;
try {
while (!finish && !instructions.empty())
{
instructions.pop();
ins = instructions.peek().getIns();
if(ins.getMnemonic().toLowerCase(Locale.ROOT).equals("add"))
{
String[] split = ins.getOpStr().split(",");
if(split.length == 3)
{
if(split[0].toLowerCase(Locale.ROOT).trim().equals("x9") && split[1].toLowerCase(Locale.ROOT).trim().equals("x9"))
{
String reg = split[2].trim().toLowerCase(Locale.ROOT);
base = getRegValue(reg,instructions.peek().getRegs()).longValue();
addinstaddr = instructions.peek().getAddr() - module.base;
}
else {
break;
}
}
else
{
break;
}
}
if(ins.getMnemonic().toLowerCase(Locale.ROOT).equals("ldr"))
{
String[] sp = ins.getOpStr().toLowerCase().split(",");
if(sp.length == 3)
{
if(sp[0].trim().toLowerCase(Locale.ROOT).equals("x9") && sp[2].trim().toLowerCase(Locale.ROOT).equals("x9]"))
{
String reg = sp[1].toLowerCase(Locale.ROOT).trim().substring(1);
listoffset = getRegValue(reg,instructions.peek().getRegs()).longValue()-module.base;
ldaaddr = instructions.peek().getAddr()- module.base;
}
}
}
if(ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("csel"))
{
String[] sp = ins.getOpStr().toLowerCase(Locale.ROOT).split(",");
if(sp.length == 4)
{
cond = sp[3].trim();
if(sp[0].trim().equals("x9"))
{
String reg1 = sp[1].trim();
String reg2 = sp[2].trim();
cond1 = getRegValue(reg1,instructions.peek().getRegs()).longValue();
cond2 = getRegValue(reg2,instructions.peek().getRegs()).longValue();
selectaddr = instructions.peek().getAddr() - module.base;
}
}
}
if(ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("cmp"))
{
if(base == -1 || listoffset == -1 || cond1 == -1 || cond2 == -1 || cond.equals("") || addinstaddr == -1 || ldaaddr == -1 || selectaddr == -1)
{
break;
}
else
{
long offset1 = base + readInt64(emulator.getBackend(), module.base+listoffset+cond1) - module.base;
long offset2 = base + readInt64(emulator.getBackend(),module.base+listoffset+cond2) - module.base;
if( brinsaddr - addinstaddr != 4)
{
System.out.println("add ins and br ins gap more than 4 size,may make mistake");
}
String condBr = "b"+cond.toLowerCase(Locale.ROOT) + " 0x"+ Integer.toHexString((int) (offset1 - addinstaddr));
String br = "b 0x" + Integer.toHexString((int)(offset2 - brinsaddr));
PatchIns pi1 = new PatchIns();
pi1.setAddr(addinstaddr);
pi1.setIns(condBr);
patchs.add(pi1);
PatchIns pi2 = new PatchIns();
pi2.setAddr(brinsaddr);
pi2.setIns(br);
patchs.add(pi2);
PatchIns pi3 = new PatchIns();
pi3.setAddr(selectaddr);
pi3.setIns("nop");
patchs.add(pi3);
PatchIns pi4 = new PatchIns();
pi4.setAddr(ldaaddr);
pi4.setIns("nop");
patchs.add(pi4);
finish = true;
}
}
}
}catch (Exception e)
{
e.printStackTrace();
}
}
}
//遍历patch表,执行patch,生成新的so,使用Ketstone将汇编转为机器码。
public void patch()
{
try {
File f = new File(inName);
FileInputStream fis = new FileInputStream(f);
byte[] data = new byte[(int) f.length()];
fis.read(data);
fis.close();
for(PatchIns pi:patchs)
{
System.out.println("procrss addr:"+Integer.toHexString((int) pi.addr)+",code:"+pi.getIns());
Keystone ks = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
KeystoneEncoded assemble = ks.assemble(pi.getIns());
for(int i=0;i<assemble.getMachineCode().length;i++)
{
data[(int) pi.addr+i] = assemble.getMachineCode()[i];
}
}
File fo = new File(outName);
FileOutputStream fos = new FileOutputStream(fo);
fos.write(data);
fos.flush();
fos.close();
System.out.println("finish");
}
catch (Exception e)
{
e.printStackTrace();
}
}
//保存指令和寄存器环境类:
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;
}
}
// 指令栈
private Stack<InsAndCtx> instructions;
//所有需要patch的指令
private List<PatchIns> patchs;
//保存指令寄存器环境
public List<Number> saveRegs(Backend bk)
{
List<Number> nb = new ArrayList<>();
for(int i=0;i<29;i++)
{
nb.add(bk.reg_read(i+Arm64Const.UC_ARM64_REG_X0));
}
nb.add(bk.reg_read(Arm64Const.UC_ARM64_REG_FP));
nb.add(bk.reg_read(Arm64Const.UC_ARM64_REG_LR));
return nb;
}
//指令hook,每条指令执行前保存环境
public void processBr()
{
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
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);
instructions.push(iac);
do_processbr();
}
@Override
public void onAttach(UnHook unHook) {
System.out.println("attach");
}
@Override
public void detach() {
System.out.println("detach");
}
},module.base+start, module.base+end,null);
}
//指令栈回溯,根据处理结果,生成patchIns,供最后统一patch
public void do_processbr()
{
Instruction ins = instructions.peek().getIns();
if(ins.getMnemonic().equals("br") && ins.getOpStr().equals("x9"))
{
boolean finish = false;
long base = -1;
long listoffset = -1;
long cond1 = -1;
long cond2 = -1;
String cond = "";
long addinstaddr = -1;
long brinsaddr = instructions.peek().getAddr() - module.base;
long selectaddr = -1;
long ldaaddr = -1;
try {
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2023-5-3 23:04
被乐子人编辑
,原因: 排版