foreword
Recently, I am trying to explore in the reverse direction. I have analyzed the login protocol of a certain sports APP. This article records the analysis process and results. It is only for learning and research. There are few tools used and the content is relatively simple. Novice items, large Dude please skip. For the analysis of the password login module, just input a mobile phone number and password, and then use the packet capture tool to analyze it. It is worth looking at the implementation logic for several fields of the login protocol from a learning perspective.
grab bag
- Capture packets using Charles, please install and configure the certificate yourself
- Grab the login interface and click the password to log in. Use fake account secrets to test the packet capture, and the packet capture can be successful
Sign analysis
First, you can see that there is a sign field in the request header, and analyze this field:
sign: b61df9a8bce7a8641c5ca986b55670e633a7ab29
The overall length is 40, and the commonly used MD5 length is 32. The first reaction is not very similar, but it is also possible to splice other fields after md5. The length of the sha1 hash function is 40, which is exactly the same. Then we will verify one by one, first check whether there is any trace of MD5, and directly write the script frida and try to run it. The content of the script is relatively clear. For the Init, Update, and Final of MD5, hook and print to see the input and output. The key code is given below:
// hook CC_MD5
// unsigned char * CC_MD5(const void *data, CC_LONG len, unsigned char *md);
Interceptor.attach(Module.findExportByName(
"libcommonCrypto.dylib"
, g_funcName), {
onEnter:
function
(args) {
console.log(g_funcName +
" begin"
);
var
len = args[1].toInt32();
console.log(
"input:"
);
dumpBytes(args[0], len);
this
.md = args[2];
},
onLeave:
function
(retval) {
console.log(g_funcName +
" return value"
);
dumpBytes(
this
.md, g_funcRetvalLength);
console.log(g_funcName +
' called from:\n'
+
Thread.backtrace(
this
.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join(
'\n'
) +
'\n'
);
}
});
// hook CC_MD5_Update
// int CC_MD5_Update(CC_MD5_CTX *c, const void *data, CC_LONG len);
Interceptor.attach(Module.findExportByName(
"libcommonCrypto.dylib"
, g_updateFuncName), {
onEnter:
function
(args) {
console.log(g_updateFuncName +
" begin"
);
var
len = args[2].toInt32();
console.log(
"input:"
);
dumpBytes(args[1], len);
},
onLeave:
function
(retval) {
console.log(g_updateFuncName +
' called from:\n'
+
Thread.backtrace(
this
.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join(
'\n'
) +
'\n'
);
}
});
// hook CC_MD5_Final
// int CC_MD5_Final(unsigned char *md, CC_MD5_CTX *c);
Interceptor.attach(Module.findExportByName(
"libcommonCrypto.dylib"
, g_finalFuncName), {
onEnter:
function
(args) {
//console.log(func.name + " begin");
finalArgs_md = args[0];
},
onLeave:
function
(retval) {
console.log(g_finalFuncName +
" return value"
);
dumpBytes(finalArgs_md, g_funcRetvalLength);
console.log(g_finalFuncName +
' called from:\n'
+
Thread.backtrace(
this
.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join(
'\n'
) +
'\n'
);
}
});
Fortunately, the content related to the sign can be clearly seen in the printing, but the latter part of the sign is missing, so it is clear that the composition of the sign value is 32(md5)+8, first look at the data construction process of md5.
b61df9a8bce7a8641c5ca986b55670e6 33a7ab29
It can be clearly seen by printing that the MD5 of sign consists of three parts of data, namely: bodyData+Url+Str, and the body data can also be obtained from Charles.
-
{"body":"5gJEXtLqe3tzRsP8a/bSwehe0ta3zQx6wG7K74sOeXQ6Auz1NI1bg68wNLmj1e5Xl7CIwWelukC445W7HXxJY6nQ0v0SUg1tVyWS5L8E2oaCgoSeC6ypFNXV2xVm8hHV"}
-
/account/v4/login/password
-
V1QiLCJhbGciOiJIUzI1NiJ9
-
Sign tail analysis
Next, we analyze the tail data of Sign. Simply blind guessing or hanging frida scripts can’t solve the problem. Let’s use IDA to see the specific implementation logic. Of course, the above MD5 analysis can also be directly decompiled from IDA. Search The sign keyword is used for positioning, but I am used to hooking the script first, and if it hits directly, I don’t need to spend time analyzing it...
Through the MD5 script printing, we can also see the related function call stack, which also provides great convenience for us to quickly locate. We directly search for the [KEPPostSecuritySign kep_signWithURL: body:] method, and we can see obvious traces of string splicing. IDA is relatively intelligent and has recognized the salt value of MD5.
-
Through the analysis, locate the [NSString kep_networkStringOffsetSecurity] function, process the string internally, and perform various judgments and shift operations in the loop. If you don’t mind the trouble, you can analyze the logic and rewrite the processing flow.
Hook for encryption algorithms such as AES128, DES, 3DES, CAST, RC4, RC2, Blowfish, etc. The key code of the script is as follows:
var
handlers = {
CCCrypt: {
onEnter:
function
(args) {
var
operation = CCOperation[args[0].toInt32()];
var
alg = CCAlgorithm[args[1].toInt32()].name;
this
.options = CCoptions[args[2].toInt32()];
var
keyBytes = args[3];
var
keyLength = args[4].toInt32();
var
ivBuffer = args[5];
var
inBuffer = args[6];
this
.inLength = args[7].toInt32();
this
.outBuffer = args[8];
var
outLength = args[9].toInt32();
this
.outCountPtr = args[10];
if
(
this
.inLength < MIN_LENGTH ||
this
.inLength > MAX_LENGTH){
return
;
}
if
(operation ===
"kCCEncrypt"
) {
this
.operation =
"encrypt"
console.log(
"***************** encrypt begin **********************"
);
}
else
{
this
.operation =
"decrypt"
console.log(
"***************** decrypt begin **********************"
);
}
console.log(
"CCCrypt("
+
"operation: "
+
this
.operation +
", "
+
"CCAlgorithm: "
+ alg +
", "
+
"CCOptions: "
+
this
.options +
", "
+
"keyBytes: "
+ keyBytes +
", "
+
"keyLength: "
+ keyLength +
", "
+
"ivBuffer: "
+ ivBuffer +
", "
+
"inBuffer: "
+ inBuffer +
", "
+
"inLength: "
+
this
.inLength +
", "
+
"outBuffer: "
+
this
.outBuffer +
", "
+
"outLength: "
+ outLength +
", "
+
"outCountPtr: "
+
this
.outCountPtr +
")"
);
//console.log("Key: utf-8 string:" + ptr(keyBytes).readUtf8String())
//console.log("Key: utf-16 string:" + ptr(keyBytes).readUtf16String())
console.log(
"key: "
);
dumpBytes(keyBytes, keyLength);
console.log(
"IV: "
);
// ECB模式不需要iv,所以iv是null
dumpBytes(ivBuffer, keyLength);
var
isOutput =
true
;
if
(!SHOW_PLAIN_AND_CIPHER &&
this
.operation ==
"decrypt"
) {
isOutput =
false
;
}
if
(isOutput){
// Show the buffers here if this an encryption operation
console.log(
"In buffer:"
);
dumpBytes(inBuffer,
this
.inLength);
}
},
onLeave:
function
(retVal) {
// 长度过长和长度太短的都不要输出
if
(
this
.inLength < MIN_LENGTH ||
this
.inLength > MAX_LENGTH){
return
;
}
var
isOutput =
true
;
if
(!SHOW_PLAIN_AND_CIPHER &&
this
.operation ==
"encrypt"
) {
isOutput =
false
;
}
if
(isOutput) {
// Show the buffers here if this a decryption operation
console.log(
"Out buffer:"
);
dumpBytes(
this
.outBuffer, Memory.readUInt(
this
.outCountPtr));
}
// 输出调用堆栈,会识别类名函数名,非常好用
console.log(
'CCCrypt called from:\n'
+
Thread.backtrace(
this
.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join(
'\n'
) +
'\n'
);
}
},
};
if
(ObjC.available) {
console.log(
"frida attach"
);
for
(
var
func
in
handlers) {
console.log(
"hook "
+ func);
Interceptor.attach(Module.findExportByName(
"libcommonCrypto.dylib"
, func), handlers[func]);
}
}
else
{
console.log(
"Objective-C Runtime is not available!"
);
}
Looking at the output log of the script, it directly hits the encryption algorithm of AES128, and the output Base64 data matches exactly. It can only be said that luck is overwhelming.
After a simple transformation, even if the script can hook to the corresponding function, it is still not possible to directly call the result offline. At this time, it is necessary to perform decompilation analysis or dynamic debugging. At this time, it is necessary to cooperate with static protection methods such as code obfuscation and VMP , coupled with security measures such as anti-debugging, the threshold for attacks is also increased accordingly.