SO逆向之Unidbg模拟执行入门

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

抓包

Charles本地证书

安卓8

1
2
3
4
5
cd /data/misc/user/0/cacerts-added/
mount -o remount,rw /
chmod 777 *
cp * /etc/security/cacerts/
mount -o remount,ro /

安卓7

1
2
3
4
5
cd /data/misc/user/0/cacerts-added/
mount -o rw,remount /system
chmod 777 *
cp * /etc/security/cacerts/
mount -o ro,remount /system

ssl pinning

image-20211105204214989

xposed+justTrustMe.apk破解ssl pinning

绿洲3.5.8

com.weibo.xvideo.NativeApi.s

分析

根据白龙大佬的博客,绿洲app脱壳后拿到target.dex

image-20211103173220627

该函数位于 oasiscoreliboasiscore.so中,ida打开该so,搜索不到函数,说明非静态绑定,因为静态绑定的函数需要以固定格式明明。该目标方法是以动态绑定的方式,所以需要先找到so中动态绑定目标的地址,才能定位目标方法的实现。

image-20211103174750071

native方法动态绑定的实现都放在JNI_OnLoad方法中,首先定位到JNI_OnLoad方法,再去找动态绑定的实现,搜索JNI进入后F5反会汇编发现已经被OLLVM混淆的亲妈都不认识了

image-20211103175405096

通常使用hook_RegisterNatives获取动态绑定的地址,本文使用Unidbg方法,项目中的src/test/java/com/bytedance/frameworks/core/encrypt路径中有一个TTEncrypt测试用例,直接执行其中的main方法,说明项目导入成功。image-20211103174547409

Unidbg

搭建模板

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
import com.github.unidbg.linux.android.dvm.AbstractJni;
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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

// 继承AbstractJni类
public class LvZhou extends AbstractJni{
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

LvZhou() {
// 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.sina.oasis").build();
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\resources\\demo\\lvzhou\\lvzhou.apk"));
// 加载目标SO
DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\resources\\demo\\lvzhou\\liboasiscore.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) {
LvZhou test = new LvZhou();
}

}

image-20211103175949709

运行LvZhou.main后打印log中发现JNI OnLoad中主要做了签名校验和动态绑定。在emulator.createDalvikVM不传入apk时填入null,样本在JNI OnLoad中所做的签名校验,就需要我们手动补环境校验了。

Unidbg封装了相关方法执行JNI函数以及有符号函数等,但需要区分类方法和实例方法

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
public static void main(String[] args) {
LvZhou test = new LvZhou();
System.out.println("Native方法返回值:" + test.call_native_s());
}
/**
* public final native String s(@NotNull byte[] bArr, boolean z)
* 字节数组需要裹上unidbg的包装类,并加到本地变量里
* @return
*/
public String call_native_s(){
// 构造jni方法的参数
List<Object> arg_list = new ArrayList<>(10);
// 参数1:JNIEnv *env
arg_list.add(vm.getJNIEnv());
// 参数2:jobject或jclass 一般用不到,直接填0即可
arg_list.add(0);
// 参数3:bytes
String input = "aid=01A-khBWIm48A079Pz_DMW6PyZR8" +
"uyTumcCNm4e8awxyC2ANU.&cfrom=28B529501" +
"0&cuid=5999578300&noncestr=46274W9279Hr1" +
"X49A5X058z7ZVz024&platform=ANDROID&timestamp" +
"=1621437643609&ua=Xiaomi-MIX2S__oasis__3.5.8_" +
"_Android__Android10&version=3.5.8&vid=10190135" +
"94003&wm=20004_90024";
byte[] input_bytes = input.getBytes(StandardCharsets.UTF_8);
ByteArray input_byte_array = new ByteArray(vm,input_bytes);
arg_list.add(vm.addLocalObject(input_byte_array));
// 参数4:boolean false 填入0
arg_list.add(0);
// 参数准备完毕 调用目标方法
Number number = module.callFunction(emulator,0xc365,arg_list.toArray())[0];
return vm.getObject(number.intValue()).getValue().toString();
}

运行测试

image-20211105113357770

ExAndroidNativeEmu

1
2
3
4
git clone git@github.com:maiyao1988/ExAndroidNativeEmu.git
set https_proxy=socks5://127.0.0.1:1080
set http_proxy=socks5://127.0.0.1:1080
pip install -r requirements.txt

lvzhou.py

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
import posixpath
from androidemu.emulator import Emulator, logger
from androidemu.java.classes.string import String

# Initialize emulator
emulator = Emulator(
vfp_inst_set=True,
vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs")
)

# 加载SO
lib_module = emulator.load_library("lib\\liboasiscore.so")

# find My module
for module in emulator.modules:
if "liboasiscore" in module.filename:
base_address = module.base
logger.info("base_address=> 0x%08x - %s" % (module.base, module.filename))
break

# run jni onload
emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00)
# 准备参数
a1 = "aid=01A-khBWIm48A079Pz_DMW6PyZR8uyTumcCNm4e8awxyC2ANU.&cfrom=28B5295010&cuid=5999578300&noncestr=46274W9279Hr1X49A5X058z7ZVz024&platform=ANDROID&timestamp=1621437643609&ua=Xiaomi-MIX2S__oasis__3.5.8__Android__Android10&version=3.5.8&vid=1019013594003&wm=20004_90024"
# 通过地址直接调用
result = emulator.call_native(module.base + 0xC365, emulator.java_vm.jni_env.address_ptr, 0x00, String(a1).getBytes(emulator, String("utf-8")), 0)
# 打印结果
print("result:"+ result._String__str)

image-20211105153307550

结果和Unidbg一致。

FindHash

输出为32位字符串,可能是哈希算法。

将findhash.xml和findhash.py放到IDA的plugins目录下,使用IDA7.5加载完so后,在IDA工具栏依次点击edit->plugin->findhash即可运行

image-20211105163103136

1
2
adb shell dumpsys activity top  查看包名为com.sina.oasis
frida -U -f com.sina.oasis -l liboasiscore_findhash_1636100950.js --no-pause

修改frida脚本中setImmediate(main,5000);由于刚启动时,此时目标so还没有加载到内存中,修改后在目标so加载完成后进行hook。根据输出找到对应的函数并分析,很快就定位到0x8AB2这个函数,并且它是MD5_Update函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook_md5_update(){
var targetSo = Module.findBaseAddress("liboasiscore.so");
let relativePtr = 0x8AB2 + 1;

console.log("Enter");
let funcPtr = targetSo.add(relativePtr);
Interceptor.attach(funcPtr,{
onEnter:function (args) {
// console.log(args[0]);
console.log(args[2]);
console.log(hexdump(args[1],{length:args[2].toInt32()}));

},onLeave:function (retval){
}
})
}
frida -UF -l hookComputeMd5.js

结果如下

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
 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
b2fa5800 59 50 31 56 74 79 26 24 58 6d 2a 6b 4a 6b 6f 52 YP1Vty&$Xm*kJkoR
b2fa5810 2c 4f 70 6b 26 61 69 64 3d 30 31 41 2d 6b 68 42 ,Opk&aid=01A-khB
b2fa5820 57 49 6d 34 38 41 30 37 39 50 7a 5f 44 4d 57 36 WIm48A079Pz_DMW6
b2fa5830 50 79 5a 52 38 75 79 54 75 6d 63 43 4e 6d 34 65 PyZR8uyTumcCNm4e
b2fa5840 38 61 77 78 79 43 32 41 4e 55 2e 26 63 66 72 6f 8awxyC2ANU.&cfro
b2fa5850 6d 3d 32 38 42 35 32 39 35 30 31 30 26 63 75 69 m=28B5295010&cui
b2fa5860 64 3d 35 39 39 39 35 37 38 33 30 30 26 6e 6f 6e d=5999578300&non
b2fa5870 63 65 73 74 72 3d 4a 32 33 33 39 67 41 43 79 30 cestr=J2339gACy0
b2fa5880 44 35 6b 33 32 39 35 33 71 30 31 67 74 66 36 78 D5k32953q01gtf6x
b2fa5890 30 38 31 39 26 70 6c 61 74 66 6f 72 6d 3d 41 4e 0819&platform=AN
b2fa58a0 44 52 4f 49 44 26 74 69 6d 65 73 74 61 6d 70 3d DROID&timestamp=
b2fa58b0 31 36 32 31 35 32 36 32 39 38 31 32 37 26 75 61 1621526298127&ua
b2fa58c0 3d 58 69 61 6f 6d 69 2d 4d 49 58 32 53 5f 5f 6f =Xiaomi-MIX2S__o
b2fa58d0 61 73 69 73 5f 5f 33 2e 35 2e 38 5f 5f 41 6e 64 asis__3.5.8__And
b2fa58e0 72 6f 69 64 5f 5f 41 6e 64 72 6f 69 64 31 30 26 roid__Android10&
b2fa58f0 76 65 72 73 69 6f 6e 3d 33 2e 35 2e 38 26 76 69 version=3.5.8&vi
b2fa5900 64 3d 31 30 31 39 30 31 33 35 39 34 30 30 33 26 d=1019013594003&
b2fa5910 77 6d 3d 32 30 30 30 34 5f 39 30 30 32 34 wm=20004_90024
0x1a
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
bbe3c322 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
bbe3c332 00 00 00 00 00 00 00 00 00 00 ..........
0x8
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
bae15ed8 f0 08 00 00 00 00 00 00

明文是从aid开始的,前面多了一块,这一块每次运行都不变,所以猜测它是盐,使用逆向之友Cyberchef测试一下

img

Frida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hookSign(){
Java.perform(function () {
var NativeApi = Java.use('com.weibo.xvideo.NativeApi');
// 使用系统工具类将byte数组转成hex、utf8.
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
NativeApi.s.implementation = function (str1, str2){
var result = this.s(str1, str2);
console.log("str:"+ByteString.of(str1).utf8())
console.log("hex:"+ByteString.of(str1).hex())
console.log(result);
return result;
}
});
}
frida -UF -l hookAndCallLvzhou.js
文章作者: J
文章链接: http://onejane.github.io/2021/11/03/SO逆向之Unidbg模拟执行入门/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏