正常情况下,http请求是charles可以获取请求信息的。
但是,有的app可能不走网络设定的代理,比如说MM应用市场
如何确定不走代理呢,你wifi设置了charles代理,抓不到包,然后把代理软件关了,再运行app。发现app还是能正常运行,说明此app网络不走代理
此时我们需要使用app proxydroid 代理机器人,去把手机上所有的app网络强制走代理机器人设置的端口
只用设置地址和端口就可以了
将代理切换打开,后将wifi设置的代理关闭,再进行app的抓取,此时会发现数据请求包已经走charles了(反正我的手机没成功,显示未root)
问了王平大佬,说没必要用这个,用httpcany可以直接再手机抓包(我还不会,下次补写)
第二种就是ssl ping(单向认证),ssl认证的问题,就是app不认可charles证书,证书不通过,此时你访问app就会保网络未链接,或者抓到的包显示unknown
这时我们需要一个app 叫justtrustme ,github上有,从github上下载,安装好以后,在xposed里面勾选上,重启设备,再进行网络抓包就可以抓到数据了
(注意:这种justTrustme只适用于安卓7.0以下,安卓7。0用过之后无法正常访问浏览器,安卓7.0需要吧证书放到根目录下就可以了,文章末尾有写怎么做,)
第三种就是ssl ping(双向认证)正常情况下是单向认证,也就说客户端是不需要有CA证书的,客户端只效验从服务器发过来的证书,
如果是双向认证吗,客户端是需要有CA证书的, 并且服务器会效验客户端发过来的CA证书,此时justtruestme就不行了, 我们需要在charles倒入此app的CA证书
我们用apktool 把进行反编译,把编译后的文件打开,ca证书一般都是以.p12或者.Pem结尾,一般都放在assets资产文件目录里。
我们找到此证书,打开charles
在ssl proxy setting里面
选择add
host 一般是*.appyuming.com 这种的, 代表的是要抓取 的app数据域名
port一般是443 代表https
我这里填写* *不知道对不对
此处要输入client.p12的证书密码。证书密码我们需要用frida hook技术获取,代码如下
import frida
import sys
i = 0
ext = \'\'
def on_message(message, data):
global i, ext
if (message[\'type\'] == \'send\' and \'event\' in message[\'payload\']):
if (message[\'payload\'][\'event\'] == \'+found\'):
i += 1
print("\n[+] Hooked keystore" + str(i) + "...")
elif (message[\'payload\'][\'event\'] == \'+type\'):
print(" [+] Cert Type: " + \'\'.join(message[\'payload\'][\'certType\']))
if (message[\'payload\'][\'certType\'] == \'PKCS12\'):
ext = \'.jks\'
elif (message[\'payload\'][\'event\'] == \'+pass\'):
print(" [+] Password: " + \'\'.join(message[\'payload\'][\'password\']))
elif (message[\'payload\'][\'event\'] == \'+write\'):
print(" [+] Writing to file: keystore" + str(i) + ext)
f = open(\'keystore\' + str(i) + ext, \'wb\')
f.write(bytes.fromhex(message[\'payload\'][\'cert\']))
f.close()
else:
print(message)
hook_root = """
Java.perform(
function(){
var DeviceUtils = Java.use("utils.DeviceUtils");
//DeviceUtils.isDeviceRooted.implementation = function(){
// var flg = this.isDeviceRooted();
//send("flg is:"+flg);
// return flg;
//};
});
"""
# 双向认证 打印出证书需要的密码
dy_spt = """
setTimeout(function() {
Java.perform(function () {
var keyStoreLoadStream = Java.use(\'java.security.KeyStore\')[\'load\'].overload(\'java.io.InputStream\', \'[C\');
/* following function hooks to a Keystore.load(InputStream stream, char[] password) */
keyStoreLoadStream.implementation = function(stream, charArray) {
/* sometimes this happen, I have no idea why, tho... */
if (stream == null) {
/* just to avoid interfering with app\'s flow */
this.load(stream, charArray);
return;
}
/* just to notice the client we\'ve hooked a KeyStore.load */
send({event: \'+found\'});
/* read the buffer stream to a variable */
var hexString = readStreamToHex (stream);
/* send KeyStore type to client shell */
send({event: \'+type\', certType: this.getType()});
/* send KeyStore password to client shell */
send({event: \'+pass\', password: charArray});
/* send the string representation to client shell */
send({event: \'+write\', cert: hexString});
/* call the original implementation of \'load\' */
this.load(stream, charArray);
/* no need to return anything */
}
});
},0);
/* following function reads an InputStream and returns an ASCII char representation of it */
function readStreamToHex (stream) {
var data = [];
var byteRead = stream.read();
while (byteRead != -1)
{
data.push( (\'0\' + (byteRead & 0xFF).toString(16)).slice(-2) );
/* <---------------- binary to hex ---------------> */
byteRead = stream.read();
}
stream.close();
return data.join(\'\');
}
"""
hook_open = """
Interceptor.attach(Module.findExportByName(null, "fopen"), {
onEnter: function(args) {
//console.log("Interceptor attached onEnter...");
var name = Memory.readUtf8String(args[0]);
if (name.indexOf("proc") == -1) {
send("open("+Memory.readUtf8String(args[0])+","+args[1]+")");
}
},
onLeave: function(args) {
//console.log("Interceptor attached onLeave...");
}
})"""
hook_assert="""
Java.perform(function () {
var AssetManager = Java.use("android.content.res.AssetManager");
var FileInputStream = Java.use("java.io.FileInputStream");
AssetManager.open.overload("java.lang.String").implementation = function(str) {
send("hook asset:"+str);
if(str.endsWith(".xxx")){
return FileInputStream.$new("/data/local/tmp/xxxxx");
}
return this.open(str)
}
});"""
oncreate_script = """
//打印调用堆栈
function printstack() {
send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
}
//array 转成 string
function array2string(array){
var buffer = Java.array(\'byte\', array);
//console.log(buffer.length);
var result = "";
for(var i = 0; i < buffer.length; ++i){
result+= (String.fromCharCode(buffer[i]));
}
return result;
}
Java.perform(
function () {
var CertificatePinner = Java.use(\'com.squareup.okhttp.CertificatePinner\');
CertificatePinner.Builder.implementation = function (bytesarray) {
send(\'I am here 0:\');
this.Builder();
printstack();
};
}
);
"""
test_module_script = """
var walk = Process.enumerateModules();
for (var i = 0; i<walk.length; i++){
send(walk[i].name);
}
var base_address = Module.findBaseAddress("libc.so");
send(\'base_address:\'+base_address);
var mod_address = Module.findExportByName("libc.so" , "dlopen");
send(\'mod_address:\'+mod_address);
var lib_module = Process.findModuleByAddress(base_address);
send("lib_module_name:"+lib_module.name);
Interceptor.attach(mod_address, {
onEnter: function(args) {
send("open("+Memory.readUtf8String(args[0])+","+args[1]+")");
},
onLeave:function(retval){
send("retval:"+retval);
}
});
"""
# process = frida.get_usb_device().attach(\'com.iCitySuzhou.suzhou001\')
# script = process.create_script(oncreate_script)
# script.on(\'message\', on_message)
# print(\'[*] Running CTF\')
# script.load()
# sys.stdin.read()
# device = frida.get_usb_device()
# pid = device.spawn([\'com.iCitySuzhou.suzhou001\'])
# process = device.attach(pid)
# script = process.create_script(oncreate_script)
# script.on(\'message\', on_message)
# print(\'[*] Running CTF\')
# script.load()
# device.resume(pid)
# sys.stdin.read()
device = frida.get_usb_device(-1)
pid = device.spawn([\'cn.soulapp.android\'])
process = device.attach(pid)
script = process.create_script(dy_spt)
script.on(\'message\', on_message)
print(\'[*] Running CTF\')
script.load()
device.resume(pid)
sys.stdin.read()
这是打印的结果
把password填写进去就好了
这步导入证书让服务端信任证书,我们还需要让客户端信任证书,老样子,justtrustme 一起作用,再打开app进行抓包 就可以了(因app损坏,未测试,此处无图)
注意事项:
1.安卓7.0(包括7.0)以后,安卓系统不再信任用户安装的证书。(例如 fiddler/charles的代理抓包证书) mumu
模拟器
解决7.0以后用户证书不信任问题两种方法:
(1)root手机,把代理证书放到系统证书根目录下
用户CA证书目录
/data/misc/user/0/cacerts-added
系统CA证书目录
/system/etc/security/cacerts
问题:
有的手机root /system 分区是只能读 仍然不能把证书放到系统根目录, 比如魅族pro5
df 命令
mount -o rw,remount /system
(2) hook 系统方法,强行让系统信任用户证书:
比如:使用xposed框架的justtrustme模块 和 frida的DroidSSLUnpinning
tricks:
1.优先使用安卓系统低版本抓包
优先使用安卓4.0 5.0 6.0抓包
2.优先使用APP低版本抓包
比如微信7.0以下 在安卓4.0-6.0上 容易抓包微信小程序
优先使用能用的APP最低版本
猿人学
APP历史版本:
https://wap.pp.cn/
apkpure.com
当安卓抓包很艰难时,试试iOS抓包
3.工欲善其事必先利其器
工具准备充分fiddler/charles/wirseshark/模拟器4.0 5.0 6.0/root了的安卓手机 4.0 5.0 6.0 7.0/苹果手机
猿人学
参考猿人学