0x01 過(guò)frida檢測(cè)
frida可以說(shuō)是逆向里面很受歡迎的工具了,你可以在運(yùn)行的時(shí)候得到幾乎你想要的所有東西,函數(shù)地址、內(nèi)存數(shù)據(jù)、java實(shí)例,根據(jù)我們的需要去修改程序的運(yùn)行邏輯等等,但是太流行也不好,迎來(lái)了各種檢測(cè)。
- ptrace占坑、進(jìn)程名檢測(cè)、端口檢測(cè)。(這繞過(guò)太簡(jiǎn)單了)
- D-Bus通信協(xié)議的檢測(cè)。
- maps、fd檢測(cè)。
- App中線程名的檢測(cè)。
直接拿出App,看看他到底怎么檢測(cè)的。節(jié)省時(shí)間,直接用hluda-server,修改一下運(yùn)行端口,以spawn方式注入frida。(hluda-server的好處在于,他所生成的各種so庫(kù)名字,去掉了frida等特征字段,可以很好的繞過(guò)maps和fd的檢測(cè)。)
直接給我干掉了??猜測(cè)有沒(méi)有可能是D-Bus通信協(xié)議的檢測(cè),App向每一個(gè)端口都發(fā)送了D-Bus認(rèn)證消息,那肯定會(huì)利用strcmp( )或者strstr( )函數(shù)進(jìn)行檢測(cè)回復(fù)的消息。那么就hook一下看看。
同樣的方法hook一下strstr函數(shù)
?不僅沒(méi)有任何輸出,app還是直接給我干掉了。
思考一下,不是D-Bus協(xié)議檢測(cè)、不是ptrace占坑、進(jìn)程名檢測(cè)、端口檢測(cè)、fd和maps檢測(cè)利用hluda繞過(guò)了,emm,等等不一定,跟師傅討論一下,又搜了幾個(gè)文章,發(fā)現(xiàn)有的app檢測(cè)非常惡心,只要是maps和fd中存在/data/local/tmp/,甚至只有tmp的字段,app就給kill掉。因?yàn)檫@個(gè)目錄對(duì)于安卓逆向工作來(lái)說(shuō),是一個(gè)比較敏感的目錄。hluda-server和frida-server都會(huì)在/data/local/tmp/目錄下生成一個(gè)包含frida所需要的so庫(kù)等文件。所以當(dāng)app一旦發(fā)現(xiàn)了加載了/data/local/tmp下的任何東西,直接就掛掉。
那怎么辦呢?讓該文件夾生成到別的目錄下,有一個(gè)-d參數(shù),試了好多次,有些問(wèn)題,一直都是在tmp目錄下遞歸生成。所以便想到,你既然去檢測(cè)maps,肯定是要讀取里面的內(nèi)容,然后尋找是否有該目錄的字段咯。
那就直接hook open函數(shù),將原程序的maps文件中一切帶有tmp的行都過(guò)濾掉,剩余的內(nèi)容輸出到另一個(gè)文件中,最后修改open的返回值,指向新生成的文件。完美!
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/data/******/maps";
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)
?完美過(guò)掉!
0x02 SO層算法逆向
1、抓個(gè)包看看,里面都有啥東西?
登錄的時(shí)候,用戶名:123456789,密碼:123456
根據(jù)字段名字的分析,重點(diǎn)關(guān)注的是sign字段(看著像是個(gè)hash散列),其次這個(gè)password應(yīng)該是經(jīng)過(guò)加密處理的。
?2、so算法逆向(passwrod參數(shù))
既然是分析so層算法,具體的Java層的分析和定位就不浪費(fèi)時(shí)間分析了。那么如何定位so文件和具體的函數(shù)呢。在Java層在和so層函數(shù)交互的時(shí)候,就是通過(guò)的JNI的機(jī)制,所以在so層函數(shù)加密數(shù)據(jù)之后,一定會(huì)把加密后的數(shù)據(jù)返回,通過(guò)JNIEnv下的NewStringUTF函數(shù)返回給Java層,所以hook一下這個(gè)函數(shù),并且輸出堆棧。
function hook_NewStringUTF(){
var artModule = Process.findModuleByName("libart.so");
var symbols = artModule.enumerateSymbols();
var newStringUTF = null;
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];
if(symbol.name.indexOf("NewStringUTF") != -1 && symbol.name.indexOf("Check") == -1){
console.log(symbol.name);
newStringUTF = symbol.address;
}
}
Interceptor.attach(newStringUTF, {
onEnter : function(args){
console.log(args[1].readCString());
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n')
},onLeave : function(retval){
}
})
}
?觀看輸出,尤其是堆棧信息,發(fā)現(xiàn)是libNativeHelper.so中的后面的是函數(shù)在文件中的偏移地址。
IDA反編譯找到相應(yīng)函數(shù),反編譯之后進(jìn)行分析,怎么分析最快呢,當(dāng)然是從你已經(jīng)擁有的數(shù)據(jù)去反推未知的數(shù)據(jù)。(從后往前分析)
找到了NewStringUTF的調(diào)用,v19就是我們的FfQn1pwmgRY=,那么v19是怎么來(lái)的(猜測(cè)可能是base64,驗(yàn)證一下),找到上面的sub_1FEC,去里面看看。
看到這里面有一串字符,和base64很像。hook一下看看
輸入
輸出
難道上面的16進(jìn)制數(shù)據(jù),就是編碼前的數(shù)據(jù)?經(jīng)過(guò)驗(yàn)證之后,這個(gè)函數(shù)就是base64的編碼函數(shù),編碼的數(shù)據(jù)以16進(jìn)制形式傳入。
那這個(gè)?15f427d69c268116 數(shù)據(jù)又是什么?繼續(xù)向上找
進(jìn)入這個(gè)函數(shù)一看,看不懂,不知道是干啥的太復(fù)雜了,hook看看。
這第一個(gè)參數(shù)不就是我們傳入的密碼么
第二個(gè)參數(shù)不就是剛才的16進(jìn)制數(shù)據(jù)嗎,其他參數(shù)看不懂了,猜測(cè)該函數(shù)應(yīng)該是某種加密
而還有一個(gè)v22 = 0xEFCDAB9078563412LL;難道是某種密鑰或者IV?
v23里面的數(shù)據(jù)很多,這是什么東西,跟蹤v23的有關(guān)函數(shù)去看看
?進(jìn)入函數(shù)里面查看,發(fā)現(xiàn)感覺(jué)有點(diǎn)眼熟,是DES加密?dest又是什么,dest是通過(guò)a3傳過(guò)來(lái)的,經(jīng)過(guò)hook之后,a3的數(shù)據(jù)是***************(程序的密鑰,不方便展出)。
這個(gè)時(shí)候就有很多想法了,一個(gè)簡(jiǎn)單的數(shù)據(jù),經(jīng)過(guò)函數(shù)處理之后,出現(xiàn)了大量的內(nèi)容,同時(shí)v23還是加密函數(shù)中的參數(shù)。難道是子密鑰的生成??感覺(jué)很強(qiáng)烈,進(jìn)入查看果然很像!
那這么一看v23就是子密鑰咯,那v22很有可能就是IV向量了,去驗(yàn)證一下
驗(yàn)證是正確的(一定要注意字節(jié)序的問(wèn)題)。
最終得出結(jié)論,將我們輸入的密碼,經(jīng)過(guò)DES/CBC模式加密后,再經(jīng)過(guò)base64編碼就是password的值。
0x03?so算法逆向(sign參數(shù))
根據(jù)最開(kāi)始的hook? NewStringUTF,找到對(duì)應(yīng)的函數(shù)位置。
不再一步步的分析了,直接找到特征
好像是MD5的初始化常量啊。
hook?
可以得到明文,然后對(duì)比一下數(shù)據(jù)包中
可以看到是 captcha +?captchaId +?dateline +?deviceIdentifier +?info +?password +?username + “ef2vx#sf*^FlklSD*9sdf(m$&qw%d7po”? 拼接起來(lái)的數(shù)據(jù)。
這里的數(shù)據(jù)就是經(jīng)過(guò)md5加密后的內(nèi)容,驗(yàn)證一下。
?
?可以得到結(jié)論,sign的值就是將數(shù)據(jù)包中的
?captcha +?captchaId +?dateline +?deviceIdentifier +?info +?password +?username + “ef2vx#sf*^FlklSD*9sdf(m$&qw%d7po”?
拼接之后,在進(jìn)行md5散列的結(jié)果。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-797212.html
至此,整個(gè)逆向結(jié)束。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-797212.html
到了這里,關(guān)于Android逆向——過(guò)frida檢測(cè)+so層算法逆向的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!