前言
繼續跟着龍哥的unidbg學習:SO逆向入門實戰教程六:s_白龍~的博客-CSDN博客
還是那句,我會借鑑龍哥的文章,以一個初學者的角度,加上自己的理解,把內容豐富一下,儘量做到不在龍哥的基礎上畫蛇添足,哈哈。感謝觀看的朋友
分析
首先抓個包看看:
這裏面這個sign,就是今天的重點了
這個app沒有殼,直接jadx打開,然後搜【sign"】或者【sign=】
很快的,就找到這個地方
目前確實還不確定是不是這裏,用objection hook下就知道了:
先hook了類,一堆,臥槽
hook tostring方法:
app多劃兩下:可以看到基本是對的上的
然後再找找堆棧調用,很快定位到這裏:
hook下這個類,發現有一堆,
慢慢來,首先這個appkey,就是這裏了
根據龍哥說的,就是libBili.s方法,但是hook了之後就加載就一直卡住。換用fridahook :
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
function main() {
Java.perform(function () {
var ClassName = "com.bilibili.nativelibrary.LibBili";
var Bilibili = Java.use(ClassName);
Bilibili.s.overload('java.util.SortedMap').implementation = function (arg) {
console.log(JSON.stringify(arg))
Java.openClassFile("/data/local/tmp/r0gson.dex").load()
const gson = Java.use('com.r0ysue.gson.Gson').$new();
const json_x = gson.toJson(arg)
console.log("json_x===>", json_x);
let res = this.s(arg)
console.log('result===>', res)
showStacks()
return res;
}
});
}
setTimeout(main, 2000)
感覺就是這個s了,入參是:
{"ad_extra":"E0DA3BAB712CAB3902D558CFD4FEB865AB1563B7D61A4A57D02854BB9A4512C5E4737CA15664C505F3E63B954BB68837690B81FE77B2E7EB578F3E3D7C528439725348FE86527D7415BA2267FCCD2C95895AE80E69A960812FE60
BC477171DAD34472EC31D010940A2876CADE887EA26FD5FBFB718D09F193D14732E54E42EB329EBF61CA33DE876AF24C03A7B9E89DB9C831AF6D1CB8B3B883AA3856AF08424AD8913BDE1207A5C2E010E23274ECCA7BC44814561E236325F0C4D2FFE069D2F9B
F5B40411A3D26D0BB864EC87713391FAE1197ACFD402D6D386B035BF04995C280989B7305964F179E149067FA864F66FA856EFBD3C8A005F92F1D7C93D82BC8C5B4804DC54154F34E5820B2A481C3F33EEDDB4323C142359920F60686D95E715E30894676AEDE
6933A31092E98F979157D267D580D056C3EDDD31934E762D7354A2E1959AAD44D1DDAF094B6E8F38C5450C6A164BD40AF2C3FB2E44BD8E0F914C7BA0FBA4FA553A2A1291BAF9E87FC528C2D53A6B3D3EFEA88F5C359119A31018AD879124BE62B4A4BCFEF98FA
5EE1BB5675D9828D1B246A10841CF1F6D1CC1C13C3C11C7E37BCEF93E933395A1FA4D7AEA4CB79DC3ED1EC162C926CFE1157366100A3AF788CF3262DC381B620FB68913694FD332D7D555CD60E3AC54E2F0A5FC2C90530A27721664A64B008FAB0A6","appkey
":"1d8b6e7d45233436","autoplay_card":"11","build":"6180500","c_locale":"zh_CN","channel":"shenma069","column":"2","device_name":"MI 6","device_type":"0","flush":"8","fnval":"400","fnver":"0","force_host":"
0","fourk":"1","guidance":"0","https_url_req":"0","idx":"1682561936","inline_danmu":"2","inline_sound":"1","login_event":"0","mobi_app":"android","network":"wifi","open_event":"","platform":"android","play
er_net":"1","pull":"false","qn":"32","recsys_mode":"0","s_locale":"zh_CN","splash_id":"","statistics":"{\"appId\":1,\"platform\":3,\"version\":\"6.18.0\",\"abtest\":\"\"}"}
我上面的流程只是爲了完整的展示,拿到app,定位加密,然後開始分析的,看過我前面的文章的應該都知道我在幹嘛。
調試
ok,那就開始so層的調試和分析了
1.找so文件
apk我們知道,但是so文件,在這裏,乍一看啥都沒有:
根據我們之前的定律,加載so文件都是static方法,然後,system.loadlibrary,但是這裏就奇怪了,啥都沒有
只看到這裏有點可疑:
點進去,知道G是bili
c.c會不會是system.loadlibrary呢?追進去:
看到使用str只有f,其他的邏輯我們可以不用管
f追進去,我們知道,後面兩個參數是null,因爲根本就沒傳這兩個,那就是這個g方法
g追進去,然後一下就看到了這個loadlibrary:
再追進去,感覺這個【com.getkeepsafe.relinker】類有點奇怪
網上搜一下即可:
看到這個介紹,ok,就是這裏了。
所以我們的so文件就是這個bili了。
2.搭架子
又報了這個錯:
[main]W/libc: pthread_create failed: clone failed: Out of memory
前面我們已經用過了:
在這加一行這個
emulator.getSyscallHandler().setEnableThreadDispatcher(true);
ok了
而且,我們的目標函數地址也打印出來了:
3.開始調用s方法
但是參數是TreeMap,根據龍哥的博客說的,需要搞個繼承關係,map->abstractMap->TreeMap
補一下:
繼續補:
插一句,如果這裏,你沒給ts,會報如下錯,而且提示都沒有,就很騷,我是用frida hook到的某一個流程的參數,剛好就沒有ts。但是實際調用的時候是有ts的:
回到剛纔的地方,臥槽,他這裏要一個 SignedQuery對象的r方法,也就是這個:
新建了一個util類,然後把jadx的copy過來,導入好已有的,然後如下:
把ContainerUtils的這兩個屬性拿過來:
現在就差b方法了
把a,b,c都copy完,還有這個:
整過來
現在還有cv.m了,
ok,整好了運行,臥槽,還要這個初始化對象:
再看看這個類的構造方法是啥:
也還好,就兩個屬性
好了終於沒有報錯了,發現,這個第二個參數就是sign:
再看這個返回結果 ,好像有點不對勁,以前的不都是直接把結果字符串打印出來了嗎,這裏咋沒打印,
根據前面的跟棧我們知道,他會調用下toString,那麼,這裏我們也把toString補上去看看:
牛逼,終於出來了。
完整代碼
package com.danmaku;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
public class bili extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
bili() {
// 創建模擬器實例,進程名建議依照實際進程名填寫,可以規避針對進程名的校驗
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.bilibili.app").build();
// 獲取模擬器的內存操作接口
final Memory memory = emulator.getMemory();
emulator.getSyscallHandler().setEnableThreadDispatcher(true);
// 設置系統類庫解析
memory.setLibraryResolver(new AndroidResolver(23));
// 創建Android虛擬機,傳入APK,Unidbg可以替我們做部分簽名校驗的工作
vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\danmaku\\bilibili.apk"));
// 加載目標SO
DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\danmaku\\libbili.so"), true); // 加載so到虛擬內存
//獲取本SO模塊的句柄,後續需要用它
module = dm.getModule();
vm.setJni(this); // 設置JNI
vm.setVerbose(true); // 打印日誌
dm.callJNI_OnLoad(emulator); // 調用JNI OnLoad
};
public static void main(String[] args) {
bili test = new bili();
System.out.println(test.getSign());
}
@Override
public boolean callBooleanMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
if ("java/util/Map->isEmpty()Z".equals(signature)) {
TreeMap<String, String> treeMap = (TreeMap<String, String>)dvmObject.getValue();
return treeMap.isEmpty();
}
return super.callBooleanMethod(vm, dvmObject, signature, varArg);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "java/util/Map->get(Ljava/lang/Object;)Ljava/lang/Object;":
StringObject keyobject = varArg.getObjectArg(0);
String key = keyobject.getValue();
TreeMap<String, String> treeMap = (TreeMap<String, String>)dvmObject.getValue();
String value = treeMap.get(key);
return new StringObject(vm, value);
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature){
case "com/bilibili/nativelibrary/SignedQuery->r(Ljava/util/Map;)Ljava/lang/String;":{
DvmObject<?> mapObject = varArg.getObjectArg(0);
TreeMap<String, String> mymap = (TreeMap<String, String>) mapObject.getValue();
String result = util.r(mymap);
return new StringObject(vm, result);
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
@Override
public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "com/bilibili/nativelibrary/SignedQuery-><init>(Ljava/lang/String;Ljava/lang/String;)V":
StringObject stringObject1 = varArg.getObjectArg(0);
StringObject stringObject2 = varArg.getObjectArg(1);
String str1 = stringObject1.getValue();
String str2 = stringObject2.getValue();
return vm.resolveClass("com/bilibili/nativelibrary/SignedQuery").newObject(new SignedQuery(str1, str2));
}
return super.newObject(vm, dvmClass, signature, varArg);
}
public class SignedQuery {
public final String a;
public final String b;
public SignedQuery(String str, String str2) {
this.a = str;
this.b = str2;
}
public String toString() {
String str = this.a;
if (str == null) {
return "";
}
if (this.b == null) {
return str;
}
return this.a + "&sign=" + this.b;
}
};
public String getSign(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
// 這裏傳入treemap
TreeMap<String, String> keymap = new TreeMap<String, String>();
keymap.put("ad_extra", "E0DA3BAB712CAB3902D558CFD4FEB865AB1563B7D61A4A57D02854BB9A4512C5E4737CA15664C505F3E63B954BB68837690B81FE77B2E7EB578F3E3D7C528439725348FE86527D7415BA2267FCCD2C95895AE80E69A960812FE60BC477171DAD34472EC31D010940A2876CADE887EA26FD5FBFB718D09F193D14732E54E42EB329EBF61CA33DE876AF24C03A7B9E89DB9C831AF6D1CB8B3B883AA3856AF08424AD8913BDE1207A5C2E010E23274ECCA7BC44814561E236325F0C4D2FFE069D2F9BF5B40411A3D26D0BB864EC87713391FAE1197ACFD402D6D386B035BF04995C280989B7305964F179E149067FA864F66FA856EFBD3C8A005F92F1D7C93D82BC8C5B4804DC54154F34E5820B2A481C3F33EEDDB4323C142359920F60686D95E715E30894676AEDE6933A31092E98F979157D267D580D056C3EDDD31934E762D7354A2E1959AAD44D1DDAF094B6E8F38C5450C6A164BD40AF2C3FB2E44BD8E0F914C7BA0FBA4FA553A2A1291BAF9E87FC528C2D53A6B3D3EFEA88F5C359119A31018AD879124BE62B4A4BCFEF98FA5EE1BB5675D9828D1B246A10841CF1F6D1CC1C13C3C11C7E37BCEF93E933395A1FA4D7AEA4CB79DC3ED1EC162C926CFE1157366100A3AF788CF3262DC381B620FB68913694FD332D7D555CD60E3AC54E2F0A5FC2C90530A27721664A64B008FAB0A6");
keymap.put("appkey", "1d8b6e7d45233436");
keymap.put("autoplay_card", "11");
keymap.put("build", "6180500");
keymap.put("c_locale", "zh_CN");
keymap.put("channel", "shenma069");
keymap.put("column", "2");
keymap.put("device_name", "MI 6");
keymap.put("device_type", "0");
keymap.put("flush", "8");
keymap.put("fnval", "400");
keymap.put("fnver", "0");
keymap.put("force_host", "0");
keymap.put("fourk", "1");
keymap.put("guidance", "0");
keymap.put("https_url_req", "0");
keymap.put("idx", "1682561936");
keymap.put("inline_danmu", "2");
keymap.put("inline_sound", "1");
keymap.put("login_event", "0");
keymap.put("mobi_app", "android");
keymap.put("network", "wifi");
keymap.put("open_event", "");
keymap.put("platform", "android");
keymap.put("player_net", "1");
keymap.put("pull", "false");
keymap.put("qn", "32");
keymap.put("recsys_mode", "0");
keymap.put("s_locale", "zh_CN");
keymap.put("splash_id", "");
keymap.put("statistics", "{\"appId\":1,\"platform\":3,\"version\":\"6.18.0\",\"abtest\":\"\"}");
keymap.put("ts", "1612693177");
DvmClass Map = vm.resolveClass("java/util/Map");
DvmClass AbstractMap = vm.resolveClass("java/util/AbstractMap",Map);
DvmObject<?> input_map = vm.resolveClass("java/util/TreeMap", AbstractMap).newObject(keymap);
list.add(vm.addLocalObject(input_map));
Number number = module.callFunction(emulator,0x1c97,list.toArray());
String result = vm.getObject(number.intValue()).getValue().toString();
return result;
}
}
util:
package com.danmaku;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
public class util {
private static final char[] f14884c = "0123456789ABCDEF".toCharArray();
private static boolean a(char c2, String str) {
return (c2 >= 'A' && c2 <= 'Z') || (c2 >= 'a' && c2 <= 'z') || !((c2 < '0' || c2 > '9') && "-_.~".indexOf(c2) == -1 && (str == null || str.indexOf(c2) == -1));
}
static String b(String str) {
return c(str, null);
}
static String c(String str, String str2) {
StringBuilder sb = null;
if (str == null) {
return null;
}
int length = str.length();
int i2 = 0;
while (i2 < length) {
int i3 = i2;
while (i3 < length && a(str.charAt(i3), str2)) {
i3++;
}
if (i3 != length) {
if (sb == null) {
sb = new StringBuilder();
}
if (i3 > i2) {
sb.append((CharSequence) str, i2, i3);
}
i2 = i3 + 1;
while (i2 < length && !a(str.charAt(i2), str2)) {
i2++;
}
try {
byte[] bytes = str.substring(i3, i2).getBytes("UTF-8");
int length2 = bytes.length;
for (int i4 = 0; i4 < length2; i4++) {
sb.append('%');
sb.append(f14884c[(bytes[i4] & 240) >> 4]);
sb.append(f14884c[bytes[i4] & 15]);
}
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
} else if (i2 == 0) {
return str;
} else {
sb.append((CharSequence) str, i2, length);
return sb.toString();
}
}
return sb == null ? str : sb.toString();
}
static String r(Map<String, String> map) {
String str;
if (!(map instanceof SortedMap)) {
map = new TreeMap<>(map);
}
StringBuilder sb = new StringBuilder(256);
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
if (!key.isEmpty()) {
sb.append(b(key));
sb.append("=");
String value = entry.getValue();
if (value == null) {
str = "";
} else {
str = b(value);
}
sb.append(str);
sb.append("&");
}
}
int length = sb.length();
if (length > 0) {
sb.deleteCharAt(length - 1);
}
if (length == 0) {
return null;
}
return sb.toString();
}
}
歐克。
4.直接補類
根據龍哥的博客,說還有另一種補環境的方式,重建類:
不再繼承abstrctjni和不再用setjni方法,改用vm.setDvmClassFactory(new ProxyClassFactory());
package com.danmaku;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
public class bili2 {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
bili2() {
// 創建模擬器實例,進程名建議依照實際進程名填寫,可以規避針對進程名的校驗
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.bilibili.app").build();
// 獲取模擬器的內存操作接口
final Memory memory = emulator.getMemory();
emulator.getSyscallHandler().setEnableThreadDispatcher(true);
// 設置系統類庫解析
memory.setLibraryResolver(new AndroidResolver(23));
// 創建Android虛擬機,傳入APK,Unidbg可以替我們做部分簽名校驗的工作
vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\danmaku\\bilibili.apk"));
// 加載目標SO
DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\danmaku\\libbili.so"), true); // 加載so到虛擬內存
//獲取本SO模塊的句柄,後續需要用它
module = dm.getModule();
// vm.setJni(this); // 設置JNI
vm.setDvmClassFactory(new ProxyClassFactory());
vm.setVerbose(true); // 打印日誌
dm.callJNI_OnLoad(emulator); // 調用JNI OnLoad
};
public static void main(String[] args) {
bili2 test = new bili2();
System.out.println(test.getSign());
}
public String getSign(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
// 這裏傳入treemap
TreeMap<String, String> keymap = new TreeMap<String, String>();
keymap.put("ad_extra", "E0DA3BAB712CAB3902D558CFD4FEB865AB1563B7D61A4A57D02854BB9A4512C5E4737CA15664C505F3E63B954BB68837690B81FE77B2E7EB578F3E3D7C528439725348FE86527D7415BA2267FCCD2C95895AE80E69A960812FE60BC477171DAD34472EC31D010940A2876CADE887EA26FD5FBFB718D09F193D14732E54E42EB329EBF61CA33DE876AF24C03A7B9E89DB9C831AF6D1CB8B3B883AA3856AF08424AD8913BDE1207A5C2E010E23274ECCA7BC44814561E236325F0C4D2FFE069D2F9BF5B40411A3D26D0BB864EC87713391FAE1197ACFD402D6D386B035BF04995C280989B7305964F179E149067FA864F66FA856EFBD3C8A005F92F1D7C93D82BC8C5B4804DC54154F34E5820B2A481C3F33EEDDB4323C142359920F60686D95E715E30894676AEDE6933A31092E98F979157D267D580D056C3EDDD31934E762D7354A2E1959AAD44D1DDAF094B6E8F38C5450C6A164BD40AF2C3FB2E44BD8E0F914C7BA0FBA4FA553A2A1291BAF9E87FC528C2D53A6B3D3EFEA88F5C359119A31018AD879124BE62B4A4BCFEF98FA5EE1BB5675D9828D1B246A10841CF1F6D1CC1C13C3C11C7E37BCEF93E933395A1FA4D7AEA4CB79DC3ED1EC162C926CFE1157366100A3AF788CF3262DC381B620FB68913694FD332D7D555CD60E3AC54E2F0A5FC2C90530A27721664A64B008FAB0A6");
keymap.put("appkey", "1d8b6e7d45233436");
keymap.put("autoplay_card", "11");
keymap.put("build", "6180500");
keymap.put("c_locale", "zh_CN");
keymap.put("channel", "shenma069");
keymap.put("column", "2");
keymap.put("device_name", "MI 6");
keymap.put("device_type", "0");
keymap.put("flush", "8");
keymap.put("fnval", "400");
keymap.put("fnver", "0");
keymap.put("force_host", "0");
keymap.put("fourk", "1");
keymap.put("guidance", "0");
keymap.put("https_url_req", "0");
keymap.put("idx", "1682561936");
keymap.put("inline_danmu", "2");
keymap.put("inline_sound", "1");
keymap.put("login_event", "0");
keymap.put("mobi_app", "android");
keymap.put("network", "wifi");
keymap.put("open_event", "");
keymap.put("platform", "android");
keymap.put("player_net", "1");
keymap.put("pull", "false");
keymap.put("qn", "32");
keymap.put("recsys_mode", "0");
keymap.put("s_locale", "zh_CN");
keymap.put("splash_id", "");
keymap.put("statistics", "{\"appId\":1,\"platform\":3,\"version\":\"6.18.0\",\"abtest\":\"\"}");
keymap.put("ts", "1612693177");
DvmClass Map = vm.resolveClass("java/util/Map");
DvmClass AbstractMap = vm.resolveClass("java/util/AbstractMap",Map);
DvmObject<?> input_map = vm.resolveClass("java/util/TreeMap", AbstractMap).newObject(keymap);
list.add(vm.addLocalObject(input_map));
Number number = module.callFunction(emulator,0x1c97,list.toArray());
String result = vm.getObject(number.intValue()).getValue().toString();
return result;
}
}
直接運行:
把需要的類全都放進去,SignedQuery、ContainerUtils、cv
記得把signedQuery放到他該有的類,其他就隨意了,他會自己在同級目錄查找,ok,直接出結果。這裏龍哥把ContainerUtils、cv類都單獨創建了該有的類名前綴,我這裏就沒有了,一樣出結果,感覺還是歸類在一起好點
這樣的效果就是,不需要去手動補需要的環境了(因爲需要的環境類整個都有了)
知識點總結
1.treemap需要注意下,在unidbg裏需要繼承map,abstractmap
2.varArg.getObjectArg(0)可以獲取該方法的參數,0,指第一個,1指第二個
StringObject stringObject1 = varArg.getObjectArg(0);
StringObject stringObject2 = varArg.getObjectArg(1);
String str1 = stringObject1.getValue();
String str2 = stringObject2.getValue();
3.unidbg有新的寫法,不繼承abstractjni,setjni的地方改成vm.setDvmClassFactory(new ProxyClassFactory());
這種方法適合java環境太多的情況,不用一個一個手動補,直接用java用到的類