商务网站开发文档,网站设置怎么调,抖音开放平台注册,郴州网站建设制作之前分析过某物h5的以及小程序的搜索接口,就是一个aes,秘钥不固定,表单里把秘钥以及密文一起发过去,服务器解密后再把数据加密返回,客户端解密展示到页面上. 这期是关于app的登录,密码登录
声明
本文章中所有内容仅供学习交流使用#xff0c;不用于其他任何目的#xff0c;…之前分析过某物h5的以及小程序的搜索接口,就是一个aes,秘钥不固定,表单里把秘钥以及密文一起发过去,服务器解密后再把数据加密返回,客户端解密展示到页面上. 这期是关于app的登录,密码登录
声明
本文章中所有内容仅供学习交流使用不用于其他任何目的不提供完整代码抓包内容、敏感网址、数据接口等均已做脱敏处理严禁用于商业用途和非法用途否则由此产生的一切后果均与作者无关 本文章未经许可禁止转载禁止任何修改后二次传播擅自使用本文讲解的技术而导致的任何意外作者均不负责若有侵权请联系作者立即删除 我创建了一个js 安卓 ios逆向的技术交流群,刚兴趣的可以加一下,在最后面.
抓包 可以看到表单里有newSign,password,userName加密了,都是32位的.并且响应也是加密的. 再来看看头部,框中的那几个都是可能需要分析的,其中有一个shumeiid,这个是数美sdk里的.这个难度估计有点大,这个我们下期分析,SK和edk和ltk自行研究,后面看了下so的结构,就算是so的难度应该也不会太大.这几个貌似都没变,但是ltk是一直变化的.这个也留着后面分析.
反编译
先查下壳,没加固,总算碰到一个没加固的,前几期都是加固的,看来是对自己的产品比较有信心.
尝试搜索下字符串username
有点多,这样一个个点过去太麻烦了,尝试hook hashmap var hashMap Java.use(java.util.HashMap);
hashMap.put.implementation function (a, b) {if(a!null a.equals(userName)){console.log(Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Throwable).$new()))console.log(hashMap.put: , a, b);}return this.put(a, b);
}frida反调试
frida启动后以attach注入
直接断开,同时app会重启,这里用的已经是去过特征版的frida了,葫芦娃编译的,还是被检测到了. 以spawn方式启动试试 直接断开并没有重启,来看看它加载了哪些so.肯定是so的检测
var dlopen Module.findExportByName(null, dlopen);
var android_dlopen_ext Module.findExportByName(null, android_dlopen_ext);Interceptor.attach(dlopen, {onEnter: function (args) {var path_ptr args[0];var path ptr(path_ptr).readCString();console.log([dlopen:], path);},onLeave: function (retval) {}
});
Interceptor.attach(android_dlopen_ext, {onEnter: function (args) {var path_ptr args[0];var path ptr(path_ptr).readCString();console.log([dlopen_ext:], path);},onLeave: function (retval) {}
});libmsaoaidsec.so mobile security alliance open aid security,字面意思,看着就不是什么好东西,网上有分析libmsaoaidsec.so的文章,自行检索,我这里是直接把它删掉,有些app可以这样过,有些删了会闪退,闪退的话只能把它过掉. 删掉后frida可以正常使用,直接attach即可.
看到mobileLogin字眼,jadx中点过去看看 f.c方法点过去看看 看到了AES ECB字眼,应该是java层的,这样的话其他的几个也有可能是java层的,但是在上面图中的hashmap中没有看到newsign字眼,这个app用的okhttp3框架,极有可能是用的okhttp3.Interceptor添加的.因为有可能是java层的,有3个参数,当然优先考虑java通杀方案.
Java.perform(function () {function showStacks() {console.log(Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Throwable).$new()));}var ByteString Java.use(com.android.okhttp.okio.ByteString);function toBase64(tag, data) {//logOutPut(tag Base64: ByteString.of(data).base64());console.log(tag Base64: ByteString.of(data).base64());}function toHex(tag, data) {//logOutPut(tag Hex: ByteString.of(data).hex());console.log(tag Hex: ByteString.of(data).hex());}function toUtf8(tag, data) {//logOutPut(tag Utf8: ByteString.of(data).utf8());console.log(tag Utf8: ByteString.of(data).utf8());}var messageDigest Java.use(java.security.MessageDigest);messageDigest.update.overload(byte).implementation function (data) {console.log(MessageDigest.update(byte) is called!);showStacks();return this.update(data);}messageDigest.update.overload(java.nio.ByteBuffer).implementation function (data) {console.log(MessageDigest.update(java.nio.ByteBuffer) is called!);showStacks();return this.update(data);}messageDigest.update.overload([B).implementation function (data) {console.log(MessageDigest.update([B) is called!);showStacks();var algorithm this.getAlgorithm();var tag algorithm update data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log();return this.update(data);}messageDigest.update.overload([B, int, int).implementation function (data, start, length) {console.log(MessageDigest.update([B, int, int) is called!);showStacks();var algorithm this.getAlgorithm();var tag algorithm update data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log(, start, length);return this.update(data, start, length);}messageDigest.digest.overload().implementation function () {console.log(MessageDigest.digest() is called!);showStacks();var result this.digest();var algorithm this.getAlgorithm();var tag algorithm digest result;toHex(tag, result);toBase64(tag, result);console.log();return result;}messageDigest.digest.overload([B).implementation function (data) {console.log(MessageDigest.digest([B) is called!);showStacks();var algorithm this.getAlgorithm();var tag algorithm digest data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result this.digest(data);var tags algorithm digest result;toHex(tags, result);toBase64(tags, result);console.log();return result;}messageDigest.digest.overload([B, int, int).implementation function (data, start, length) {console.log(MessageDigest.digest([B, int, int) is called!);showStacks();var algorithm this.getAlgorithm();var tag algorithm digest data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result this.digest(data, start, length);var tags algorithm digest result;toHex(tags, result);toBase64(tags, result);console.log(, start, length);return result;}var mac Java.use(javax.crypto.Mac);mac.init.overload(java.security.Key, java.security.spec.AlgorithmParameterSpec).implementation function (key, AlgorithmParameterSpec) {console.log(Mac.init(java.security.Key, java.security.spec.AlgorithmParameterSpec) is called!);return this.init(key, AlgorithmParameterSpec);}mac.init.overload(java.security.Key).implementation function (key) {console.log(Mac.init(java.security.Key) is called!);var algorithm this.getAlgorithm();var tag algorithm init Key;var keyBytes key.getEncoded();toUtf8(tag, keyBytes);toHex(tag, keyBytes);toBase64(tag, keyBytes);console.log();return this.init(key);}mac.update.overload(byte).implementation function (data) {console.log(Mac.update(byte) is called!);return this.update(data);}mac.update.overload(java.nio.ByteBuffer).implementation function (data) {console.log(Mac.update(java.nio.ByteBuffer) is called!);return this.update(data);}mac.update.overload([B).implementation function (data) {console.log(Mac.update([B) is called!);var algorithm this.getAlgorithm();var tag algorithm update data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log();return this.update(data);}mac.update.overload([B, int, int).implementation function (data, start, length) {console.log(Mac.update([B, int, int) is called!);var algorithm this.getAlgorithm();var tag algorithm update data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log(, start, length);return this.update(data, start, length);}mac.doFinal.overload().implementation function () {console.log(Mac.doFinal() is called!);var result this.doFinal();var algorithm this.getAlgorithm();var tag algorithm doFinal result;toHex(tag, result);toBase64(tag, result);console.log();return result;}var cipher Java.use(javax.crypto.Cipher);cipher.init.overload(int, java.security.cert.Certificate).implementation function () {console.log(Cipher.init(int, java.security.cert.Certificate) is called!);return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.Key, java.security.SecureRandom).implementation function () {console.log(Cipher.init(int, java.security.Key, java.security.SecureRandom) is called!);return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.cert.Certificate, java.security.SecureRandom).implementation function () {console.log(Cipher.init(int, java.security.cert.Certificate, java.security.SecureRandom) is called!);return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.Key, java.security.AlgorithmParameters, java.security.SecureRandom).implementation function () {console.log(Cipher.init(int, java.security.Key, java.security.AlgorithmParameters, java.security.SecureRandom) is called!);return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.Key, java.security.spec.AlgorithmParameterSpec, java.security.SecureRandom).implementation function () {console.log(Cipher.init(int, java.security.Key, java.security.spec.AlgorithmParameterSpec, java.security.SecureRandom) is called!);return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.Key, java.security.AlgorithmParameters).implementation function () {console.log(Cipher.init(int, java.security.Key, java.security.AlgorithmParameters) is called!);return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.Key).implementation function () {console.log(Cipher.init(int, java.security.Key) is called!);var algorithm this.getAlgorithm();var tag algorithm init Key;var className JSON.stringify(arguments[1]);if(className.indexOf(OpenSSLRSAPrivateKey) -1){var keyBytes arguments[1].getEncoded();toUtf8(tag, keyBytes);toHex(tag, keyBytes);toBase64(tag, keyBytes);}console.log();return this.init.apply(this, arguments);}cipher.init.overload(int, java.security.Key, java.security.spec.AlgorithmParameterSpec).implementation function () {console.log(Cipher.init(int, java.security.Key, java.security.spec.AlgorithmParameterSpec) is called!);var algorithm this.getAlgorithm();var tag algorithm init Key;var keyBytes arguments[1].getEncoded();toUtf8(tag, keyBytes);toHex(tag, keyBytes);toBase64(tag, keyBytes);var tags algorithm init iv;var iv Java.cast(arguments[2], Java.use(javax.crypto.spec.IvParameterSpec));var ivBytes iv.getIV();toUtf8(tags, ivBytes);toHex(tags, ivBytes);toBase64(tags, ivBytes);console.log();return this.init.apply(this, arguments);}cipher.doFinal.overload(java.nio.ByteBuffer, java.nio.ByteBuffer).implementation function () {console.log(Cipher.doFinal(java.nio.ByteBuffer, java.nio.ByteBuffer) is called!);showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload([B, int).implementation function () {console.log(Cipher.doFinal([B, int) is called!);showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload([B, int, int, [B).implementation function () {console.log(Cipher.doFinal([B, int, int, [B) is called!);showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload([B, int, int, [B, int).implementation function () {console.log(Cipher.doFinal([B, int, int, [B, int) is called!);showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload().implementation function () {console.log(Cipher.doFinal() is called!);showStacks();return this.doFinal.apply(this, arguments);}cipher.doFinal.overload([B).implementation function () {console.log(Cipher.doFinal([B) is called!);showStacks();var algorithm this.getAlgorithm();var tag algorithm doFinal data;var data arguments[0];toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result this.doFinal.apply(this, arguments);var tags algorithm doFinal result;toHex(tags, result);toBase64(tags, result);console.log();return result;}cipher.doFinal.overload([B, int, int).implementation function () {console.log(Cipher.doFinal([B, int, int) is called!);showStacks();var algorithm this.getAlgorithm();var tag algorithm doFinal data;var data arguments[0];toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);var result this.doFinal.apply(this, arguments);var tags algorithm doFinal result;toHex(tags, result);toBase64(tags, result);console.log(, arguments[1], arguments[2]);return result;}var signature Java.use(java.security.Signature);signature.update.overload(byte).implementation function (data) {console.log(Signature.update(byte) is called!);return this.update(data);}signature.update.overload(java.nio.ByteBuffer).implementation function (data) {console.log(Signature.update(java.nio.ByteBuffer) is called!);return this.update(data);}signature.update.overload([B, int, int).implementation function (data, start, length) {console.log(Signature.update([B, int, int) is called!);var algorithm this.getAlgorithm();var tag algorithm update data;toUtf8(tag, data);toHex(tag, data);toBase64(tag, data);console.log(, start, length);return this.update(data, start, length);}signature.sign.overload([B, int, int).implementation function () {console.log(Signature.sign([B, int, int) is called!);return this.sign.apply(this, arguments);}signature.sign.overload().implementation function () {console.log(Signature.sign() is called!);var result this.sign();var algorithm this.getAlgorithm();var tag algorithm sign result;toHex(tag, result);toBase64(tag, result);console.log();return result;}
});不过我比较喜欢用算法助手,原理是一样的.先在lsp里把目标app勾上,算法助手页面把这个app打开 全部勾上后重启app,登录一下,抓包同时开着,这个包里的
{cipherParam: userName,countryCode: 86,loginToken: ,newSign: 614a0f4d2ef049ff1d0145b0a96d2ef0,password: ca8f119a27ec17f98b463807cd0b6b62,platform: android,timestamp: 1711714586437,type: pwd,userName: 83c7358c94e3a24e11f0fde1cbf5d559_1,v: 5.38.5
}userName
日志里搜一下83c7358c94e3a24e11f0fde1cbf5d559,后面的_1自己添加上去的.
有结果
aes ecb nopadding,无填充是因为已经填充过了,看明文后面那一串,直接用pkcs7就可以了.userName解决
password
直接搜ca8f119a27ec17f98b463807cd0b6b62 密码加一个盐值,没什么好说的.
newSign
搜一下614a0f4d2ef049ff1d0145b0a96d2ef0
md5签的一串base64值.搜索这个值有没有结果,搜一部分dWWoXlbR3K87j2N27Dkv4uOPUnOsh0xrJ5t 没有搜到,这个值是base64过的,尝试hook一下base64的encodeToString方法
var base64 Java.use(android.util.Base64);
base64.encodeToString.overload([B, int).implementation function (a, b) {
console.log(Java.use(android.util.Log).getStackTraceString(Java.use(java.lang.Throwable).$new()))
console.log(base64.encodeToString: , JSON.stringify(a));
var result this.encodeToString(a, b);
console.log(base64.encodeToString result: , result)
return result;}没有hook到,估计是so base64后返回的,所以没有hook到. 先看下先前的堆栈
let i0 Java.use(gf.i0);
i0[c].implementation function (map, j, str) {console.log(i0.c is called: map${map}, j${j}, str${str});let result this[c](map, j, str);console.log(i0.c result${result});return result;
};hook一下这个c方法,入参的str是空值 中间添加了些东西,这里直接跟这个aesencrypt.encode方法,一路跟到 encodebyte方法,写这个方法的主动调用
function call2(){Java.perform(function (){let AESEncrypt Java.use(com.duapp.aesjni.AESEncrypt);var str010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100var bArr [99,105,112,104,101,114,80,97,114,97,109,117,115,101,114,78,97,109,101,99,111,117,110,116,114,121,67,111,100,101,56,54,108,111,103,105,110,84,111,107,101,110,112,97,115,115,119,111,114,100,99,97,56,102,49,49,57,97,50,55,101,99,49,55,102,57,56,98,52,54,51,56,48,55,99,100,48,98,54,98,54,50,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,49,49,55,48,52,48,56,49,52,55,54,116,121,112,101,112,119,100,117,115,101,114,78,97,109,101,56,51,99,55,51,53,56,99,57,52,101,51,97,50,52,101,49,49,102,48,102,100,101,49,99,98,102,53,100,53,53,57,95,49,117,117,105,100,49,48,49,51,48,48,102,98,51,57,48,51,48,56,52,101,118,53,46,51,56,46,53]var res AESEncrypt[encodeByte](bArr, str)console.log(res)})
}des 3des aes关系
第一个字符串转16进制后是24字节,写文章之前不记得前面有个aes了,以为这个是3des,3des秘钥就是24字节,3des由des进化过来的,因为des不安全,那个时候aes还没出来,aes中的a是advanced,高级的,由des进化而来,aes的原始名称是Rijndael算法,是由创造这个算法的两位作者名字组成的.因为是在des基础上改进的,所以大家都称它aes. des秘钥8字节太短了,aes有16字节,24字节,32字节的,密钥的长度在一定程度上决定着算法的安全性而DES密钥长度过短也就导致了它的安全性较低而AES则更加安全这也是AES能够取代DES的重要原因之一. 在aes还没出之前为了解决des安全性的问题,有专家推出了3des,cyberchef中名称是TripleDES,Triple有3的意思,秘钥24字节,由3把秘钥组成,每8个字节算一把秘钥,加密流程如图所示,如果前两把秘钥相同,就相当于只有一次des加密,3des目前已被证明安全性不足,目前用的都是aes,如果没记错的话某书的x-s就是用的3des,好吧扯得有点远了,回到正文. 用3des加密一遍结果不对,只能看so了.
第一个数组转utf-8
import base64
byte_list [99,105,112,104,101,114,80,97,114,97,109,117,115,101,114,78,97,109,101,99,111,117,110,116,114,121,67,111,100,101,56,54,108,111,103,105,110,84,111,107,101,110,112,97,115,115,119,111,114,100,99,97,56,102,49,49,57,97,50,55,101,99,49,55,102,57,56,98,52,54,51,56,48,55,99,100,48,98,54,98,54,50,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,49,49,55,48,52,48,56,49,52,55,54,116,121,112,101,112,119,100,117,115,101,114,78,97,109,101,56,51,99,55,51,53,56,99,57,52,101,51,97,50,52,101,49,49,102,48,102,100,101,49,99,98,102,53,100,53,53,57,95,49,117,117,105,100,49,48,49,51,48,48,102,98,51,57,48,51,48,56,52,101,118,53,46,51,56,46,53]
num_list bytearray()
for item in byte_list:if item 0:item item 256num_list.append(item)
utf8_string num_list.decode()
print(utf8_string:,utf8_string)结果就是表单里除了newsign的东西cipherParamuserNamecountryCode86loginTokenpasswordca8f119a27ec17f98b463807cd0b6b62platformandroidtimestamp1711704081476typepwduserName83c7358c94e3a24e11f0fde1cbf5d559_1uuid101300fb3903084ev5.38.5,再拼了一个uuid和版本号.
so加固
上图中so的名字是libJNIEncrypt.so,只有64位的,用ida64打开
提示文件结果被破坏,点yes 警告这个so有无意义或者无效的节.点ok. 刚进来两处爆红,上面一条都是灰色的,没有代码段,不用想,加固了.
so dump
dump内存中的so,yang神的脚本
function dump_so(so_name) {Java.perform(function () {var currentApplication Java.use(android.app.ActivityThread).currentApplication();var dir currentApplication.getApplicationContext().getFilesDir().getPath();var libso Process.getModuleByName(so_name);console.log([name]:, libso.name);console.log([base]:, libso.base);console.log([size]:, ptr(libso.size));console.log([path]:, libso.path);var file_path dir / libso.name _ libso.base _ ptr(libso.size) .so;var file_handle new File(file_path, wb);if (file_handle file_handle ! null) {Memory.protect(ptr(libso.base), libso.size, rwx);var libso_buffer ptr(libso.base).readByteArray(libso.size);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log([dump]:, file_path);}});
}
dump_so(libJNIEncrypt.so)dump下来拉到电脑上用sofixer修复一下.
dump下来的没有前面的警告了,上面的进度条也变成了蓝色,同时看到了Jnionload,动态注册,以及base64和aes128,难怪之前hook不到base64,原来是so base64的 接下来找动态注册函数.hook libart
var addrRegisterNatives null;
var symbols Module.enumerateSymbolsSync(libart.so);
for (var i 0; i symbols.length; i) {var symbol symbols[i];if (symbol.name.indexOf(art) 0 symbol.name.indexOf(JNI) 0 symbol.name.indexOf(RegisterNatives) 0 symbol.name.indexOf(CheckJNI) 0) {addrRegisterNatives symbol.address;console.log(RegisterNatives is at , symbol.address, symbol.name);break}
}
if (addrRegisterNatives) {// RegisterNatives(env, 类型, Java和C的对应关系,个数)Interceptor.attach(addrRegisterNatives, {onEnter: function (args) {var env args[0]; // jni对象var java_class args[1]; // 类var class_name Java.vm.tryGetEnv().getClassName(java_class);var taget_class com.duapp.aesjni.AESEncrypt; //111 某个类中动态注册的soif (class_name taget_class) {//只找我们自己想要类中的动态注册关系console.log(\n[RegisterNatives] method_count:, args[3]);var methods_ptr ptr(args[2]);var method_count parseInt(args[3]);for (var i 0; i method_count; i) {// Java中函数名字的var name_ptr Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));// 参数和返回值类型var sig_ptr Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 Process.pointerSize));// C中的函数内存地址var fnPtr_ptr Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 Process.pointerSize * 2));var name Memory.readCString(name_ptr);var sig Memory.readCString(sig_ptr);var find_module Process.findModuleByAddress(fnPtr_ptr);// 地址、偏移量、基地址var offset ptr(fnPtr_ptr).sub(find_module.base);console.log(name:, name, sig:, sig,module_name:,find_module.name ,offset:, offset);}}}});
}以spawn启动 0x174c,ida中跳过去看看,然后转换一下jnienv对象
看返回值,来自v18,v18来自j_AES_128_ECB_PKCS5Padding_Encrypt,很明显了,ecb无iv 16字节秘钥,pkcs5填充,直接frida hook这个函数打印两个入参.
var soAddr Module.findBaseAddress(libJNIEncrypt.so);
var funcAddr soAddr.add(0x182C) //32位的话记得1Interceptor.attach(funcAddr,{onEnter: function(args){console.log(onEnter arg[0]: ,hexdump(args[0]))console.log(onEnter arg[1]: ,hexdump(args[1]))this.arg0 args[0]},onLeave: function(retval){// console.log(onLeave arg[]: )console.log(onLeave result: ,retval)}});入参1是java层传来的明文,参数2是key d245a0ba8d678a61,验证一下,和主动调用结果一致,最后md5就是newsign了,至此,newsign分析完毕,没什么难度的.
技术交流lyaoyao__i(两个_),群聊7天有效 微信公众号 知识星球