上篇博文中淺析了從手機淘寶中提煉出商品搜索接口,很多人有個疑惑,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個子編號共同編碼了整棵樹的路徑。樹中每個節點都包含相應函數經過異或處理后的地址,秘鑰位於父節點中。