大家好,我是小生,这次的app是一个国内某计划app, 功能相当全,界面也很美观,很实用,这个app我很欣赏。总共花了有三天晚上加一个白天,抓包分析,脱壳,过检测,手撕smail, 调试等, 做开发好久了,逆向有段时间没有接触了,很生疏了
就是会员太贵了,终身会员300多嘞!
【为该公司的权益考虑,不提供成品,也不提供app相关 信息】
(现在大大小小的app全都加壳,甚至一些颜色灰产的也加国内的这些壳!!! 动不动就抽取,dex2c,都不能愉快的好好玩耍了)
还有asserts目录下的 libjiagu.so 就知道是数字壳无疑了!
apktool解包,发现6月份的新版数字加固
脱壳用的fart改的脱壳机,详细过程就不赘述了
总共脱下来21个dex,一个个先脱进jadx中看看是否都是有用的,发现有两个全是壳相关的,剩下了19个
发现脱下来还是相对较完整的,里面也有损坏的部分,但影响不太大,
因为这次我需要的是里面的vip功能,按照惯例先搜isVip等字样
发现搜出来很多结果,不影响,排除掉本app的广告sdk和依赖的库,一个个看,看和用户相关的,发现两个类都是相关的
直接写hook,
只截取部分,
然后 frida启动!
我是先attach启动的,发现会闪退
换成去掉部分特征的strongr frida发现还是如此,
1.我又尝试了换端口,span启动,
2.hook libc.so中的 strstr,strcmp来去掉内存里的frida,gmain,gdbus等字样
3.hook 重定向/proc/xxx/maps
4.hook libc.so的exit
5.hook android.os.Process的killProcess
再配合上常用的几个过检测脚本还是一样闪退,感觉事情不简单了
提前说一嘴,这个frida检测不是在壳里,是在app的so里,还有hook这个业务代码要延迟一段时间执行,不然classloader还没有加载相关类。
既然不是在java层,那就是在native层检测的了,通常是hook android_dlopen_ext,观察加载到哪个so的时候退出就可以定位到了,
可以定位到是在libmxxxdesc.so中
然后hook pthread_create函数,尝试找到来自libmxxxdesc.so创建的检测线程
然后就一直卡在那了,一直也找不到来自该so的创建线程的调用,
下面的部分借鉴看雪的看雪bilibili frida过检测
把so放进ida中也没有发现有创建线程的导入符号
尝试从更早的时机,通过hook dlsym函数来看是否有通过dlsym来获取pthread_create地址来进行调用
发现确实调用了创建线程的函数,只不过不是直接调用,而是采用通过dlsym获取地址再调用
下面采用创建一个虚假的创建函数的地址返回,来欺骗目标so(还是来源于看雪bilibili frida过检测的思路和代码)
就过掉了检测
通过hook关键的函数发现确实可以达到付费vip的效果,但是部分界面显示的vip样式还是有点问题,
我逐个把dex脱进jadx中,进行查看,去除掉没用的dex, 发现可以去除掉两个全是数字壳的特征dex,
1.然后使用MT管理器把这21个dex替换了原来的dex
2.然后把asserts文件夹中的libjiagu.so 那四个数字壳的so文件删掉
3.然后把AndroidMinfest.xml中原来的com.stub.StubApp
为程序真正的入口com.xxxxxx
这个app是真的大,光androidMinfest文件就干出去将近5000行!!(后面改smail的时候很痛苦)
然后重打包编译,进行jarsinger签名,一气呵成,安装,闪退! 漂亮!
我一开始以为是不是有签名验证啊,我就再jadx中进行搜素packagemanager相关的,但都关系不太大,最后发现是脱壳还有数字的残留特征,
就是下面这种效果,1000多条!
可以用正则的方式匹配替换掉,这里很麻烦,我替换了整整有半个多小时,各种各样的,真恶心!
这里我是使用 一键正则 工具走捷径了(尽管这样,也很慢)
下面这两个可以通用替换掉一些,但还是会有很多很多很多漏网之鱼
其实这个app算我运气好,onCreate函数没有被抽取掉,很赏脸了!
再都完成替换之后,确认没有stubapp, stub/stub等数字壳特征之后,再进行重打包,签名,发现可以打开了,我测试了一下,里面有两个子页面有点问题,打开会闪退,不过我会用到的页面都正常,(这个app大大小小加起来有62个页面,那两个无所谓)
首先声明一下,我不会smail(以下纯现学现用,所以看着像屎一样很正常)
这一步就没有什么技术含量了,(对于我这种小卡拉米以及 这种简单的app而言),主要是耐心和细心,
这里我是采用mt管理器来进行编辑的,不得不说,确实很方便,但是改smail也很麻烦,要操作寄存器,改完还不知道,只能重打包后安装才能验证出来,一不小心改错就会闪退,前文说到有两个相关的类,一个有get set方法,很好处理,get的话直接
还有一个bean全是public字段,没有get set方法,而且引用的地方相当多,我没有办法在构造函数中进行赋值,因为后续会被覆盖掉,这里我有想到用抓包改包的方式,我在有root和xposed的测试机上试验过,没问题,但我想在没有root和xposed的环境使用,这种方案显然不可行
我只能在每一个用到的地方都进行修改,比如
之前一直采用的charles+postern方式抓包,用花哥的话说,走socket,靠近底层,能获取更多的上层流量
现在我改成了 Reqable小黄鸟来抓包,头一次用,挺方便的,也是要root,这个没得跑,
关于证书安装的问题,安卓7以后要手动remount,把证书移动到/system/etc/security/cacerts目录下
我试了好几次,小黄鸟都识别不到证书已安装,尽管64xxxk.0已经在系统证书目录中,后来我尝试移除charles证书,试了两次,重启过后可以了! 至于没有网络或者其他的问题导致无法安装证书,我的博客里有记载。
敢于尝试,就有成功的可能
文中所用到的部分过检测代码
function
hook_dlopenAndExt() {
Interceptor.attach(Module.findExportByName(
null
,
"dlopen"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
})
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
});
}
function
hook_dlopenAndExt() {
Interceptor.attach(Module.findExportByName(
null
,
"dlopen"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
})
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
});
}
function
create_fake_pthread_create() {
const fake_pthread_create = Memory.alloc(4096)
Memory.protect(fake_pthread_create, 4096,
"rwx"
)
Memory.patchCode(fake_pthread_create, 4096, code => {
const cw =
new
Arm64Writer(code, { pc: ptr(fake_pthread_create) })
cw.putRet()
})
return
fake_pthread_create
}
function
hook_dlsym() {
var
count = 0
console.log(
"=== HOOKING dlsym ==="
)
var
interceptor = Interceptor.attach(Module.findExportByName(
null
,
"dlsym"
),
{
onEnter:
function
(args) {
const name = ptr(args[1]).readCString()
console.log(
"[dlsym]"
, name)
if
(name ==
"pthread_create"
) {
count++
}
},
onLeave:
function
(retval) {
if
(count == 1) {
retval.replace(fake_pthread_create)
}
else
if
(count == 2) {
retval.replace(fake_pthread_create)
interceptor.detach()
}
}
}
)
return
Interceptor
}
function
hook_dlopen() {
var
interceptor = Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"[LOAD]"
, path)
if
(path.indexOf(
"libmxxxxxec.so"
) > -1) {
hook_dlsym()
}
}
}
}
)
return
interceptor
}
var
fake_pthread_create = create_fake_pthread_create()
var
dlopen_interceptor = hook_dlopen()
function
create_fake_pthread_create() {
const fake_pthread_create = Memory.alloc(4096)
Memory.protect(fake_pthread_create, 4096,
"rwx"
)
Memory.patchCode(fake_pthread_create, 4096, code => {
const cw =
new
Arm64Writer(code, { pc: ptr(fake_pthread_create) })
cw.putRet()
})
return
fake_pthread_create
}
function
hook_dlsym() {
var
count = 0
console.log(
"=== HOOKING dlsym ==="
)
var
interceptor = Interceptor.attach(Module.findExportByName(
null
,
"dlsym"
),
{
onEnter:
function
(args) {
const name = ptr(args[1]).readCString()
console.log(
"[dlsym]"
, name)
if
(name ==
"pthread_create"
) {
count++
}
},
onLeave:
function
(retval) {
if
(count == 1) {
retval.replace(fake_pthread_create)
}
else
if
(count == 2) {
retval.replace(fake_pthread_create)
interceptor.detach()
}
}
}
)
return
Interceptor
}
function
hook_dlopen() {
var
interceptor = Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"[LOAD]"
, path)
if
(path.indexOf(
"libmxxxxxec.so"
) > -1) {
hook_dlsym()
}
}
}
}
)
return
interceptor
}
var
fake_pthread_create = create_fake_pthread_create()
var
dlopen_interceptor = hook_dlopen()
public
class
Uxxxxfo
implements
Serializable {
public
List<AdX> adxList;
public
boolean
axxxxeVip;
public
String alxxxcon;
public
String axxxge;
public
CheckFreeVipInfo cxxxnfo;
public
boolean
evxxp;
public
int
exxxxay;
public
String id;
public
boolean
isPoxxxp;
public
boolean
isxxxit;
public
boolean
isVip;
......
......
public
class
Uxxxxfo
implements
Serializable {
public
List<AdX> adxList;
public
boolean
axxxxeVip;
public
String alxxxcon;
public
String axxxge;
public
CheckFreeVipInfo cxxxnfo;
public
boolean
evxxp;
public
int
exxxxay;
public
String id;
public
boolean
isPoxxxp;
public
boolean
isxxxit;
public
boolean
isVip;
......
......
function
loadGson() {
Java.openClassFile(
"/data/local/tmp/xiaosheng-dex-tool.dex"
).load();
var
js = Java.use(
"com.xiaosheng.tool.json.Gson"
);
var
gson = js.$
new
();
return
gson;
}
function
hook_dlopen_ext() {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
}
}
);
}
function
hook_dlopenAndExt() {
Interceptor.attach(Module.findExportByName(
null
,
"dlopen"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
})
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
});
}
function
hook_open() {
var
pth = Module.findExportByName(
null
,
"open"
);
Interceptor.attach(ptr(pth), {
onEnter:
function
(args) {
this
.filename = args[0];
console.log(
""
,
this
.filename.readCString())
if
(
this
.filename.readCString().indexOf(
".so"
) != -1) {
args[0] = ptr(0)
}
}, onLeave:
function
(retval) {
return
retval;
}
})
}
function
hookProcess() {
var
process = Java.use(
"android.os.Process"
);
process.killProcess.implementation =
function
(pid) {
console.log(
"kill process:"
+ pid)
}
}
function
hookExit() {
var
ByPassTracerPid =
function
() {
var
fgetsPtr = Module.findExportByName(
"libc.so"
,
"fgets"
);
var
fgets =
new
NativeFunction(fgetsPtr,
'pointer'
, [
'pointer'
,
'int'
,
'pointer'
]);
Interceptor.replace(fgetsPtr,
new
NativeCallback(
function
(buffer, size, fp) {
var
retval = fgets(buffer, size, fp);
var
bufstr = Memory.readUtf8String(buffer);
if
(bufstr.indexOf(
"TracerPid:"
) > -1) {
Memory.writeUtf8String(buffer,
"TracerPid:\t0"
);
console.log(
"tracerpid replaced: "
+ Memory.readUtf8String(buffer));
}
return
retval;
},
'pointer'
, [
'pointer'
,
'int'
,
'pointer'
]));
};
}
function
hook_Pthreadfunc() {
var
pthread_creat_addr = Module.findExportByName(
"libc.so"
,
"pthread_create"
)
Interceptor.attach(pthread_creat_addr, {
onEnter(args) {
console.log(
"call pthread_create..."
)
let func_addr = args[2]
console.log(
"The thread function address is "
+ func_addr)
try
{
console.log(
'pthread_create called from:\n'
+ Thread.backtrace(
this
.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress)
.join(
'\n'
)
+
'\n'
);
}
catch
(e) {
}
}
})
}
function
hookBaseExit() {
function
main() {
const openPtr = Module.getExportByName(
'libc.so'
,
'open'
);
const open =
new
NativeFunction(openPtr,
'int'
, [
'pointer'
,
'int'
]);
var
readPtr = Module.findExportByName(
"libc.so"
,
"read"
);
var
read =
new
NativeFunction(readPtr,
'int'
, [
'int'
,
'pointer'
,
"int"
]);
var
fakePath =
"/data/local/tmp/fakeMap"
;
var
file =
new
File(fakePath,
"w"
);
var
buffer = Memory.alloc(512);
Interceptor.replace(openPtr,
new
NativeCallback(
function
(pathnameptr, flag) {
var
pathname = Memory.readUtf8String(pathnameptr);
var
realFd = open(pathnameptr, flag);
if
(pathname.indexOf(
"maps"
) != 0) {
while
(parseInt(read(realFd, buffer, 512)) !== 0) {
var
oneLine = Memory.readCString(buffer);
if
(oneLine.indexOf(
"tmp"
) === -1) {
file.write(oneLine);
}
}
var
filename = Memory.allocUtf8String(fakePath);
return
open(filename, flag);
}
var
fd = open(pathnameptr, flag);
return
fd;
},
'int'
, [
'pointer'
,
'int'
]));
}
setImmediate(main)
}
function
replace_str() {
var
pt_strstr = Module.findExportByName(
"libc.so"
,
'strstr'
);
var
pt_strcmp = Module.findExportByName(
"libc.so"
,
'strcmp'
);
Interceptor.attach(pt_strstr, {
onEnter:
function
(args) {
var
str1 = args[0].readCString();
var
str2 = args[1].readCString();
if
(str2.indexOf(
"tmp"
) !== -1 ||
str2.indexOf(
"frida"
) !== -1 ||
str2.indexOf(
"gum-js-loop"
) !== -1 ||
str2.indexOf(
"gmain"
) !== -1 ||
str2.indexOf(
"gdbus"
) !== -1 ||
str2.indexOf(
"pool-frida"
) !== -1 ||
str2.indexOf(
"linjector"
) !== -1) {
this
.hook =
true
;
}
}, onLeave:
function
(retval) {
if
(
this
.hook) {
retval.replace(0);
}
}
});
Interceptor.attach(pt_strcmp, {
onEnter:
function
(args) {
var
str1 = args[0].readCString();
var
str2 = args[1].readCString();
if
(str2.indexOf(
"tmp"
) !== -1 ||
str2.indexOf(
"frida"
) !== -1 ||
str2.indexOf(
"gum-js-loop"
) !== -1 ||
str2.indexOf(
"gmain"
) !== -1 ||
str2.indexOf(
"gdbus"
) !== -1 ||
str2.indexOf(
"pool-frida"
) !== -1 ||
str2.indexOf(
"linjector"
) !== -1) {
this
.hook =
true
;
}
}, onLeave:
function
(retval) {
if
(
this
.hook) {
retval.replace(0);
}
}
})
}
function
anti_maps() {
var
pt_strstr = Module.findExportByName(
"libc.so"
,
'strstr'
);
var
pt_strcmp = Module.findExportByName(
"libc.so"
,
'strcmp'
);
Interceptor.attach(pt_strstr, {
onEnter:
function
(args) {
var
str1 = args[0].readCString();
var
str2 = args[1].readCString();
if
(str2.indexOf(
"REJECT"
) !== -1 || str2.indexOf(
"frida"
) !== -1) {
this
.hook =
true
;
}
},
onLeave:
function
(retval) {
if
(
this
.hook) {
retval.replace(0);
}
}
});
Interceptor.attach(pt_strcmp, {
onEnter:
function
(args) {
var
str1 = args[0].readCString();
var
str2 = args[1].readCString();
if
(str2.indexOf(
"REJECT"
) !== -1 || str2.indexOf(
"frida"
) !== -1) {
this
.hook =
true
;
}
},
onLeave:
function
(retval) {
if
(
this
.hook) {
retval.replace(0);
}
}
});
}
const STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {
constructor() {
this
.handle = Memory.alloc(STD_STRING_SIZE);
}
dispose() {
const [data, isTiny] =
this
._getData();
if
(!isTiny) {
Java.api.$
delete
(data);
}
}
disposeToString() {
const result =
this
.toString();
this
.dispose();
return
result;
}
toString() {
const [data] =
this
._getData();
return
data.readUtf8String();
}
_getData() {
const str =
this
.handle;
const isTiny = (str.readU8() & 1) === 0;
const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
return
[data, isTiny];
}
}
function
prettyMethod(method_id, withSignature) {
const result =
new
StdString();
Java.api[
'art::ArtMethod::PrettyMethod'
](result, method_id, withSignature ? 1 : 0);
return
result.disposeToString();
}
function
hook_libc_exit() {
var
exit = Module.findExportByName(
"libc.so"
,
"exit"
);
console.log(
"native:"
+ exit);
Interceptor.attach(exit, {
onEnter:
function
(args) {
try
{
console.log(Thread.backtrace(
this
.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join(
"\n"
));
}
catch
(e) {
console.log(e)
}
},
onLeave:
function
(retval) {
}
});
}
function
anti_exit() {
const exit_ptr = Module.findExportByName(
null
,
'_exit'
);
console.log(
"anti_kill, kill_ptr:"
+ exit_ptr)
if
(
null
== exit_ptr) {
return
;
}
Interceptor.replace(exit_ptr,
new
NativeCallback(
function
(code) {
if
(
null
==
this
) {
return
0;
}
console.log(
"kill debug entry,lr"
)
return
0;
},
'int'
, [
'int'
,
'int'
]));
}
function
anti_kill() {
const kill_ptr = Module.findExportByName(
null
,
'kill'
);
console.log(
"anti_kill, kill_ptr:"
+ kill_ptr)
if
(
null
== kill_ptr) {
return
;
}
Interceptor.replace(kill_ptr,
new
NativeCallback(
function
(ptid, code) {
if
(
null
==
this
) {
return
0;
}
console.log(
"kill debug entry,lr"
)
return
0;
},
'int'
, [
'int'
,
'int'
]));
}
function
loadGson() {
Java.openClassFile(
"/data/local/tmp/xiaosheng-dex-tool.dex"
).load();
var
js = Java.use(
"com.xiaosheng.tool.json.Gson"
);
var
gson = js.$
new
();
return
gson;
}
function
hook_dlopen_ext() {
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
),
{
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
}
}
);
}
function
hook_dlopenAndExt() {
Interceptor.attach(Module.findExportByName(
null
,
"dlopen"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
})
Interceptor.attach(Module.findExportByName(
null
,
"android_dlopen_ext"
), {
onEnter:
function
(args) {
var
pathptr = args[0];
if
(pathptr !== undefined && pathptr !=
null
) {
var
path = ptr(pathptr).readCString();
console.log(
"load "
+ path);
}
},
onLeave:
function
(retval) {
}
});
}
function
hook_open() {
var
pth = Module.findExportByName(
null
,
"open"
);
Interceptor.attach(ptr(pth), {
onEnter:
function
(args) {
this
.filename = args[0];
console.log(
""
,
this
.filename.readCString())
if
(
this
.filename.readCString().indexOf(
".so"
) != -1) {
args[0] = ptr(0)
}
}, onLeave:
function
(retval) {
return
retval;
}
})
}
function
hookProcess() {
var
process = Java.use(
"android.os.Process"
);
process.killProcess.implementation =
function
(pid) {
console.log(
"kill process:"
+ pid)
}
}
function
hookExit() {
var
ByPassTracerPid =
function
() {
var
fgetsPtr = Module.findExportByName(
"libc.so"
,
"fgets"
);
var
fgets =
new
NativeFunction(fgetsPtr,
'pointer'
, [
'pointer'
,
'int'
,
'pointer'
]);
Interceptor.replace(fgetsPtr,
new
NativeCallback(
function
(buffer, size, fp) {
var
retval = fgets(buffer, size, fp);
var
bufstr = Memory.readUtf8String(buffer);
if
(bufstr.indexOf(
"TracerPid:"
) > -1) {
Memory.writeUtf8String(buffer,
"TracerPid:\t0"
);
console.log(
"tracerpid replaced: "
+ Memory.readUtf8String(buffer));
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-8-11 23:19
被xsSkyFall编辑
,原因: