解题过程:
这一题拿到手就是一个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直接获取:
之后输入合适的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
总结
其实刚开始想过去看so文件,但是呢,伪代码又臭又长,所以看到反编译后,在native层,就直接尝试了hook,结果就知道会在运行上出现问题,反正题目嘛,就是那样,具体会运行出现程序异常终止的原因,有的博主已经分析过了,在这里就不重复分析这里的原因了,这里主要还是记录做法。
本人菜鸟一枚,如有错误还请大佬多多指正。