首页
社区
课程
招聘
[原创]一次尝试某某会APP签名算法逆向追踪:从抓包到SO层
发表于: 1天前 1262

[原创]一次尝试某某会APP签名算法逆向追踪:从抓包到SO层

1天前
1262

【原创】一次完整的App签名某某会算法逆向追踪:从抓包到SO层
最近在逆向某某会App的登录接口,抓包发现请求头里有个签名,看起来挺有意思,决定完整追踪一下它的生成过程。下面是我一步步的记录。

1 burp包上的证据痕迹 标出疑似加密

authorization OAuth api_sign=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (标出)疑似加密 找

看到这一长串,第一反应是:这八成是个签名算法。那就顺着这个字段往下追吧。

2 反编译提取1关键词搜索

OAuth api_sign= (疑似的加密)

用Jadx打开APK,直接搜"api_sign",看看能不能定位到关键代码。

3 文本凑格式 找架构相似 找出关键函数

apiProccessModel4.apiSign = str;
if (str != null) {
apiProccessModel4.request.addHeader("Authorization", "OAuth api_sign=" + str);
}

可见str就是那个疑似加密

运气不错,一下就找到了。这里有个apiSign被赋值为str,然后塞进Header里。那这个str就是我们要找的加密值。

4

 str=b.b() 带=号的直接定义

找str对应的意思
前面有 string str =''
一大堆过程定义

但是后面有str = b.b(context2, e10, apiProccessModel3.tokenSecret, apiProccessModel3.url);
直接表达

往上翻了一下,前面一堆初始化代码,但真正的赋值在这里:str是从一个b.b()方法来的。传了context、参数、tokenSecret和url进去。

5 看return

public static String b(Context context, TreeMap<String, String> treeMap, String str, String str2) {
if (treeMap != null && TextUtils.isEmpty(treeMap.get(ApiConfig.SKEY))) {
treeMap.put(ApiConfig.SKEY, f(context, new String[0]));
}
return a(context, treeMap, str);

点进b.b()看一眼,它调了a()方法,看来真正的逻辑在a()里。

6 return + .apiSign(context, treeMap, str) 实际函数调用

private static String a(Context context, TreeMap<String, String> treeMap, String str) {
try {
if (VCSPCommonsConfig.getContext() == null) {
VCSPCommonsConfig.setContext(context);
}
String apiSign = VCSPSecurityBasicService.apiSign(context, treeMap, str);
if (!TextUtils.isEmpty(apiSign)) {
return apiSign;

a()方法里调了VCSPSecurityBasicService.apiSign(),继续跟。

7 看return

public static String apiSign(Context context, TreeMap<String, String> treeMap, String str) throws Exception {
if (context == null) {
context = VCSPCommonsConfig.getContext();
}
return VCSPSecurityConfig.getMapParamsSign(context, treeMap, str, false);

apiSign()里又调了getMapParamsSign(),这层套一层的,有点耐心慢慢跟。

8 继续看return 代码比较长 拉下一些看

public static String getMapParamsSign(Context context, TreeMap<String, String> treeMap, String str, boolean z10) {
String str2 = null;
if (treeMap == null) {
return null;
}
boolean z11 = false;
Set<Map.Entry<String, String>> entrySet = treeMap.entrySet();
if (entrySet != null) {
Iterator<Map.Entry<String, String>> it = entrySet.iterator();
while (true) {
if (it == null || !it.hasNext()) {
break;
}
Map.Entry<String, String> next = it.next();
if (next != null && next.getKey() != null && ApiConfig.USER_TOKEN.equals(next.getKey()) && !TextUtils.isEmpty(next.getValue())) {
z11 = true;
break;
}
}
}
if (z11) {
if (TextUtils.isEmpty(str)) {
str = VCSPCommonsConfig.getTokenSecret();
}
str2 = str;
}
return getSignHash(context, treeMap, str2, z10);

getMapParamsSign()代码有点长,但最后return的是getSignHash(),看来关键还在后面。

9 继续看return

public static String getSignHash(Context context, Map<String, String> map, String str, boolean z10) {
try {
return gs(context.getApplicationContext(), map, str, z10);
} catch (Throwable th2) {
VCSPMyLog.error(clazz, th2);
return "error! params invalid";
}
}

getSignHash()里调了gs(),这个gs()看起来有点东西。

10 重点!!!

private static String gs(Context context, Map<String, String> map, String str, boolean z10) {
try {
if (clazz == null || object == null) {
synchronized (lock) {
initInstance();
}
}
if (gsMethod == null) {
gsMethod = clazz.getMethod("gs", Context.class, Map.class, String.class, Boolean.TYPE);
}
return (String) gsMethod.invoke(object, context, map, str, Boolean.valueOf(z10));
} catch (Exception e10) {
e10.printStackTrace();
return "Exception gs: " + e10.getMessage();
} catch (Throwable th2) {
th2.printStackTrace();
return "Throwable gs: " + th2.getMessage();
}
}

private static void initInstance() {
if (clazz == null || object == null) {
try {
int i10 = KeyInfo.f69594a;
clazz = KeyInfo.class;
object = KeyInfo.class.newInstance();
} catch (Exception e10) {
e10.printStackTrace();
}
}
}

哇塞,这里居然是反射调用!initInstance()里把clazz赋值为KeyInfo.class,然后通过反射调用gs()。难怪前面一直找不到具体实现,原来是藏在这里了。

尝试了getMethod("gs", Context.class, Map.class, String.class, Boolean.TYPE); 但是内部自带函数
尝试了invoke(object, context, map, str, Boolean.valueOf(z10)); 但是内部自带函数
可以看到 initInstance()函数在上面被调用 找 initInstance()函数定义位置在下面 gs来自clazz 都殊途同归到看下面 clazz = KeyInfo.class;

就是说gs找不到直接定义表达 但是可以推断来自KeyInfo

反射绕了一圈,最后还是指向KeyInfo类。那好,直接去看KeyInfo。

11 果然找到了

public static String gs(Context context, Map<String, String> map, String str, boolean z10) {
try {
try {
return gsNav(context, map, str, z10);
} catch (Throwable th2) {
return "KI gs: " + th2.getMessage();
}
} catch (Throwable unused) {
SoLoader.load(context, LibName);
return gsNav(context, map, str, z10);
}
}
private static native String gsNav(Context context, Map<String, String> map, String str, boolean z10);

可以看到最后到native层了 JNI开发 那去把apk改成zip 去文件夹里面找so文件拿ida看

看到native关键字就懂了——算法在SO层。gs()里调了gsNav(),这个gsNav()是native方法,得去SO里找了。

public class KeyInfo {
private static final String LibName = "keyinfo";

文件名这里 到ida去搜 带lib

12 打开ida 放入"libkeyinfo"文件 因为是jni开发 所以直接搜java_
找到对应的函数打开 按F5把汇编转C
导入jni.h文件

13 右键点击a1函数 --> convert to struct --> JNIEnv 然后hide codes


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 19小时前 被kanxue编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 5871
活跃值: (10212)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
没来的及保存,被管理改了
12小时前
0
游客
登录 | 注册 方可回帖
返回