某健身APP登录数据包逆向分析

admin 2023年9月13日10:29:35评论20 views字数 10964阅读36分32秒阅读模式

首先使用Burpsutie抓取账户密码的登录数据包:

某健身APP登录数据包逆向分析

可以看到数据包是类型是json格式,body字段中的参数是加密的。同时,在数据包头部中有Sign。这个Sign的长度是40,并不是MD5的 长度,但在没有别的头绪之前,先尝试对其进行MD5的追踪:
使用frida-trace -UF -i "CC_MD5"
修改frida默认脚本如下:

{  onEnter(log, args, state) {    log('CC_MD5()--arg[0]='+args[0].readUtf8String());  },  onLeave(log, retval, state) {    log('CC_MD5()--return--=');    var md5_digest = hexdump(retval,{length:16});    var hexified = " ";    var raw_array = md5_digest.split("n");    for (var a=0; a<raw_array.length; a++)    {      var line_array = raw_array[a].split(" ");      for (var b=1; b<line_array.length-1; b++)       {        if (line_array[b].length === 2)        {          hexified += line_array[b];          hexified = hexified.trim();        }      }    }    log(hexified+"n");  }}

根据数据包中的Sign值在脚本输出内容中进行搜索:


某健身APP登录数据包逆向分析

某健身APP登录数据包逆向分析

可以看到这个sign的组成是由数据包+接口+V1QiLCJhbGciOiJIUzI1NiJ9组成。

多尝试抓几个登录的数据包发现,V1QiLCJhbGciOiJIUzI1NiJ9没有变化。

某健身APP登录数据包逆向分析

使用MD5对数据包+接口+V1QiLCJhbGciOiJIUzI1NiJ9进行编码:

某健身APP登录数据包逆向分析

e8c9fe8c23a7f8045b89f4e58c34e726e8c9fe8c23a7f8045b89f4e58c34e726e31fc14c

还有后8位不清楚是什么。

对app进行反汇编,搜索V1QiLCJhbGciOiJIUzI1NiJ9看看

某健身APP登录数据包逆向分析

发现是从+[KEPPostSecuritySign kep_signWithURL:body:]:函数方法进行调用:

某健身APP登录数据包逆向分析

某健身APP登录数据包逆向分析

根据我们之前分析的结果,在以下指令中,

add        x8, x8, #0x58 ; 0x108a4c058@PAGEOFF, @"V1QiLCJhbGciOiJIUzI1NiJ9"

大致就是将body+接口+V1QiLCJhbGciOiJIUzI1NiJ9。

 add        x2, x2, #0x78                               ; 0x108a4c078@PAGEOFF, @"%@%@%@%@"

这条指令将 x2 寄存器中的值与立即数 0x78 相加,并将结果再存回 x2 寄存器。这样,x2 寄存器现在指向的是一个具体的内存地址,这个地址与字符串 "@%@@%@@%@@%@" 相关。这个字符串是一个格式字符串,常用于 NSString 的格式化输出方法中。

然后排查sub_1081cf880、sub_108073780、 sub_1080737c0方法。

发现sub_108073780调用了一个kep_networkMd5的方法,它首先加载该方法的选择器到 x1 寄存器,接着加载 _objc_msgSend 的地址到 x16 寄存器,然后调用 _objc_msgSend 来执行方法调用。

某健身APP登录数据包逆向分析

因此判断这里就是将数据包+接口+V1QiLCJhbGciOiJIUzI1NiJ9进行MD5编码。

然后在sub_1080737c0中加载 x16 寄存器与 0x108694000 相关的页面地址。

某健身APP登录数据包逆向分析

大致就是将上一步MD5的值与刚刚000000010246da98进行拼接。我们尝试去调用kep_networkStringOffsetSecurity:这个方法:

某健身APP登录数据包逆向分析

某健身APP登录数据包逆向分析

接下来我们分析body的加密内容。根据加密的密文格式,可能使用AES加密算法,使用以下脚本看看是否更够hook到加密算法:

// color着色var Color = {    RESET: "x1b[39;49;00m", Black: "0;01", Blue: "4;01", Cyan: "6;01", Gray: "7;01", Green: "2;01", Purple: "5;01", Red: "1;01", Yellow: "3;01",    Light: {        Black: "0;11", Blue: "4;11", Cyan: "6;11", Gray: "7;01", Green: "2;11", Purple: "5;11", Red: "1;11", Yellow: "3;11"    }};
var LOG = function (input, kwargs) { kwargs = kwargs || {}; var logLevel = kwargs['l'] || 'log', colorPrefix = 'x1b[3', colorSuffix = 'm'; if (typeof input === 'object') input = JSON.stringify(input, null, kwargs['i'] ? 2 : null); if (kwargs['c']) input = colorPrefix + kwargs['c'] + colorSuffix + input + Color.RESET; console[logLevel](input);};

function base64encode(str) { var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var out, i, len; var c1, c2, c3;
len = str.length; i = 0; out = ""; while (i < len) { c1 = str.charCodeAt(i++) & 0xff; if (i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt((c1 & 0x3) << 4); out += "=="; break; } c2 = str.charCodeAt(i++); if (i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt((c2 & 0xF) << 2); out += "="; break; } c3 = str.charCodeAt(i++); out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)); out += base64EncodeChars.charAt(c3 & 0x3F); } return out;}
function bin2string(array){ var result = ""; for(var i = 0; i < array.length; ++i){ result+= (String.fromCharCode(array[i])); } return result;}

Interceptor.attach(Module.findExportByName('libcommonCrypto.dylib', 'CCCrypt'), { onEnter: function (args) { // Save the arguments this.operation = args[0] this.CCAlgorithm = args[1] this.CCOptions = args[2] this.keyBytes = args[3] this.keyLength = args[4] this.ivBuffer = args[5] this.inBuffer = args[6] this.inLength = args[7] this.outBuffer = args[8] this.outLength = args[9] this.outCountPtr = args[10] console.log("[+] --------------------------------------------------------------"); // 不要在onLeave回溯堆栈,会产生误报! console.log('tACCURATE Backtrace:nt' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('nt')); // console.log('tFUZZY Backtrace:nt' + Thread.backtrace(this.context,Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('nt'));
},
onLeave: function (retVal) { if (this.operation == 0) { // Show the buffers here if this an encryption operation
// console.log("[+] CCAlgorithm: " + this.CCAlgorithm); if (this.CCAlgorithm == 0x0) {console.log("[+] CCAlgorithm: " + this.CCAlgorithm + " --> AES Encrypt");} if (this.CCAlgorithm == 0x1) {console.log("[+] CCAlgorithm: " + this.CCAlgorithm + " --> DES Encrypt");} if (this.CCAlgorithm == 0x2) {console.log("[+] CCAlgorithm: " + this.CCAlgorithm + " --> 3DES Encrypt");}

// enum { // /* options for block ciphers */ // kCCOptionPKCS7Padding = 0x0001, // kCCOptionECBMode = 0x0002 // /* stream ciphers currently have no options */ // }; // typedef uint32_t CCOptions; if (this.CCOptions == 0x0 || this.CCOptions == 0x1) {console.log("[+] CCOptions: " + this.CCOptions + " --> mode CBC");} if (this.CCOptions == 0x2 || this.CCOptions == 0x3) {console.log("[+] CCOptions: " + this.CCOptions + " --> mode ECB");} // kCCOptionPKCS7Padding | kCCOptionECBMode == 0x03 // if (this.CCOptions != 0x1 && this.CCOptions != 0x3) {console.log("[+] CCOptions: " + this.CCOptions);}
console.log(Memory.readByteArray(this.inBuffer, this.inLength.toInt32())); try { // console.log("[+] Before Encrypt: " + Memory.readUtf8String(this.inBuffer, this.inLength.toInt32())); LOG("[+] Before Encrypt: " + Memory.readUtf8String(this.inBuffer, this.inLength.toInt32()), { c: Color.Gray }); } catch(e) { // var ByteArray = Memory.readByteArray(this.inBuffer, this.inLength.toInt32()); // var uint8Array = new Uint8Array(ByteArray); // // console.log("[+] uint8Array: " + uint8Array);
// var str = ""; // for(var i = 0; i < uint8Array.length; i++) { // var hextemp = (uint8Array[i].toString(16)) // if(hextemp.length == 1){ // hextemp = "0" + hextemp // } // str += hextemp + " "; // }
// console.log("[+] Before Encrypt: " + str); // 打印hex,非可见ascii范围 } console.log(Memory.readByteArray(this.keyBytes, this.keyLength.toInt32())); if (this.keyLength.toInt32() == 16) {console.log("[+] KEY Length --> 128");} if (this.keyLength.toInt32() == 24) {console.log("[+] KEY Length --> 192");} if (this.keyLength.toInt32() == 32) {console.log("[+] KEY Length --> 256");} try { // console.log("[+] KEY: " + Memory.readUtf8String(this.keyBytes, this.keyLength.toInt32())); LOG("[+] KEY: " + Memory.readUtf8String(this.keyBytes, this.keyLength.toInt32()), { c: Color.Gray }); } catch(e) { var ByteArray = Memory.readByteArray(this.keyBytes, this.keyLength.toInt32()); var uint8Array = new Uint8Array(ByteArray); // console.log("[+] uint8Array: " + uint8Array);
var str = ""; for(var i = 0; i < uint8Array.length; i++) { var hextemp = (uint8Array[i].toString(16)) if(hextemp.length == 1){ hextemp = "0" + hextemp } str += hextemp + " "; }
// console.log("[+] KEY: " + str); // 打印hex,非可见ascii范围 LOG("[+] KEY: " + str, { c: Color.Gray }); }
if (this.CCOptions == 0x0 || this.CCOptions == 0x1) { console.log(Memory.readByteArray(this.ivBuffer, 16)); try { // console.log("[+] IV: " + Memory.readUtf8String(this.ivBuffer, this.keyLength.toInt32())); LOG("[+] IV: " + Memory.readUtf8String(this.ivBuffer, 16), { c: Color.Gray }); } catch(e) { var ByteArray = Memory.readByteArray(this.ivBuffer, 16); var uint8Array = new Uint8Array(ByteArray);
var str = ""; for(var i = 0; i < uint8Array.length; i++) { var hextemp = (uint8Array[i].toString(16)) if(hextemp.length == 1){ hextemp = "0" + hextemp } str += hextemp + " "; }
// console.log("[+] IV: " + str); // 打印hex,非可见ascii范围 LOG("[+] IV: " + str, { c: Color.Gray }); } } // console.log(Memory.readByteArray(this.outBuffer, Memory.readUInt(this.outCountPtr))); // 打印hex,非可见ascii范围 var array = new Uint8Array(Memory.readByteArray(this.outBuffer, Memory.readUInt(this.outCountPtr))); // console.log("[+] After Encrypt: " + base64encode(bin2string(array))); LOG("[+] After Encrypt: " + base64encode(bin2string(array)), { c: Color.Gray });
console.log("[-] --------------------------------------------------------------n"); }
if (this.operation == 1) { // Show the buffers here if this a decryption operation
// console.log("inLength: " + this.inLength.toInt32()); // console.log("outLength: " + this.outLength.toInt32()); // console.log("outCount: " + Memory.readUInt(this.outCountPtr)); //实际的outBuffer的有效长度
// console.log("[+] CCAlgorithm: " + this.CCAlgorithm); if (this.CCAlgorithm == 0x0) {console.log("[+] CCAlgorithm: " + this.CCAlgorithm + " --> AES Decrypt");} if (this.CCAlgorithm == 0x1) {console.log("[+] CCAlgorithm: " + this.CCAlgorithm + " --> DES Decrypt");} if (this.CCAlgorithm == 0x2) {console.log("[+] CCAlgorithm: " + this.CCAlgorithm + " --> 3DES Decrypt");}
// enum { // /* options for block ciphers */ // kCCOptionPKCS7Padding = 0x0001, // kCCOptionECBMode = 0x0002 // /* stream ciphers currently have no options */ // }; // typedef uint32_t CCOptions; if (this.CCOptions == 0x0 || this.CCOptions == 0x1) {console.log("[+] CCOptions: " + this.CCOptions + " --> mode CBC");} if (this.CCOptions == 0x2 || this.CCOptions == 0x3) {console.log("[+] CCOptions: " + this.CCOptions + " --> mode ECB");} // kCCOptionPKCS7Padding | kCCOptionECBMode == 0x03 // if (this.CCOptions != 0x1 && this.CCOptions != 0x3) {console.log("[+] CCOptions: " + this.CCOptions);}
var array = new Uint8Array(Memory.readByteArray(this.inBuffer, this.inLength.toInt32())); // console.log("[+] Before Decrypt: " + base64encode(bin2string(array))); LOG("[+] Before Decrypt: " + base64encode(bin2string(array)), { c: Color.Gray }); console.log(Memory.readByteArray(this.keyBytes, this.keyLength.toInt32())); if (this.keyLength.toInt32() == 16) {console.log("[+] KEY Length --> 128");} if (this.keyLength.toInt32() == 24) {console.log("[+] KEY Length --> 192");} if (this.keyLength.toInt32() == 32) {console.log("[+] KEY Length --> 256");} try { // console.log("[+] KEY: " + Memory.readUtf8String(this.keyBytes, this.keyLength.toInt32())); LOG("[+] KEY: " + Memory.readUtf8String(this.keyBytes, this.keyLength.toInt32()), { c: Color.Gray }); } catch(e) { var ByteArray = Memory.readByteArray(this.keyBytes, this.keyLength.toInt32()); var uint8Array = new Uint8Array(ByteArray); // console.log("[+] uint8Array: " + uint8Array);
var str = ""; for(var i = 0; i < uint8Array.length; i++) { var hextemp = (uint8Array[i].toString(16)) if(hextemp.length == 1){ hextemp = "0" + hextemp } str += hextemp + " "; }
// console.log("[+] KEY: " + str); // 打印hex,非可见ascii范围 LOG("[+] KEY: " + str, { c: Color.Gray }); }
if (this.CCOptions == 0x0 || this.CCOptions == 0x1) { console.log(Memory.readByteArray(this.ivBuffer, 16)); try { // console.log("[+] IV: " + Memory.readUtf8String(this.ivBuffer, this.keyLength.toInt32())); LOG("[+] IV: " + Memory.readUtf8String(this.ivBuffer, 16), { c: Color.Gray }); } catch(e) { var ByteArray = Memory.readByteArray(this.ivBuffer, 16); var uint8Array = new Uint8Array(ByteArray);
var str = ""; for(var i = 0; i < uint8Array.length; i++) { var hextemp = (uint8Array[i].toString(16)) if(hextemp.length == 1){ hextemp = "0" + hextemp } str += hextemp + " "; }
// console.log("[+] IV: " + str); // 打印hex,非可见ascii范围 LOG("[+] IV: " + str, { c: Color.Gray }); } }
console.log(Memory.readByteArray(this.outBuffer, Memory.readUInt(this.outCountPtr))); try { // console.log("[+] After Decrypt: " + Memory.readUtf8String(this.outBuffer, Memory.readUInt(this.outCountPtr))); LOG("[+] After Decrypt: " + Memory.readUtf8String(this.outBuffer, Memory.readUInt(this.outCountPtr)), { c: Color.Gray }); } catch(e) { // var ByteArray = Memory.readByteArray(this.outBuffer, Memory.readUInt(this.outCountPtr)); // var uint8Array = new Uint8Array(ByteArray); // // console.log("[+] uint8Array: " + uint8Array);
// var str = ""; // for(var i = 0; i < uint8Array.length; i++) { // var hextemp = (uint8Array[i].toString(16)) // if(hextemp.length == 1){ // hextemp = "0" + hextemp // } // str += hextemp + " "; // }
// console.log("[+] After Decrypt: " + str); // 打印hex,非可见ascii范围 }
console.log("[-] --------------------------------------------------------------n"); } }})


根据登录时候输入的手机号码,看看是否有登录数据包的加密信息:

某健身APP登录数据包逆向分析

可以看到,使用的加密算法是AES128,加密模式是CBC。加密前的数据包也输出了。

使用AES加密数据包:

某健身APP登录数据包逆向分析


输出的结果并不是刚刚burpsuite的密文,我们跟进一下刚刚输出的函数SecurityUtil:

某健身APP登录数据包逆向分析

它还有一个encodeBase64Data方法:

某健身APP登录数据包逆向分析

在进行AES加密之前,还有一个base64的编码:

某健身APP登录数据包逆向分析

在刚刚的基础上增加一个base64的编码:

某健身APP登录数据包逆向分析

成功将body的参数进行解密。

原文始发于微信公众号(赛哈文):某健身APP登录数据包逆向分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年9月13日10:29:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某健身APP登录数据包逆向分析https://cn-sec.com/archives/2031806.html

发表评论

匿名网友 填写信息