上篇博文中浅析了从手机淘宝中提炼出商品搜索接口,很多人有个疑惑,x-sign怎么来的?目前很多网友表示是通过xposed hook用模拟器作服务器中转的方式。下面我们通过逆向so文件的方式取得这个x-sign的算法。
##找到x-sign的计算点经过一系列跳转后,我们看到了com.taobao.wireless.security.adapter.a接口的a方法。
private String a(String[] arg4, String arg5, int arg6, String arg7) {
return this.a.getRouter().doCommand(10401, new Object[]{arg4, arg5, Integer.valueOf(arg6), arg7});
}
在接下来的跳转链之后,我们又找到了实现RouterComponent接口以及doCommand方法的一个类:
package com.alibaba.wireless.security.mainplugin;
import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
public class a implements IRouterComponent {
public a() {
super();
}
public Object doCommand(int arg2, Object[] arg3) {
return JNICLibrary.doCommandNative(arg2, arg3);
}
}
还有一个JNICLibrary类,其中声明了doCommandNative方法:
package com.taobao.wireless.security.adapter;
public class JNICLibrary {
public static native Object doCommandNative(int arg0, Object[] arg1);
}
因此,我们需要在原生代码中找到doCommandNative方法。
混淆机器码
在libsgmain.so文件中包含一个原生库(libsgmain.so实际上是一个.JAR文件,其中实现了与加密有关的接口):libsgmainso-6.xx.x。在IDA中加载该库后,我们看到了一堆错误消息提示框,问题在于section头表无效。
通过elf查看工具我们可以看到但我们并不需要这个信息,程序头表对我们而言已经足够,可以正确加载并分析ELF文件。因此我们可以简单删除section头表,将头部中对应的字段置空。
然后再次在IDA中打开该文件。
我们有两种方法能告诉Java虚拟机哪个原生库包含代码中声明的原生代码的具体实现。第一种方法就是采用Java_package_name_ClassName_methodName之类的名字,第二种方法是调用RegisterNatives函数,在加载库的时候进行注册(在JNI_OnLoad函数中)。对于这个案例,如果我们使用第一种方法,那么函数名应该类似于Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative。在导出函数中我们找不到这个名字,这意味着我们需要查找RegisterNatives。因此,我们转到JNI_OnLoad函数,看到如下代码:
这里代码执行了哪些逻辑?初步分析时,函数头以及函数尾都是典型的ARM架构。第一条指令会将函数需要使用的寄存器值push到栈中(这里为R0、R1、R2以及LR,用来保存函数返回地址)。最后一条指令恢复已保存的寄存器值,将返回地址存到PC寄存器中,然后返回函数。但如果我们仔细分析,可能会注意到倒数第二条指令改变了返回地址。来计算一下代码执行后返回地址的值。该地址加载自R1(0xB130),减去5,然后被mov到R0,再加上0x10,最后这个值等于0xB13B。因此,IDA认为最终指令执行的是正常的函数返回操作,然而实际上会跳转到0xB13B这个地址。
这里需要注意的是,ARM处理器有两个型号以及两组指令:ARM以及Thumb。地址的低位用来决定处理器会使用哪一组指令集。这里地址为0xB13A,因此对应的是Thumb模式。
在这个库中,每个函数开头处都添加了类似的语句以及某些垃圾代码,这里我们不会详细分析这些内容,只要记住几乎所有函数的实际代码都离函数开头有一段距离。
由于已有代码中没有显式转换到0xB13A,因此IDA无法识别该地址处的代码。同样,IDA也没有将库中的大部分数据识别为代码,这样我们分析起来需要稍微用点技巧 因此,我们手动告诉IDA代码位置,然后得到如下结果:接下来我们采用脚本来patch代码。(鉴于篇幅 脚本内容略)patch完成后,我们可以指引IDA找到函数的真实代码。IDA会逐一收集所有函数代码,然后我们就可以使用HexRays来反编译代码。我们已经找到加密算法和密钥,现在让我们尝试解密类名。我们得到的结果为com/taobao/wireless/security/adapter/JNICLibrary
命令结构树
现在我们需要找到哪里调用了RegisterNatives,这将我们指引到doCommandNative函数。经过一系列分析还原得出具体逻辑:
int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args)
{
int v5; // r5
struc_2 *a5; // r6
int v9; // r1
int v11; // [sp+Ch] [bp-14h]
int v12; // [sp+10h] [bp-10h]
v5 = 0;
v12 = *(_DWORD *)off_8AC00;
v11 = 0;
a5 = (struc_2 *)malloc(0x14u);
if ( a5 )
{
a5->field_0 = 0;
a5->field_4 = 0;
a5->field_8 = 0;
a5->field_C = 0;
v9 = command % 10000 / 100;
a5->field_0 = command / 10000;
a5->field_4 = v9;
a5->field_8 = command % 100;
a5->field_C = env;
a5->field_10 = args;
v5 = sub_9D60(command / 10000, v9, command % 100, 1, (int)a5, &v11);
}
free(a5);
if ( !v5 && v11 )
sub_7CF34(env, v11, &byte_83ED7);
return v5;
}
函数名表示这是开发者将所有函数转到原生库的统一入口点,我们的目标函数编号为10401。从代码中我们可以通过命令编号生成3个子编号:command / 10000、command % 10000 / 100以及command % 10(这里我们对应的是1、4以及1)。这3个子编号、指向JNIEnv的指针以及传给该函数的其他参数共同组成一个结构体,以便后续使用。这棵树会在JNI_OnLoad中动态创建,其中3个子编号共同编码了整棵树的路径。树中每个节点都包含相应函数经过异或处理后的地址,秘钥位于父节点中。