从零开始逆向分析APP系列----微博APP协议分析
在抓包尝试调用微博极速版APP相关接口的时候,发现修改参数并不能通过服务端的校验,会提示如下:
{"errmsg":"客户端身份校验失败","errno":-105,"errtype":"DEFAULT_ERROR","isblock":false}
登录的数据包如下:
POST https://api.weibo.cn/2/account/login HTTP/1.1
X-Sessionid: 1ecc1f6e-3e64-46ff-9e3a-bc401f199b75
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 353
Host: api.weibo.cn
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.12.1
c=weibofastios&i=1234567&s=00000000&u=13123232321&p=lXtrKeNaL48e0vosKaz%2F9nVIBo%2BfWPEZ2l8t%2FUe9h50V5gIpXVjcpkscP4257e2LqXZlv70u4y1h2QKvF7BsoSycfq%2BfAk6dE9%2FYnMfVpTGSmGyDi4re2xnz5WxVoqcpPjP%2BuFl5e0El87ZeYCXn05oG8yNPzetkEOrIjzsVqy4%3D&getuser=1&getoauth=1&getcookie=1&lang=zh_CN&aid=01A1H_TanahcG58Oq1snTGp9kL_6PttMMsBzCuqVCH1HGFBEs.&from=2599295010
通过一番分析,我们发现主要的校验是在s字段,s字段并不像一般哈希算法的加密长度,那么s是如何生成的呢,这就是本文探究的对象。
准备工具:JADX、Frida、IDA Pro
[i]注:本文主要目的是以探究s字段算法为目的进行学习和研究,若有侵权,请联系删除。
定位加密算法
将我们的安装包用jadx打开
点击搜索,我们搜索字符串”s”看看:
出来了很多,注意到这个calculateS函数,calculate是计算的意思
接下来我们需要使用frida去hook这个函数,看看他有哪些传参,返回了什么传参
Frida工具hook java函数
启动frida-server、动态转发
编写hook脚本如下:
function hookJava(){
Java.perform(function(){
var WeicoSecurityUtils = Java.use("com.sina.weibo.security.WeicoSecurityUtils");
WeicoSecurityUtils.generateS.implementation = function(a,b,c,d,e){
var result = this.generateS(a,b,c,d,e);
console.log("WeicoSecurityUtils",b,c,d,e,"\nresult",result);
return result;
};
});
}
function main(){
hookJava();
}
setImmediate(main);
启动脚本
frida -U -f com.sina.weibolite -l js/hookWeibo.js
WeicoSecurityUtils 13123232323123123 g4c8CKKdwh3LE1mRX7uxyx7AafXUkJsh 2599295010 902784192
result 00000000
可以看到13123232323123123是我们的账号+密码,别的一些参数呢都能在提交参数中找到,或是固定值,就不过多分析。接下来我们再编写一个主动调用的脚本,方便我们去调试,完善代码如下:
function hookJava(){
Java.perform(function(){
var WeicoSecurityUtils = Java.use("com.sina.weibo.security.WeicoSecurityUtils");
WeicoSecurityUtils.generateS.implementation = function(a,b,c,d,e){
var result = this.generateS(a,b,c,d,e);
console.log("WeicoSecurityUtils",b,c,d,e,"\nresult",result);
return result;
};
});
}
function callGenerateS(){
Java.perform(function(){
var WeicoSecurityUtils = Java.use("com.sina.weibo.security.WeicoSecurityUtils");
//得到context
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();
var result = WeicoSecurityUtils.generateS(context,"13123232323123123","g4c8CKKdwh3LE1mRX7uxyx7AafXUkJsh","2599295010","902784192");
console.log("WeicoSecurityUtils-result",result);
return result;
});
}
function main(){
//hookJava();
}
setImmediate(main);
再调用一下看看
Ida Pro分析so代码
将apk改后缀为zip拿到lib目录下面的libnative-lib.so,使用ida打开
具体分析如下
int __fastcall Java_com_sina_weibo_security_WeicoSecurityUtils_generateS(_JNIEnv *a1, int a2, int a3, int a4, int a5, int a6, int a7)
{
_JNIEnv *v7; // r4
void *v8; // r6
int context; // r5
const char *str4; // r9
const char *str3; // r11
jclass AboutActivity; // r0
void *AboutActivity_; // r6
jmethodID forVerify; // r0
void *v15; // r8
const char *forVerifyResult; // r10
int v17; // r5
int str3_; // r6
bool v19; // zf
jclass WeicoSecurityUtils; // r0
void *v21; // r6
jmethodID v22; // r0
int v23; // r8
int result; // r0
size_t v25; // r5
size_t v26; // r0
char *v27; // r5
jclass v28; // r0
void *v29; // r6
jmethodID v30; // r0
int v31; // r5
int v32; // [sp+Ch] [bp-1Ch]
char *str; // [sp+10h] [bp-18h]
char *str2; // [sp+14h] [bp-14h]
void *str_; // [sp+18h] [bp-10h]
v7 = a1;
v8 = (void *)a4;
context = a3;
str_ = (void *)a4;
str4 = a1->functions->GetStringUTFChars(&a1->functions, (jstring)a7, 0);// 得到第四个传参字符串 902784192
v32 = verify((int)v7, context, (int)str4);
_android_log_print(4);
str = (char *)v7->functions->GetStringUTFChars(&v7->functions, v8, 0);// 得到第一个传参字符串 13123232323123123
str2 = (char *)v7->functions->GetStringUTFChars(&v7->functions, (jstring)a5, 0);// 得到第二个传参字符串 g4c8CKKdwh3LE1mRX7uxyx7AafXUkJsh
str3 = v7->functions->GetStringUTFChars(&v7->functions, (jstring)a6, 0);// 得到第三个传参字符串 2599295010
_android_log_print(4);
AboutActivity = v7->functions->FindClass(&v7->functions, "com/weico/international/mvp/v2/AboutActivity");// 获取AboutActivity类
AboutActivity_ = AboutActivity;
forVerify = v7->functions->GetStaticMethodID(&v7->functions, AboutActivity, "forVerify", "()Ljava/lang/String;");// 获取AboutActivity类方法forVerify
v15 = (void *)_JNIEnv::CallStaticObjectMethod(v7, AboutActivity_, forVerify);// 调用此方法得到一个字符串
forVerifyResult = v7->functions->GetStringUTFChars(&v7->functions, v15, 0);
v17 = strcmp(forVerifyResult, "#*123321"); // 比较字符串是否为#*123321
v7->functions->DeleteLocalRef(&v7->functions, AboutActivity_);
v7->functions->ReleaseStringUTFChars(&v7->functions, v15, forVerifyResult);
v7->functions->ReleaseStringUTFChars(&v7->functions, (jstring)a7, str4);
str3_ = *(unsigned __int8 *)str3;
_android_log_print(4);
v19 = v17 == 0; // 判断v17是否为0,并赋值给v19
if ( !v17 ) // 如果v17不为零
v19 = v32 == 1; // 判断v32是否为一,结果赋值给v19
if ( v19 && str3_ ) // 一系列的判断应该是为了检测是否为客户端,防止别人打包调用这个so
{
WeicoSecurityUtils = v7->functions->FindClass(&v7->functions, "com/sina/weibo/security/WeicoSecurityUtils");
v21 = WeicoSecurityUtils;
v22 = v7->functions->GetStaticMethodID(
&v7->functions,
WeicoSecurityUtils,
"aa4",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");// 到头来又调用了java层的算法aa4
v23 = _JNIEnv::CallStaticObjectMethod(v7, v21, v22);
v7->functions->ReleaseStringUTFChars(&v7->functions, str_, str);
v7->functions->ReleaseStringUTFChars(&v7->functions, (jstring)a5, str2);
v7->functions->ReleaseStringUTFChars(&v7->functions, (jstring)a6, str3);
v7->functions->DeleteLocalRef(&v7->functions, v21);
result = v23;
}
else
{
v25 = strlen(str);
v26 = strlen(str2);
v27 = (char *)malloc(v26 + v25 + 1);
sprintf(v27, "%s%s", str, str2);
v7->functions->NewStringUTF(&v7->functions, v27);
v28 = v7->functions->FindClass(&v7->functions, "com/sina/weibo/security/WeicoSecurityUtils");
v29 = v28;
v30 = v7->functions->GetStaticMethodID(&v7->functions, v28, "aa3", "(Ljava/lang/String;)Ljava/lang/String;");// 如果检测结果为假,则调用这个方法
v31 = _JNIEnv::CallStaticObjectMethod(v7, v29, v30);
v7->functions->ReleaseStringUTFChars(&v7->functions, str_, str);
v7->functions->ReleaseStringUTFChars(&v7->functions, (jstring)a5, str2);
v7->functions->ReleaseStringUTFChars(&v7->functions, (jstring)a6, str3);
v7->functions->DeleteLocalRef(&v7->functions, v29);
result = v31;
}
return result;
}
我们又看到java这边的aa4算法
我们写个hook,看看两次sha512的传参值分别是什么
脚本补充如下
function hookJava(){
Java.perform(function(){
var KotlinUtilKt = Java.use("com.weico.international.utility.KotlinUtilKt");
KotlinUtilKt.sha512.implementation = function(a){
var result = this.sha512(a);
console.log("sha512",a,result);
return result;
};
});
}
function callGenerateS(){
Java.perform(function(){
var WeicoSecurityUtils = Java.use("com.sina.weibo.security.WeicoSecurityUtils");
//得到context
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();
var result = WeicoSecurityUtils.generateS(context,"13123232323123121","g4c8CKKdwh3LE1mRX7uxyx7AafXUkJsh","2599295010","902784192");
console.log("WeicoSecurityUtils-result",result);
return result;
});
}
function main(){
hookJava();
}
setImmediate(main);
call一下
第一个sha512结果
eff01f442d0d8d5969660681bcf72240b9a016f083f657b39b2e8e5fa6680d0e6b70b0bfc785be38e87cb41f14209cfb39de42168575ca0dcfff1c313a9b8e61
第二个sha512结果
a50e5c2a080ecadb54bf1f7dfbba9b63ff2fac02a049593604af2144ac2070358fda4a613b93bcd4b3eafa020031262c418de65b8d2156fb919e2e2875ea0377
首先取第二个sha512的第0个字符a,得到a在字符串0123456789abcdef的对应位置11
取第一个sha512的第11位得到字符0
然后再取第二个sha512的第0+11个字符0,得到a在字符串0123456789abcdef的对应位置0
取第一个sha512的第0+11位得到字符0,一直循环下去,因为取到的第二个sha512的值为0,因此后面的字符串不会发生变化,算法基本就是这样子,不知道大家能不能听懂,哈哈,反正大家对照着java代码看一遍应该能知道我说的是什么意思了。
本来想做微博APP的,后面看了极速版比较小,就拿了这个来分析,没想到极速版的缩水了,核心代码放在了java里面,反正咱也不懂为什么要这么写代码,很迷惑,不过可能这就是加密艺术吧(手动狗头)。
新的一年祝大家新年快乐、身体健康,我看见什么比较有意思的加密还会给大家带来教程的,会继续把这个系列完善,当然只是针对新手,因为本人技术也只是刚入门的菜鸟,共勉。