协议枚举、爆破及算法模拟
很多的加密算法是在so中编写的,并且加了混淆,死磕的话逆向难度是比较大的,在不必要的情况下可以尝试去主动调用算法实现目的:
测试环境:
测试用的demo
这里使用我们前面写的脚本来抓包,因为协议都是TCP所以可以使用我们前面写的抓取javaTCP的代码,再加上libc
上代码
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();
hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function printJavaStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, " \n ");
LogPrint("=============================" + name + " Java Stack strat=======================");
LogPrint(replaceStr);
LogPrint("=============================" + name + " Java Stack end======================= \n ");
Exception.$dispose();
}
});
}
function printNativeStack(context, name) {
var array = Thread.backtrace(context, Backtracer.ACCURATE);
var trace = array.map(DebugSymbol.fromAddress).join("\n");
LogPrint("=============================" + name + " Native Stack strat=======================");
LogPrint(trace);
LogPrint("=============================" + name + " Native Stack end======================= \n ");
}
function isprintable(value) {
if (value >= 32 && value <= 126) {
return true;
}
return false;
}
function getsocketdetail(fd) {
var type = Socket.type(fd);
if (type !== null) {
var peer = Socket.peerAddress(fd);
var local = Socket.localAddress(fd);
// return `type:${type}, address:${JSON.stringify(peer)}, local:${JSON.stringify(local)}`;
return ("type: " + type + ", address: " + JSON.stringify(peer) + ", local: " + JSON.stringify(local));
}
return "unknown";
}
function hooktcp() {
Java.perform(function () {
var SocketClass = Java.use('java.net.Socket');
SocketClass.$init.overload('java.lang.String', 'int').implementation = function (arg0, arg1) {
console.log("[" + Process.getCurrentThreadId() + "]new Socket connection: " + arg0 + ",port: " + arg1);
printJavaStack('tcp connect...')
return this.$init(arg0, arg1);
}
var SocketInputStreamClass = Java.use('java.net.SocketInputStream');
//socketRead0
SocketInputStreamClass.socketRead0.implementation = function (arg0, arg1, arg2, arg3, arg4) {
var size = this.socketRead0(arg0, arg1, arg2, arg3, arg4);
//console.log("[" + Process.getCurrentThreadId() + "]socketRead0:size:" + size + ",content:" + JSON.stringify(arg1));
var bytearray = Java.array('byte', arg1);
var content = '';
for (var i = 0; i < size; i++) {
if (isprintable(bytearray[i])) {
content = content + String.fromCharCode(bytearray[i]);
}
}
var socketimpl = this.impl.value;
var address = socketimpl.address.value;
var port = socketimpl.port.value;
console.log("\naddress:" + address + ",port: " + port + "\n" + JSON.stringify(this.socket.value) + "\n[" + Process.getCurrentThreadId() + "]receive: " + content);
printJavaStack('socketRead0')
return size;
}
var SocketOutPutStreamClass = Java.use('java.net.SocketOutputStream');
SocketOutPutStreamClass.socketWrite0.implementation = function (arg0, arg1, arg2, arg3) {
var result = this.socketWrite0(arg0, arg1, arg2, arg3);
//console.log("[" + Process.getCurrentThreadId() + "]socketWrite0:len:" + arg3 + "--content:" + JSON.stringify(arg1));
var bytearray = Java.array('byte', arg1);
var content = '';
for (var i = 0; i < arg3; i++) {
if (isprintable(bytearray[i])) {
content = content + String.fromCharCode(bytearray[i]);
}
}
var socketimpl = this.impl.value;
var address = socketimpl.address.value;
var port = socketimpl.port.value;
console.log("send address:" + address + ",port: " + port + "[" + Process.getCurrentThreadId() + "]send: " + content);
console.log("\n" + JSON.stringify(this.socket.value) + "\n[" + Process.getCurrentThreadId() + "]send: " + content);
printJavaStack('socketWrite0')
return result;
}
})
}
function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var recvfrom_addr = libcmodule.getExportByName("recvfrom");
var sendto_addr = libcmodule.getExportByName("sendto");
// console.log(`${recvfrom_addr} --- ${sendto_addr}`);
console.log(recvfrom_addr + "---" + sendto_addr);
Interceptor.attach(recvfrom_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
this.arg3 = args[3];
this.arg4 = args[4];
this.arg5 = args[5];
LogPrint("go into libc.so->recvfrom");
var result = getsocketdetail(this.arg0.toInt32());
if (result.indexOf("udp") > 0) {
handleUdp('recvfrom', this.arg4, this.arg5);
}
printNativeStack(this.context, "recvfrom");
},
onLeave: function (retval) {
var size = retval.toInt32();
if (size > 0) {
var result = getsocketdetail(this.arg0.toInt32());
// console.log(`${result} --- libc.so->recvfrom: ${hexdump(this.arg1, { length: size })}`);
console.log(result + "--- libc.so->recvfrom: " + hexdump(this.arg1, { length: size }))
}
LogPrint("leave libc.so->recvfrom");
}
});
Interceptor.attach(sendto_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
this.arg3 = args[3];
this.arg4 = args[4];
this.arg5 = args[5];
LogPrint("go into libc.so->sendto");
var result = getsocketdetail(this.arg0.toInt32());
if (result.indexOf("udp") > 0) {
handleUdp('sendto', this.arg4, this.arg5);
}
printNativeStack(this.context, "sendto");
},
onLeave: function (retval) {
var size = this.arg2.toInt32();
if (size > 0) {
var result = getsocketdetail(this.arg0.toInt32());
// console.log(`${result} --- libc.so->sendto: ${hexdump(this.arg1, { length: size })}`);
console.log(result + "--- libc.so->sendto: " + hexdump(this.arg1, { length: size }))
}
LogPrint("leave libc.so->sendto");
}
});
}
function main() {
hooktcp();
hooklibc();
}
setImmediate(main)
抓包:目标App是我们前面说的测试用App
frida -UF -l hooksocket.js --no-pause -o log.txt
测试结果:
- 根据
hooklibc()
调用printNativeStack()
打印的结果来看,App使用的都是Java层的函数,并没有直接调用libc或者更深层的函数。 - 这样就不需要hook
libc
了,可以在脚本中删除这个函数了
修改脚本后继续测试:输入账号密码,点击登陆
可以看到发送的数据,看起来像是base64加密的
以及发包函数
a.a.a.a$a
以及收包函数a.a.a.a$c
,这里的调试信息也没有抹去,可以定位到它的地址在214行以及
java.lang.Thread.run
的地址在764行
用GDA分析apk
- 有360的壳
那就先脱壳,在协议分析中很多时候都要先脱壳才能分析:这里脱壳要使用getDex
所以刷了7.1.2
- 这里我们直接使用fart自动化脱壳工具进行脱壳就行了,360的壳直接去dump就可以了,因为它只是onCreate函数的一个vmp保护
编写脚本dump dex
// 只适用于存在getDex和getBytes api 的Android版本->7.1.2以下
function dumpdex(){
// com.example.socket.MainActivity
// a.a.a.a$a // 直接通过类名定位到需要的dex
Java.perform(function(){
var File = Java.use("java.io.File")
var FileOutputStream = Java.use("java.io.FileOutputStream")
// 首先,定位到类的class loader,通过它得到class对象
Java.enumerateClassLoadersSync().forEach(function(loader){
console.log(loader + "\n")
try {
var aclass = loader.loadClass("a.a.a.a$a")
console.log(aclass)
var dexobj = aclass.getDex()
console.log(dexobj)
var dexbytes = dexobj.getBytes()
var dexsavepath = "/data/data/com.example.socket/dump.dex"
var dexfile = File.$new(dexsavepath)
if(!dexfile.exists()){
dexfile.createNewFile()
}
var fileoutputstream = FileOutputStream.$new(dexsavepath)
fileoutputstream.write(dexbytes)
fileoutputstream.flush()
fileoutputstream.close()
console.log("save dex success!")
} catch (err) {
}
})
})
}
- 在Android7以下,可以通过
getDex
和getBytes
来获取目标dex并dump下来的方式进行脱壳
dump成功
- 用GDA分析的时候提示文件打开失败
- 检查一下是不是文件头错误,用010Edit把文件头替换成正常的文件头
- 头没有了,八个字节全都是0
- 替换个正常的头过来
- 脱壳后就可以看到
run
函数
- 合并两个参数
hook
HelloKitty.hello
获取参数和返回值function hookHelloKitty(){ // com.example.socket.MainActivity // a.a.a.a$a // 直接通过类名定位到需要的dex Java.perform(function(){ var File = Java.use("java.io.File") var FileOutputStream = Java.use("java.io.FileOutputStream") // 首先,定位到类的class loader,通过它得到class对象 Java.enumerateClassLoadersSync().forEach(function(loader){ console.log(loader + "\n") try { var aclass = loader.loadClass("a.a.a.a$a") Java.classFactory.loader = loader var HelloKitty = Java.use("com.example.socket.HelloKitty") // hello HelloKitty.hello.implementation = function(arg0){ console.log("HelloKitty.hello is called! arg0: " + arg0) var result = this.hello(arg0) console.log("HelloKitty.hello called over! result: " + result) return result } } catch (err) { } }) }) }
- 通过hook直接打印出来了我们输入的账号密码
这些是hook a.a.a.a$a
也就是hello
得到的结果,是发送
- 这里的
.c
是接收相关的函数
查看a.a(byte)
kitty
函数是一个native函数,暂时不管它
那么我们这里继续hook HelloKitty.kitty(str)
函数来拿到它的参数和返回值
// kitty
HelloKitty.kitty.implementation = function (arg0) {
console.log("HelloKitty.kitty is called! arg0: " + arg0)
var result = this.kitty(arg0)
console.log("HelloKitty.kitty called over! result: " + result)
return result
}
hook结果
可以拿到传入的参数
kitty
函数执行完之后解密的字符串还有点击登录响应之后返回的结果->提示密码错误
此时经过前面的hook我们基本可以知道:hello()
是用来加密的,kitty()
是用来解密的
这两个都是native函数,而这个360的壳没有对so进行加密,用IDA可以分析这个so
- ida里面可以直接搜索到这两个函数
如果暴力破解的话
我们需要去模拟框起来的流程,模拟它的流程给服务端发送指定格式的数据,然后根据返回的结果判断是否破解成功
function checkuser(username) {
//com.example.socket.MainActivity
//a.a.a.a$a
/* String crypt = HelloKitty.hello(this.f1a);
byte[] content = crypt.getBytes("utf-8");
MessageDigest m = MessageDigest.getInstance("MD5");
m.update(crypt.getBytes("UTF8"));
m.digest()
byte[] finalarray = a.a(content, m.digest());
if (a.e != null) {
a.e.write(finalarray);
}
*/
Java.perform(function () {
var File = Java.use('java.io.File');
var FileOutputStream = Java.use('java.io.FileOutputStream');
Java.enumerateClassLoadersSync().forEach(function (loader) {
console.log(loader + "\n");
try {
loader.loadClass("a.a.a.a$a")
Java.classFactory.loader = loader;
var HelloKitty = Java.use('com.example.socket.HelloKitty');
var crypt = HelloKitty.hello(username);
console.log("hello result:" + crypt);
/*byte[] content = "utf-8";
content = crypt.getBytes(content);*/
var StringClass = Java.use('java.lang.String');
console.log(StringClass)
var strobj = StringClass.$new(crypt);
console.log(strobj)
var cryptbytes = strobj.getBytes("utf-8");
console.log(JSON.stringify(cryptbytes));
/* MessageDigest m = MessageDigest.getInstance("MD5");
m.update(crypt.getBytes("UTF8"));
m.digest()*/
var MessageDigest = Java.use('java.security.MessageDigest');
var m = MessageDigest.getInstance("MD5");
m.update(cryptbytes);
var md5hash = m.digest();
console.log("hash:" + JSON.stringify(md5hash));
//byte[] finalarray = a.a(content, m.digest());
// if (a.e != null) {
// a.e.write(finalarray);
// }
var aClass = Java.use('a.a.a.a');
console.log(aClass)
var sendbytes = aClass.a(cryptbytes, md5hash);
console.log("sendbytes:" + JSON.stringify(sendbytes));
var outputstream = aClass._e.value;
console.log(outputstream);
outputstream.write(sendbytes);
outputstream.flush();
console.log("send success");
} catch (e) {
}
})
})
}
function main() {
checkuser('{"msgtype":"login","loginname":"100","loginpwd":"qaz"}')
}
setImmediate(main);
- 只要循环去生成0~100的账号和三个字母的密码,循环就可以了
- 这种暴力破解只需要不停的传递参数模拟调用就可以了,不涉及对它native层的加解密的分析
未完待续。。。
- 标题: 协议枚举、爆破及算法模拟
- 作者: xiaoeryu
- 创建于 : 2024-07-29 10:04:08
- 更新于 : 2024-07-29 10:11:36
- 链接: https://github.com/xiaoeryu/2024/07/29/协议枚举、爆破及算法模拟/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论