对dex文件进行反编译后,发现把字符串传入securityCheck函数并根据返回的boolean值判断是否验证成功。而securityCheck函数是在本机代码动态链接库libcrackme.so内。
逆向汇编libcrackme.so,会发现从地址12A8H处有securityCheck内惟一的循环,而比较字符串几乎都要使用循环。因此可以猜想此处为验证过程。
发现进行比较的2个字符串的首地址分别存在r0和r2寄存器内,而r0从函数开始处至此一直没改变(虽然和r5有一次传值,但最后油传回r0),而且r0通常用于存储首个函数参数的指针,因此可以断定传入的字符串的首地址存于r0内。而r2的内容是答案存储的首地址。因此只要把r2存储的地址所指向的字符串的字符依次作为返回值,让Java端读取并显示即可得到答案。
在Java端可以看出,函数返回值为boolean,由于boolean在JNI的类型映射为unsigned char,byte在JNI的类型映射为char,而返回值不作为函数签名,因此可以在Java端写成public native static byte securityCheck(String index);修改so,使之每次向函数传入要返回的字符在答案字符串内的序号,然后返回对应序号的字符。
然而参数类型是函数签名的一部分,因此不应更改参数类型。此时就需要把序号值用String进行封装,封装方法为:字符串封装=new String(new
byte[]{(byte)序号&0xFF}),即传入字符串首字符的ASCII码为序号值。由于非空字符串的首字符的ASCII码不能为00,所以序号从1开始。
然后就要修改libscrackme.so。定位到12A8H,把
.text:000012A8 00 30 D2 E5 LDRB R3, [R2]
.text:000012AC 00 10 D0 E5 LDRB R1, [R0]
.text:000012B0 01 00 53 E1 CMP R3, R1
.text:000012B4 05 00 00 1A BNE loc_12D0
改为
.text:000012A8 01 20 42 E2 SUB R2, R2, #1 ;序号从1开始,因此先把指针减1
.text:000012AC 00 10 D0 E5 LDRB R1, [R0] ;字符串首字符为序号
.text:000012B0 01 20 82 E0 ADD R2, R2, R1 ;答案指针加上序号
.text:000012B4 00 10 D2 E5 LDRB R1, [R2] ;获取指针所指向的值
.text:000012B8 05 00 00 EA B loc_12D4 ;跳到函数返回部分
然后新建一个ANDROID项目,引用该库,并添加如下函数
public native static byte securityCheck(String index);
public void onClick(View view) { //点击按键显示答案
final byte 结束符 = (byte)0;
final int 最大长度 = 256;
StringBuffer 答案 = new StringBuffer();
for(int 序号=1;序号<=最大长度;++序号){
String 序号的字符串封装 = new String(new byte[]{(byte)(序号&0xFF)});
byte 字符 = securityCheck(序号的字符串封装);
if(结束符==字符) break;
答案.append((char) 字符);
}
((TextView)findViewById(R.id.textView)).setText(答案.toString());
}
点击按键,即可显示答案。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课