这篇文章上次修改于 1093 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
原文 -> https://blog.csdn.net/qq_38851536/article/details/118397129
前言
这是SO逆向入门实战教程的第十篇,总共会有十三篇,十三个实战
- 通过该样本可以充分学习如何在unidbg中补充环境朋友zh3nu11和我共同完成了这篇内容,感谢
准备
首先我们发现了init函数,它应该就是SO的初始化函数,其余的函数看名字也都很易懂
Init模拟执行
我们首先用unidbg跑通Init初始化函数,先搭建基本框架
package com.article10;
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.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class SecurityUtil extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public SecurityUtil(){
emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("base.apk"));
DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
module = dm.getModule();
vm.setJni(this);
vm.setVerbose(true);
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
SecurityUtil test = new SecurityUtil();
}
}
运行产生如下报错
红框即报错原因,内存错误,箭头所指是根本原因,样本中open了这两个文件,但找不到,所以我们需要补充这两个文件
我们需要逐一做理解
1.proc/23638/xxx是什么
proc文件系统由内核提供,它是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。系统中当前运行的每一个进程都有对应的一个目录在 proc下,以进程的 PID号为目录名,它们是读取进程信息的接口。
此处的23638就是unidbg中APP的当前进程,每次运行,unidbg都会随机化给一个进程PID,就像Linux系统中一样
我修改了src/main/java/com/github/unidbg/AbstractEmulator.java中的如图位置,使PID固定,因为PID不停变动可能会影响后续分析,但这不是必须的操作
2.此处读取cmdline做什么
在Android系统中,进程的cmdline返回应用的进程名
那么此处的目的就很明显了,验证环境是否是”自己“的环境,防止应用被重打包
3.在unidbg中如何填补
proc 文件系统是伪文件系统,其目录下的所有文件,读写和正常文件没差别,所以在unidbg中做好文件的重定向就行了
三步走,实现IOResolver接口,注册接口,实现resolve方法并补充上我们的逻辑
package com.article10;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class SecurityUtil extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public SecurityUtil(){
emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
emulator.getSyscallHandler().addIOResolver(this);
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("base.apk"));
DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
module = dm.getModule();
int pid = emulator.getPid();
System.out.println("APP pid:"+pid);
vm.setJni(this);
vm.setVerbose(true);
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
SecurityUtil test = new SecurityUtil();
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
}
return null;
}
}
那status呢?
该文件包含该进程的众多信息:可执行文件名、当前状态、PID和 PPID、实际及有效的 UID和 GID、内存使用情况、以及其他
我们可以随便看一个
root@hammerhead:/proc/9884 # cat status
Name: adb
State: S (sleeping)
Tgid: 9884
Pid: 9884
PPid: 1
TracerPid: 0
Uid: 2000 2000 2000 2000
Gid: 2000 2000 2000 2000
FDSize: 32
Groups: 1003 1004 1007 1011 1015 1028 3001 3002 3003 3006
VmPeak: 4012 kB
VmSize: 2992 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 880 kB
VmRSS: 880 kB
VmData: 1196 kB
VmStk: 136 kB
VmExe: 104 kB
VmLib: 1304 kB
VmPTE: 8 kB
VmSwap: 0 kB
Threads: 2
SigQ: 1/12274
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000001000
SigCgt: 000000000000a4f8
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: fffffff0000000c0
Cpus_allowed: f
Cpus_allowed_list: 0-3
voluntary_ctxt_switches: 7
nonvoluntary_ctxt_switches: 38
一般而言,样本检测status是为了其中的TracerPid字段
TracerPid反调试的原理就是检测这个字段是否为0,为0说明没有被调试,不为0说明正在被调试,检测调试器直接退出就可以达到反调试的效果
所以我们常常这么处理——只返回TracerPid字段
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
}
if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, "TracerPid:\t0\n".getBytes()));
}
return null;
}
有人可能会担心不保险,万一其他字段也被用到了呢,样本找不到这些字段导向错误逻辑怎么办?
这个情况是存在的,我们马上就说,先运行看看效果,emm,似乎跑通了
我们终于可以开始执行init函数了,但我打算先看看这个getNameByPid函数
首先Frida Call 测试一下
function callgetPid(pid){
var securityUtil = null;
Java.perform(function () {
Java.choose("ctrip.android.security.SecurityUtil", {
//枚举时调用
onMatch:function(instance){
//打印实例
securityUtil = instance;
console.log("find instance")
},
//枚举完成后调用
onComplete:function() {
console.log("end")
}});
var result = securityUtil.getNameByPid(pid);
console.log(result);
})
}
看一下PID
结果是ip.android.viewunidbg主动调用测试一下
package com.article10;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class SecurityUtil extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public SecurityUtil(){
emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
emulator.getSyscallHandler().addIOResolver(this);
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("base.apk"));
DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
module = dm.getModule();
int pid = emulator.getPid();
System.out.println("APP pid:"+pid);
vm.setJni(this);
vm.setVerbose(true);
dm.callJNI_OnLoad(emulator);
}
public void callgetNameByPid(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(emulator.getPid());
Number number = module.callFunction(emulator, 0xee01, list.toArray())[0];
String name = vm.getObject(number.intValue()).getValue().toString();
System.out.println(name);
}
public static void main(String[] args) {
SecurityUtil test = new SecurityUtil();
test.callgetNameByPid();
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
}
if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
return FileResult.<AndroidFileIO>success(new ByteArrayFileIO(oflags, pathname, "TracerPid:\t0\n".getBytes()));
}
return null;
}
}
结果是0,实际上,这就是status没补齐全带来的锅
这是APP真实的status
bullhead:/proc/19929 # cat status
Name: ip.android.view
State: R (running)
Tgid: 19929
Pid: 19929
PPid: 17506
TracerPid: 0
Uid: 10148 10148 10148 10148
Gid: 10148 10148 10148 10148
FDSize: 512
Groups: 3002 3003 9997 20148 50148
VmPeak: 2224800 kB
VmSize: 2180604 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 354920 kB
VmRSS: 322600 kB
VmData: 375124 kB
VmStk: 8192 kB
VmExe: 20 kB
VmLib: 209888 kB
VmPTE: 2024 kB
VmSwap: 2952 kB
Threads: 122
SigQ: 2/6517
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000001204
SigIgn: 0000000000000000
SigCgt: 00000006400096fc
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
Seccomp: 2
Cpus_allowed: 0f
Cpus_allowed_list: 0-3
Mems_allowed: 1
Mems_allowed_list: 0
voluntary_ctxt_switches: 123395
nonvoluntary_ctxt_switches: 42
样本正是读取其中的Name,所以unidbg补环境一定要心细和谨慎,处处可能出问题
看一下修改后的重定向方法
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
}
if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, ("Name: ip.android.view\n" +
"State: R (running)\n" +
"Tgid: "+emulator.getPid()+"\n" +
"Pid: "+emulator.getPid()+"\n" +
"PPid: 17506\n" +
"TracerPid: 0\n" +
"Uid: 10148 10148 10148 10148\n" +
"Gid: 10148 10148 10148 10148\n" +
"FDSize: 512\n" +
"Groups: 3002 3003 9997 20148 50148\n" +
"VmPeak: 2224800 kB\n" +
"VmSize: 2185240 kB\n" +
"VmLck: 0 kB\n" +
"VmPin: 0 kB\n" +
"VmHWM: 354920 kB\n" +
"VmRSS: 324572 kB\n" +
"VmData: 379340 kB\n" +
"VmStk: 8192 kB\n" +
"VmExe: 20 kB\n" +
"VmLib: 209888 kB\n" +
"VmPTE: 2020 kB\n" +
"VmSwap: 3012 kB\n" +
"Threads: 127\n" +
"SigQ: 2/6517\n" +
"SigPnd: 0000000000000000\n" +
"ShdPnd: 0000000000000000\n" +
"SigBlk: 0000000000001204\n" +
"SigIgn: 0000000000000000\n" +
"SigCgt: 00000006400096fc\n" +
"CapInh: 0000000000000000\n" +
"CapPrm: 0000000000000000\n" +
"CapEff: 0000000000000000\n" +
"CapBnd: 0000000000000000\n" +
"CapAmb: 0000000000000000\n" +
"Seccomp: 2\n" +
"Cpus_allowed: 0f\n" +
"Cpus_allowed_list: 0-3\n" +
"Mems_allowed: 1\n" +
"Mems_allowed_list: 0\n" +
"voluntary_ctxt_switches: 21102\n" +
"nonvoluntary_ctxt_switches: 20849").getBytes()));
}
return null;
}
接下来步入正题——执行init方法!增改内容如下,并运行
public void callInit(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
list.add(vm.addLocalObject(context));
module.callFunction(emulator, 0x36a85, list.toArray());
};
public static void main(String[] args) {
SecurityUtil test = new SecurityUtil();
test.callInit();
}
补JAVA环境是unidbg中的基础活,但它是有技巧的,善用JNITrace可以补的又快又好,比如本篇的unidbg环境,就是在1h内补好的如果补环境有问题或者不会补,欢迎评论区或者课上交流讨论
见招拆招补充几个后
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":{
return vm.resolveClass("java/io/File").newObject(signature);
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "java/io/File->getPath()Ljava/lang/String;":{
System.out.println("PATH:"+dvmObject.getValue());
if(dvmObject.getValue().equals("android/os/Environment->getExternalStorageDirectory()Ljava/io/File;")){
return new StringObject(vm, "/mnt/sdcard");
}
if(dvmObject.getValue()=="android/content/Context->getFilesDir()Ljava/io/File;"){
return new StringObject(vm, "/data/data/ctrip.android.view/files");
}
}
case "android/content/Context->getPackageResourcePath()Ljava/lang/String;":
return new StringObject(vm, "/data/app/ctrip.android.view-fM4xyjk_eygpJsiITNW4JA==/base.apk");
case "android/content/Context->getFilesDir()Ljava/io/File;":
return vm.resolveClass("java/io/File").newObject(signature);
case "android/content/Context->getAssets()Landroid/content/res/AssetManager;":
return new AssetManager(vm, signature);
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
报错了,这次看不出啥明显错误
日志全开再看看,好像更迷糊了
但其实这个问题我们早就讲过了,Android中通过libandroid.so对Assets资源文件进行操作,日志中可以看到”getAssets“等字眼
但是由于libandroid.so的依赖SO太多了,unidbg很难一一处理,所以资源文件相关的处理会报错
为此unidbg做了一个折中的解决防范——可以自己注册虚拟模块,或者叫虚拟SO,libandroid.so已经由作者实现了,实现了常用的几个Assets操作的API
只用加上这一行就行,需要注意必须在样本SO之前加载或者叫注册进去
继续运行,为什么还报错
是这样的,样本在通过虚拟SO读取资源文件,资源的读取有四种模式
/** Available access modes for opening assets with {@link AAssetManager_open} */
enum {
/** No specific information about how data will be accessed. **/
AASSET_MODE_UNKNOWN = 0,
/** Read chunks, and seek forward and backward. */
AASSET_MODE_RANDOM = 1,
/** Read sequentially, with an occasional forward seek. */
AASSET_MODE_STREAMING = 2,
/** Caller plans to ask for a read-only buffer with all data. */
AASSET_MODE_BUFFER = 3
};
但作者只实现了模式1和2,而此处使用mode 0
mode 0没有啥特殊处理要做,我们直接合并入mode 2和3的逻辑即可
继续运行,即已经过了这个坑
有的人可能会困惑,Native层读写资源文件做什么?其实这是常见做法,比如把key编码进资源文件的一张图片里,Native层去读取这个资源文件获取key
下面就是继续补JAVA环境,使用JNItrace辅助,飞速补完
补完后代码整体长这样
package com.article10;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
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.api.AssetManager;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class SecurityUtil extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public SecurityUtil(){
emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
emulator.getSyscallHandler().addIOResolver(this);
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
vm = emulator.createDalvikVM(new File("base.apk"));
new AndroidModule(emulator, vm).register(memory);
DalvikModule dm = vm.loadLibrary(new File("libscmain.so"), true);
module = dm.getModule();
int pid = emulator.getPid();
System.out.println("APP pid:"+pid);
vm.setJni(this);
vm.setVerbose(true);
dm.callJNI_OnLoad(emulator);
}
public void callgetNameByPid(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(emulator.getPid());
Number number = module.callFunction(emulator, 0xee01, list.toArray())[0];
String name = vm.getObject(number.intValue()).getValue().toString();
System.out.println(name);
}
public void callInit(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
list.add(vm.addLocalObject(context));
module.callFunction(emulator, 0x36a85, list.toArray());
};
public static void main(String[] args) {
Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
SecurityUtil test = new SecurityUtil();
test.callInit();
}
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":{
return vm.resolveClass("java/io/File").newObject(signature);
}
case "okio/zz->b(I)Ljava/lang/String;":{
int key = varArg.getInt(0);
switch (key){
case 1:{
return new StringObject(vm, "353626076466627");
}
case 0:{
return new StringObject(vm, "8cff8823cf19b5ec");
}
case 101:{
return new StringObject(vm, "25483");
}
case 103:{
return new StringObject(vm, "1920*1080");
}
case 104:{
return new StringObject(vm, "");
}
case 102:{
return new StringObject(vm, "17637");
}
case 105:{
return new StringObject(vm, "WIFI");
}
case 106:{
return new StringObject(vm, "0.0.0.0:0");
}
case 8:{
return new StringObject(vm, "0.0.0.0:0");
}
case 9:{
return new StringObject(vm, "");
}
case 10:{
return new StringObject(vm, "00:00:00:00:00:00");
}
case 107:{
return new StringObject(vm, "[full-100]");
}
case 108:{
return new StringObject(vm, "78");
}
}
System.out.println("okio/zz->b(I) Key:"+key);
}
case "java/net/NetworkInterface->getByName(Ljava/lang/String;)Ljava/net/NetworkInterface;":{
String name = null;
DvmObject<?> namedvm = varArg.getObject(0);
if(namedvm!=null){
name = (String) namedvm.getValue();
}
return vm.resolveClass("java/net/NetworkInterface").newObject(name);
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "java/io/File->getPath()Ljava/lang/String;":{
System.out.println("PATH:"+dvmObject.getValue());
if(dvmObject.getValue().equals("android/os/Environment->getExternalStorageDirectory()Ljava/io/File;")){
return new StringObject(vm, "/mnt/sdcard");
}
if(dvmObject.getValue()=="android/content/Context->getFilesDir()Ljava/io/File;"){
return new StringObject(vm, "/data/data/ctrip.android.view/files");
}
}
case "android/content/Context->getPackageResourcePath()Ljava/lang/String;":
return new StringObject(vm, "/data/app/ctrip.android.view-fM4xyjk_eygpJsiITNW4JA==/base.apk");
case "android/content/Context->getFilesDir()Ljava/io/File;":
return vm.resolveClass("java/io/File").newObject(signature);
case "android/content/Context->getAssets()Landroid/content/res/AssetManager;":
return new AssetManager(vm, signature);
case "java/net/NetworkInterface->getHardwareAddress()[B":
byte[] result = hexStringToByteArray("64BC0C65AA1E");
return new ByteArray(vm, result);
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
if (("proc/"+emulator.getPid()+"/cmdline").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, "ctrip.android.view".getBytes()));
}
if (("proc/" + emulator.getPid() + "/status").equals(pathname)) {
return FileResult.success(new ByteArrayFileIO(oflags, pathname, ("Name: ip.android.view\n" +
"State: R (running)\n" +
"Tgid: "+emulator.getPid()+"\n" +
"Pid: "+emulator.getPid()+"\n" +
"PPid: 17506\n" +
"TracerPid: 0\n" +
"Uid: 10148 10148 10148 10148\n" +
"Gid: 10148 10148 10148 10148\n" +
"FDSize: 512\n" +
"Groups: 3002 3003 9997 20148 50148\n" +
"VmPeak: 2224800 kB\n" +
"VmSize: 2185240 kB\n" +
"VmLck: 0 kB\n" +
"VmPin: 0 kB\n" +
"VmHWM: 354920 kB\n" +
"VmRSS: 324572 kB\n" +
"VmData: 379340 kB\n" +
"VmStk: 8192 kB\n" +
"VmExe: 20 kB\n" +
"VmLib: 209888 kB\n" +
"VmPTE: 2020 kB\n" +
"VmSwap: 3012 kB\n" +
"Threads: 127\n" +
"SigQ: 2/6517\n" +
"SigPnd: 0000000000000000\n" +
"ShdPnd: 0000000000000000\n" +
"SigBlk: 0000000000001204\n" +
"SigIgn: 0000000000000000\n" +
"SigCgt: 00000006400096fc\n" +
"CapInh: 0000000000000000\n" +
"CapPrm: 0000000000000000\n" +
"CapEff: 0000000000000000\n" +
"CapBnd: 0000000000000000\n" +
"CapAmb: 0000000000000000\n" +
"Seccomp: 2\n" +
"Cpus_allowed: 0f\n" +
"Cpus_allowed_list: 0-3\n" +
"Mems_allowed: 1\n" +
"Mems_allowed_list: 0\n" +
"voluntary_ctxt_switches: 21102\n" +
"nonvoluntary_ctxt_switches: 20849").getBytes()));
}
return null;
}
}
接下来遇到报错
样本在读取apk文件,继续在resolve方法中做好重定向的工作
if (("/data/app/ctrip.android.view-fM4xyjk_eygpJsiITNW4JA==/base.apk").equals(pathname)) {
return FileResult.success(new SimpleFileIO(oflags, new File("yourAPkpath\\xxx.apk"), pathname));
}
继续运行,可能再补充一些JAVA环境,整体没有报错了,似乎init补充完了
日志结尾看到样本对这些文件进行访问,虽然没有报错,但我们可能会好奇,它在做什么呢?
事实上,如果存在这些文件,则说明手机已经Root,样本在试图访问这些文件,来判断系统是否被Root了
我们这里不做任何处理
unidbg模拟执行SimpleSign
先Frida call一下,参数二固定,参数一可自行修改
function callSimpleSign(){
var securityUtil = null;
Java.perform(function () {
Java.choose("ctrip.android.security.SecurityUtil", {
//枚举时调用
onMatch:function(instance){
//打印实例
securityUtil = instance;
console.log("find instance")
},
//枚举完成后调用
onComplete:function() {
console.log("end")
}});
var input1 = stringToBytes("7be9f13e7f5426d139cb4e5dbb1fdba7")
var result = securityUtil.simpleSign(input1, "getdata");
console.log(result);
})
}
结果恒为75位长度,且相同输入也会有不同输出,原因未知
接下来unidbg中测试
public void callSimpleSign(){
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
String input = "7be9f13e7f5426d139cb4e5dbb1fdba7";
byte[] inputByte = input.getBytes(StandardCharsets.UTF_8);
ByteArray inputByteArray = new ByteArray(vm,inputByte);
list.add(vm.addLocalObject(inputByteArray));
list.add(vm.addLocalObject(new StringObject(vm, "getdata")));
Number number = module.callFunction(emulator, 0x80735, list.toArray())[0];
System.out.println(vm.getObject(number.intValue()).getValue().toString());
};
public static void main(String[] args) {
Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
SecurityUtil test = new SecurityUtil();
test.callInit();
System.out.println("call SimpleSign");
test.callSimpleSign();
}
结果也很顺利,如果报错,则根据缺失的JAVA环境继续补即可
考虑一个小问题,即使明文一致,结果也一直在变,这是为什么呢?有哪些原因可能导致这个结果呢?
(PS:提示,时间戳、内联汇编
尾声
相关程序和资料放群里,防止遇到恶意举报
没有评论