前言

久違的看看安卓題,順便水一篇文章,有任何問題歡迎指出!

分析

java層

拉入jadx,很容易可以定位到關鍵邏輯,經典的加密對比。

image.png

加密函數enc在native層,而且加載了2個so。

嘗試分別將2個so都拉入ida,但都未發現enc,顯然是動態注冊的。

image.png

image.png

網上抄的一個frida腳本,用來hook動態注冊的native函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// com.n1ctf2024.ezapk
function find_RegisterNatives(params) {
let symbols = Module.enumerateSymbolsSync("libart.so");
let addrRegisterNatives = null;
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];

//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
hook_RegisterNatives(addrRegisterNatives)
}
}

}

function hook_RegisterNatives(addrRegisterNatives) {

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
let java_class = args[1];
let class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);

let methods_ptr = ptr(args[2]);

let method_count = parseInt(args[3]);
for (let i = 0; i < method_count; i++) {
let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

let name = Memory.readCString(name_ptr);
let sig = Memory.readCString(sig_ptr);
let symbol = DebugSymbol.fromAddress(fnPtr_ptr)
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
}
}
});
}
}

setImmediate(find_RegisterNatives);

發現是在libnative1.so!0x1b148

image.png

native層

0x1b148函數如下,重命名成enc,做了一些奇奇怪怪的操作,等等再分析

image.png

在此之前,由於比較好奇另一個so libnative2.so有什麼用,於是順手查看了.init_array段,看看有沒有偷偷在做壞事。( 因為在java層沒有發現任何對libnative2.so函數的調用,只有調用libnative1.so的,因此合理懷疑對libnative2.so的操作是在libnative1.so中進行的,而.init_array就是一個很好的時機。 )

sub_1B540.init_array段最後一個函數,果然發現了libnative2.so的字樣。

image.png

sub_1B540一開始先從/proc/self/maps裡獲取libnative.so的基址

image.png

然後調用sub_1B000將libnative2.so的一些信息初始化,保存在libnative2_base

image.png

sub_1B000的實現挺像linker加載so的流程中的prelink_image

image.png

sub_1B540最後部份如下,大概是將libnative2.so裡的rand函數返回值固定為0xE9

image.png

image.png

看完.init_array後,可以回到enc函數了( 其實上面的那些東西看不看都沒有所謂,單純是我好奇想看看,純動調其實就能解決這題 )。

動調後會發現,第1個紅框大概是在取libnative2.so某個函數的offset,然後第2個紅框調用該函數對輸入進行加密( 通過base + offset得到函數的具體地址 )。

image.png

一步一步調試完後會發現,總共從libnative2.so裡取了3個加密函數。

第1個的offset是0x106C,只是簡單的異或,異或的值是上述分析的0xE9

image.png

第2個的offset是0x12C0,一眼RC4。

image.png

第3個的offset是0x1AB0,一眼base64。

image.png

解密結果

最終結果:MysT3r10us_C0d3_2024N1CTF!

image.png

結語

這題目挺好的,感覺我以往遇到的題更偏向於算法的部份,而這題對算法分析的要求不高,反而多了一些android逆向實際可能會遇到的東西,挺有意思的。