这是一到安卓逆向:学到的解题思路特别多:
androidso_re 这一题拿到手就是一个apk文件,不过在进行测试的时候遇到了很多无语的事情(想在的题目质量是挺堪忧的)
安装好后一眼看上去就是一个判断输入内容得:
直接进行反编译,这里首先是:放入到jadx中
这里就是对于flag进行了判断,首先判断的是格式开头以及结尾“flag{}”,以及长度,32为位。中间的内容都在inspect函数中
紧跟着进入inspect函数:
这里返现其中是先进行DES的加密,之后又是进行了base64的加密,最终与”JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw==”进行比较,比较后,如果一样就是输出”You are right.”,如果错了就是”You are wrong.”。
这里的关键就是这个key和IV的值。进一步跟进进入jni类:
静态的在native层,这里可以直接hook,得到iv与key的值。下面就是,在联想模拟器中用的版本是7.1的但是一旦进行判断就会终止程序。调制了一会发现少了一个动态链接文件。这个文件就是libc++_shared.so
一开始hook就会触发这个程序停止运行。试了好久,最后想着用arm64-v8版本的去直接运行应该可已规避这种问题,后来找了一圈发现在雷电模拟器可以运行一次,之后就是hook直接获取:
hook的脚本:
脚本运行的命令:
frida -U -f com.example.re11113 -l C:\Users\wanghl\Desktop\CISCN\androidso_re_9ad389a3a4cdf48602cb08c6d51b3b32\hook.js
之后输入合适的flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
// hook.js Java.perform(function () { var jniClass = Java.use("com.example.re11113.jni"); jniClass.getiv.implementation = function () { var iv = this.getiv(); // 获取原始返回值 console.log("IV: " + iv); // 打印 IV 值 return iv; // 返回原始值,不影响应用运行 } jniClass.getkey.implementation = function () { var key = this.getkey(); // 获取原始返回值 console.log("key: " + key); // 打印 IV 值 return key; // 返回原始值,不影响应用运行 } });
最后就是解密的脚本:
import base64 from Crypto.Cipher import DES def decrypt_flag(encrypted_flag, key, iv): # 确保密钥长度为 8 个字节 key = key.ljust(8, b'\0') cipher = DES.new(key, DES.MODE_CBC, iv) decrypted_bytes = cipher.decrypt(base64.b64decode(encrypted_flag)) try: return decrypted_bytes.decode('utf-8').rstrip('\x00') except UnicodeDecodeError: return decrypted_bytes.decode('latin-1').rstrip('\x00') def main(): encrypted_flag = "JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw==" # 通过Frida脚本拿到key和iv key = b'A8UdWaeq' iv = b'Wf3DLups' flag = decrypt_flag(encrypted_flag, key, iv) print("Decrypted flag:", flag) if __name__ == '__main__': main() #Decrypted flag: 188cba3a5c0fbb2250b5a2e590c391ce
最后成功的拿到flag
赛后反思:
这里后面发现还有一种方法,就是直接在so文件中逆出整个的加密过程,这个方法妙啊,但是有些慢。
下面来进行分析一下:
这里看到了码表,一般会直接猜base64加密: 其中先是rot13的加密过程,这里为了方便对比直接放上用C语言实现rot13加密的过程:
#include <stdio.h> // ROT13 加密函数 void rot13(char *str) { char *p = str; while (*p) { if ((*p >= 'A' && *p <= 'Z')) { *p = ((*p - 'A' + 13) % 26) + 'A'; } else if ((*p >= 'a' && *p <= 'z')) { *p = ((*p - 'a' + 13) % 26) + 'a'; } p++; } } int main() { char text[100]; printf("请输入要加密的文本: "); fgets(text, sizeof(text), stdin); // 去除换行符 size_t len = strlen(text); if (len > 0 && text[len-1] == '\n') { text[len-1] = '\0'; } rot13(text); printf("加密后的文本: %s\n", text); return 0; }
这里就不放解密文本了,因位它是对称加密
下面放上IDA的so文件的关键部分:
所以进行rot13解密,后面发现这里的与标准的rot不同,
这里是变换为16位,65-49=97-81=16。后面就是base64
Wf3DLups这个是位移IV
下面是key:
这里很容易就发现可能是RC4加密:RC4加密是需要输入key值得,这里不难发现是有的:
YourRC4Key,而加密后的密文:TFSecret_Key
但是这里有个好玩得函数jiejie(姐姐函数)
这里先贴上一个RC4加解密的C语言代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> // RC4 初始化函数 void rc4_init(unsigned char *s, unsigned char *key, int key_length) { int i, j = 0; unsigned char k[256]; unsigned char tmp; // 初始化 S 数组 for (i = 0; i < 256; i++) { s[i] = i; k[i] = key[i % key_length]; } // 初始排列 for (i = 0; i < 256; i++) { j = (j + s[i] + k[i]) % 256; tmp = s[i]; s[i] = s[j]; s[j] = tmp; } } // RC4 加密/解密函数 void rc4_crypt(unsigned char *s, unsigned char *data, int data_length) { int i = 0, j = 0, t, k; unsigned char tmp; for (k = 0; k < data_length; k++) { i = (i + 1) % 256; j = (j + s[i]) % 256; tmp = s[i]; s[i] = s[j]; s[j] = tmp; t = (s[i] + s[j]) % 256; data[k] ^= s[t]; } } int main() { unsigned char key[] = "mysecretkey"; // 密钥 unsigned char data[] = "Hello, World!"; // 要加密或解密的数据 int data_length = strlen((char *)data); unsigned char s[256]; printf("原始数据: %s\n", data); // 初始化 RC4 rc4_init(s, key, strlen((char *)key)); // 加密 rc4_crypt(s, data, data_length); printf("加密后的数据: %s\n", data); // 重新初始化 RC4 以便解密 rc4_init(s, key, strlen((char *)key)); // 解密 rc4_crypt(s, data, data_length); printf("解密后的数据: %s\n", data); return 0; }
仔细分析发现下面还有异或的操作:
异或的是0x038933B8540C206A
后得到:A8UdWaeqÁ²k. 下面又有个细节,就是最后只要了8位
最终得key:A8UdWaeq
#总结:这里思路刚开始确实没打开,还有就是感觉hook的方式更快一些