一、目标
李老板: 奋飞呀,AI都把代码还原的活给干了,咱们这个代码还原的教程还要写吗?
奋飞: 当然要写了,AI是AI,那是批量化流水线操作,能有我这种纯手工还原的代码帅?我这可是有温度的代码。总不能有了翻译软件,你就不学英语了吧?当然日语可以不用学了。
有个名人说过:你永远赚不到超出你认知范围外的钱。所以奋飞说:你无法指挥AI干超出你认知范围外的活。
二、步骤
2、条件断点 3、数据打印
我们要想知道程序运行到 0x1170 时 x4=0xdd89ca68 的来历,那就需要从前面的代码开始Trace,
往上找了找 在 0x1144 有个判断,说明前面经过了若干次循环。
.text:0000000000001144 7F 01 08 EB CMP X11, X8
.text:0000000000001148 88 CC FF 54 B.HI loc_AD8
为了分析起来方便,我只想在最后一次循环的时候Trace代码来分析,这样就需要条件断点了。
在做条件断点之前,我们先把 X11 和 X8 的值打印出来。
debugger.addBreakPoint(module.base + 0x1144, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
Arm64RegisterContext ctx = emulator.getContext();
int iX11 = ctx.getXInt(11);
int iX8 = ctx.getXInt(8);;
System.out.printf("X11 = %d , X8 = %d\n", iX11,iX8);
return true;
}
});
跑一下,
X11 = 64 , X8 = 64
奇怪只打印了一次,那说明这里不是循环,或者是个假循环。
那就继续往上找吧
text:0000000000000C8C 8C 81 00 91 ADD X12, X12, #0x20 ; ' '
.text:0000000000000C90 ; 156: while ( v24 < 0x10 );
.text:0000000000000C90 03 FA FF 54 B.CC loc_BD0
.text:0000000000000C94 FE 17 50 29 LDP W30, W5, [SP,#0x110+var_90]
.text:0000000000000C98 11 03 19 4A EOR W17, W24, W25
感觉这个 0xC94 应该是最后一次计算的位置了。 (我们可以先在 0xC94 下一个断点,如果只触发一次说明可用,如果触发多次,说明它在循环体里面。)
5、Trace代码
Unidbg Trace代码有两种方式,一种是直接写代码来实现, 把Trace结果存入traceCode1.log
try {
// Trace 的范围是 0xC94 到 0x1170
emulator.traceCode(module.base+0xC94, module.base+0x1170).setRedirect(new PrintStream(new File("traceCode1.log")));
} catch (IOException e) {
throw new IllegalStateException(e);
}
另一种是在调试命令行输入Trace命令。
先在 0xC94下断点,进入调试窗口,输入 traceCode 命令然后直接回车即可。
traceCode
Set trace LinuxModule{base=0x40000000, size=12288, name='libnative-lib.so'} instructions success.
如果要把Trace结果保存到文件,就加上文件名 traceCode traceCode1.log
然后 c 命令继续执行,Trace结果就出来了。
开始还原算法
在Trace结果里面搜索 0xdd89ca68 , 发现他是通过 0x68ca89dd 翻转来的
0x40001154: "rev w4, w22" w22=0x68ca89dd => w4=0xdd89ca68
这里遇到不熟悉的指令可以很自然的咨询AI了
我们先把 0x68ca89dd 相关的结果挑出来,原则就是 从结果向入参回溯。
0x40001048: "add w12, w12, w15" w12=0x47448b48 w15=0x93f6f2bb => w12=0xdb3b7e03
0x40001098: "orn w12, w12, w11" w12=0xdb3b7e03 w11=0x49ac16b => w12=0xfb7f7e97
0x4000109c: "eor w12, w12, w16" w12=0xfb7f7e97 w16=0xa96ba62 => w12=0xf1e9c4f5
0x400010a0: "add w12, w12, w15" w12=0xf1e9c4f5 w15=0x93f6f2bb => w12=0x85e0b7b0
0x400010b0: "add w12, w12, w15" w12=0x85e0b7b0 w15=0x4212f2e5 => w12=0xc7f3aa95
0x4000107c: "add w11, w11, w17" w11=0xffa26fc9 w17=0x2cc489bc => w11=0x2c66f985
0x40001088: "add w11, w11, w17" w11=0x2c66f985 w17=0xf3d1564b => w11=0x20384fd0
0x4000108c: "ror w11, w11, #0xb" w11=0x20384fd0 => w11=0xfa040709
0x400010b4: "ror w12, w12, #0x1a" w12=0xc7f3aa95 => w12=0xfceaa571
0x40001090: "add w11, w11, w16" w11=0xfa040709 w16=0xa96ba62 => w11=0x49ac16b
0x400010b8: "add w12, w12, w11" w12=0xfceaa571 w11=0x49ac16b => w12=0x18566dc
0x40001048: "add w12, w12, w15" w12=0x47448b48 w15=0x93f6f2bb => w12=0xdb3b7e03
0x40001098: "orn w12, w12, w11" w12=0xdb3b7e03 w11=0x49ac16b => w12=0xfb7f7e97
0x4000109c: "eor w12, w12, w16" w12=0xfb7f7e97 w16=0xa96ba62 => w12=0xf1e9c4f5
0x400010a0: "add w12, w12, w15" w12=0xf1e9c4f5 w15=0x93f6f2bb => w12=0x85e0b7b0
0x400010b0: "add w12, w12, w15" w12=0x85e0b7b0 w15=0x4212f2e5 => w12=0xc7f3aa95
0x4000107c: "add w11, w11, w17" w11=0xffa26fc9 w17=0x2cc489bc => w11=0x2c66f985
0x40001088: "add w11, w11, w17" w11=0x2c66f985 w17=0xf3d1564b => w11=0x20384fd0
0x4000108c: "ror w11, w11, #0xb" w11=0x20384fd0 => w11=0xfa040709
0x400010b4: "ror w12, w12, #0x1a" w12=0xc7f3aa95 => w12=0xfceaa571
0x40001090: "add w11, w11, w16" w11=0xfa040709 w16=0xa96ba62 => w11=0x49ac16b
0x400010b8: "add w12, w12, w11" w12=0xfceaa571 w11=0x49ac16b => w12=0x18566dc
0x40001108: "add w22, w12, w22" w12=0x18566dc w22=0x67452301 => w22=0x68ca89dd
然后打开 VSCode开始写代码
int w12,w11,w15;
w12 = w12 | ~w11 // 0x40001098: "orn w12, w12, w11" w12=0xdb3b7e03 w11=0x49ac16b => w12=0xfb7f7e97
w12 = w12 ^ w16 // 0x4000109c: "eor w12, w12, w16" w12=0xfb7f7e97 w16=0xa96ba62 => w12=0xf1e9c4f5
// 上面这两步 其实就是
// w12 = (w12 | ~w11) ^ w16
w12 = w12 + w15 // 0x400010a0: "add w12, w12, w15" w12=0xf1e9c4f5 w15=0x93f6f2bb => w12=0x85e0b7b0
w12 = w12 + Num_w15(常量) // 0x400010b0: "add w12, w12, w15" w12=0x85e0b7b0 w15=0x4212f2e5 => w12=0xc7f3aa95
w12 = w12 >> 0x1a; // 0x400010b4: "ror w12, w12, #0x1a" w12=0xc7f3aa95 => w12=0xfceaa571
w12 = w12 + w11; // 0x400010b8: "add w12, w12, w11" w12=0xfceaa571 w11=0x49ac16b => w12=0x18566dc
是的,还原算法就是这么的朴实无华而又枯燥无味。
w12 = (w12 | ~w11) ^ w16 这个算式让我们有点兴奋
他和标准MD5 算法的 I 非常相似。
//F,G,H,I四个非线性变换函数
#define F(x,y,z) ((x & y) | (~x & z))
#define G(x,y,z) ((x & z) | (y & ~z))
#define H(x,y,z) (x^y^z)
#define I(x,y,z) (y ^ (x | ~z))
这样的话可能我们就可以轻松一点,在标准MD5算法的框架上去还原,只找出里面魔改的部分。
忍不住用AI了
把这个枯燥无味的过程扔给 Cursor 会怎样
用C语言还原w12的计算过程,添加中文注释
0x40001048: "add w12, w12, w15" w12=0x47448b48 w15=0x93f6f2bb => w12=0xdb3b7e03
0x40001098: "orn w12, w12, w11" w12=0xdb3b7e03 w11=0x49ac16b => w12=0xfb7f7e97
0x4000109c: "eor w12, w12, w16" w12=0xfb7f7e97 w16=0xa96ba62 => w12=0xf1e9c4f5
0x400010a0: "add w12, w12, w15" w12=0xf1e9c4f5 w15=0x93f6f2bb => w12=0x85e0b7b0
0x400010b0: "add w12, w12, w15" w12=0x85e0b7b0 w15=0x4212f2e5 => w12=0xc7f3aa95
0x4000107c: "add w11, w11, w17" w11=0xffa26fc9 w17=0x2cc489bc => w11=0x2c66f985
0x40001088: "add w11, w11, w17" w11=0x2c66f985 w17=0xf3d1564b => w11=0x20384fd0
0x4000108c: "ror w11, w11, #0xb" w11=0x20384fd0 => w11=0xfa040709
0x400010b4: "ror w12, w12, #0x1a" w12=0xc7f3aa95 => w12=0xfceaa571
0x40001090: "add w11, w11, w16" w11=0xfa040709 w16=0xa96ba62 => w11=0x49ac16b
0x400010b8: "add w12, w12, w11" w12=0xfceaa571 w11=0x49ac16b => w12=0x18566dc
#include <stdio.h>
int main() {
unsigned int w12 = 0x47448b48;
unsigned int w15 = 0x93f6f2bb;
unsigned int w11 = 0xffa26fc9;
unsigned int w17 = 0x2cc489bc;
unsigned int w16 = 0xa96ba62;
w12 = w12 + w15;
printf("w12 = %x\n", w12);
w12 = (w12 | ~w11);
printf("w12 = %x\n", w12);
w12 = w12 ^ w16;
printf("w12 = %x\n", w12);
w12 = w12 + w15;
printf("w12 = %x\n", w12);
w12 = w12 + 0x4212f2e5;
printf("w12 = %x\n", w12);
w11 = w11 + w17;
printf("w11 = %x\n", w11);
w11 = w11 + 0xf3d1564b;
printf("w11 = %x\n", w11);
w11 = (w11 >> 11) | (w11 << (32 - 11));
printf("w11 = %x\n", w11);
w12 = (w12 >> 26) | (w12 << (32 - 26));
printf("w12 = %x\n", w12);
w11 = w11 + w16;
printf("w11 = %x\n", w11);
w12 = w12 + w11;
printf("w12 = %x\n", w12);
return 0;
}
感觉比我写的好, 程序员要进化出一个新品种了,就是给AI写 prompt
三、总结
以前觉得当leader很幸福,啥活都不用干,直接指挥小弟干活就行。
后来真当了leader才发现,我去,你得A语言到Z语言都会,不然做php的小弟说这玩意实现不了,做Flash的小弟说这个功能得做一个月。
AI也是如此,机械重复性的工作它比你做的好,创意和指路的活还得你自己干。
决定战争胜负的是人,而不是一两件新式武器。这一论断永远都不过时
关注微信公众号,最新技术干货实时推送