2025解题领红包 Android中级详细WP
本帖最后由 Command 于 2025-2-13 07:44 编辑## APK
Jadx打开APK, 搜索"**错误**"可以找到这里
```java
public final class BattleActivityKt$BattleScreen$1$3$1$1$2 extends AbstractC1360j implements InterfaceC1335a {
// ......
public final void invoke() {
String BattleScreen$lambda$21;
BattleScreen$lambda$21 = BattleActivityKt.BattleScreen$lambda$21(this.$activationCode$delegate);
if (!BattleActivityKt.Check(BattleScreen$lambda$21)) {
Toast.makeText(this.$context, "秘钥错误,请重试!", 0).show();
return;
}
// ......
}
}
```
能够发现这里调用了**BattleActivityKt.Check**对输入的Flag进行校验
```java
public static final native boolean Check(String str);
```
发现是Native函数, 那就直接看**libwuaipojie2025_game.so**
## SO
IDA, 启动! (本文结合Unidbg分析ARM64的so)
### 找函数
发现**Check**在so里并未被导出, 找**JNI_OnLoad**
```c
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
__int64 v3; // x0
_QWORD v4; // BYREF
v4 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
if ( (*vm)->GetEnv(vm, (void **)v4, 65542LL) )
return -1;
v3 = (*(__int64 (__fastcall **)(_QWORD, const char *))(*(_QWORD *)v4 + 48LL))(
v4,
"com/zj/wuaipojie2025_game/ui/BattleActivityKt");
if ( !v3 )
return -1;
if ( (*(int (__fastcall **)(_QWORD, __int64, char **, __int64))(*(_QWORD *)v4 + 1720LL))(
v4,
v3,
off_163510,
1LL) < 0 )
return -1;
return 65542;
}
```
(这啥玩意? 算了懒得分析了, 直接Unidbg模拟一下看看)
```java
AndroidEmulator emulator = AndroidEmulatorBuilder
.for64Bit()
.addBackendFactory(new Unicorn2Factory(true))
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
VM vm = emulator.createDalvikVM(new File("52pojie.apk"));
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary(new File("libwuaipojie2025_game.so"), true);
dm.callJNI_OnLoad(emulator);
```
由此可知**Check**的地址为**0xe8c54**
```
RegisterNative(com/zj/wuaipojie2025_game/ui/BattleActivityKt, Check(Ljava/lang/String;)Z, RX@0x400e8c540xe8c54)
```
```c
// Check (a3为传入str)
bool __fastcall sub_E8C54(JNIEnv *a1, jobject a2, jstring *a3)
{
const char *InputUTF; // x0
const char *Input; // x21
char v11; // w0
char v14; // w8
void (__fastcall *v15)(_QWORD *, const char *, __int64, _QWORD *); // x22
_QWORD *v16; // x23
_BOOL4 v19; // w22
_QWORD v21; // BYREF
_OWORD v22; // BYREF
InputUTF = (*a1)->GetStringUTFChars(a1, a3, 0LL);
if ( InputUTF )
{
Input = InputUTF;
// ......省略部分不太重要的代码, 大概是环境检测? 总之不重要
v15 = (void (__fastcall *)(_QWORD *, const char *, __int64, _QWORD *))nullsub_1(*(_QWORD *)((unsigned __int64)v22 & 0xFFFFFFFFFFFFFFF7LL | (8LL * (((unsigned __int8)(v11 | v14) ^ (((unsigned int)ao ^ (unsigned int)a) >> 24)) & 1)))); // 计算v15的地址 (其实调用的就是ao, 但不一定能确定, 还得Unidbg)
dword_16359C = -559038669;
v21 = 0LL;
v21 = 0LL;
v16 = (_QWORD *)operator new[](0x13uLL);
v15(v21, Input, 19LL, v16); // 调用v15获取v16 对应指令为BLR X22
v19 = *v16 == 0x72ECF89BAF8F2748LL
&& v16 == 0xB63AE26B0C720798LL
&& *(_QWORD *)((char *)v16 + 11) == 0xF75942B63AE26B0CLL;// 检验
operator delete[](v16);
(*a1)->ReleaseStringUTFChars(a1, a3, Input);
}
else
{
return 0;
}
return v19;
}
```
可以发现`v15`的地址不能确定, 直接用Unidbg断在调用`v15`的那里即可
```java
emulator.attach().addBreakPoint(md.base+0xe8f44); // 0xe8f44为指令BLR X22的偏移量, 断下后看X22寄存器减掉基址就行
```
最后可以发现调用函数为**ao** (0xe9f60)
### 分析
```c
// a2就是输入的字符串, a3为19, result为最后结果
void __fastcall ao(__int128 *a1, const char *a2, __int64 a3, _QWORD *result)
{
__int64 i; // x23
__int128 v8; // BYREF
__int64 v9; //
v9 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); // 没用
v8 = *a1; // 传入a1为定值, 故v8也为定值
if ( a3 )
{
for ( i = 0LL; i != a3; ++i )// 循环19次, 侧面说明Flag长度为19
{
if ( (i & 0xF) == 0 )
sub_E9954(&v8);// 这里的这个函数其实不用跟进去看...... (和输入啥的都没关系, v8一开始就是定值, v8更新的次数也固定)
*((_BYTE *)result + i) = *(_BYTE *)((unsigned __int64)&v8 | i & 0xF) ^ a2; // 实际上做的事情 ("^"左侧其实都是确定的值, 不随输入的改变而改变)
}
}
}
```
由此我们不难发现 (只要没有和我一样一开始就跑偏去看**sub_E9954**), 实际上就是将字符串的每一位与不同的数进行异或然后存入`result`而已
我们只要获取到`i`在0-18时`*(_BYTE *)((unsigned __int64)&v8 | i & 0xF)`的值就好, 我选择使用Unidbg对后面的`EOR`(ARM中异或指令)进行Hook
而在那里的汇编是这样的
```assembly
; ......
ORR X8, X24, X25
LDRB W9,
LDRB W8,
EOR W8, W8, W9; Addr=0xE9FB8
STRB W8,
ADD X23, X23, #1
; ......
```
在这里先简单讲一下`EOR`指令
```assembly
EOR W8, W8, W9; 将W8与W9进行异或, 结果存回W8 (在这里W8就是^左边那一长串, W9就是a2)
```
### 获取秘钥
Unidbg指令Hook
```java
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
if (address == md.base + 0xe9fb8) {// 0xe9fb8就是刚才说的异或指令的偏移
// 输出 W8, chr(a2)
System.out.printf("0x%1$x, %2$c", emulator.getBackend().reg_read(Unicorn.UC_ARM64_REG_W8).intValue(), (char) emulator.getBackend().reg_read(Unicorn.UC_ARM64_REG_W9).intValue());
System.out.println();
}
}
@Override
public void onAttach(UnHook unHook) {}
@Override
public void detach() {}
}, md.base, md.base + md.size, null);
```
然后我们可以得到每一位要与之异或的值
```python
```
那么现在我们有异或的值, 还需要知道正确的结果才能得到Flag
这时往前看, 回到刚才的函数**Check** (`sub_E8C54`), 有这样一段代码
```c
_QWORD *v16// Tips: 单个_QWORD 8个字节
v16 = (_QWORD *)operator new[](0x13uLL); // 这里申请了一块长度为0x13(19)的内存空间
v15(v21, Input, 19LL, v16); // v16是刚才的result
v19 = *v16 == 0x72ECF89BAF8F2748LL // v16的前8字节
&& v16 == 0xB63AE26B0C720798LL// 又有8字节
&& *(_QWORD *)((char *)v16 + 11) == 0xF75942B63AE26B0CLL; // 这里有可能看不明白, 这其实是从11-19的8个字节, 也就是说包括第二个8字节尾部的5字节
return v19;
```
也就是, 输入正确的情况下`v16`里应该是这样的
```
v16:
0000: 48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6
0010: 42 59 F7 00 00 00 00 00 00 00 00 00 00 00 00 00
数字对应的字节 (小端序)
0x72ECF89BAF8F2748LL: 48 27 8F AF 9B F8 EC 72
0xB63AE26B0C720798LL: 98 07 72 0C 6B E2 3A B6
0xF75942B63AE26B0CLL: 0C 6B E2 3A B6 42 59 F7
后两个明显可以看到重合部分
```
正确结果有了, 异或的值有了, 直接写Python脚本解密
```python
Flag = ''
for i in zip(, ):
# print(i ^ i)
Flag += chr(i ^ i)
print(Flag)
```
最后输出结果
```
flag{md5(uid+202eö
```
好像最后那点不太对? 不过猜也能猜出来是`uid+2025` 毕竟2025年嘛
实际上应该是`flag{md5(uid+2025)}` ......吧? (但我从apk里输入进去也不对啊......)
(顺带提一嘴, 秘钥输入框需要在进入战斗后点右上角那个圆形才会出现... 应该不会有人和我一样直到快做完题才发现吧) ao是错误方法,这里加密的秘钥和正确的秘钥只差几个字节,导致异或出来的结果可以猜出来。另外前面的检测方法没过掉就走ao这个错误方法 前面的思路基本和我一样,也到0xe8c54了,本来是打算断掉调试看能不能行,但由于IDA捣鼓半天也没捣鼓出调试,就放弃了。光啃代码啃半天进度缓慢就放弃了,哎。。可能是我还在学,还有好多方法或者工具不知道,所以没啥思路了。等我继续看完安卓逆向这档事吧,目前还没到frida{:1_924:} 我也是搞到这儿,然后提交了好多次也没成功,其中也试过2025,看来这道题跟我无缘啊{:1_923:} nanaqilin 发表于 2025-2-13 08:46
我也是搞到这儿,然后提交了好多次也没成功,其中也试过2025,看来这道题跟我无缘啊
你应该是把这个交上去了? 需要把UID和2025拼接到一起然后MD5再提交的 Command 发表于 2025-2-13 08:47
你应该是把这个交上去了? 需要把UID和2025拼接到一起然后MD5再提交的
是拼起来?我直接取的加和,然后再求的Md5{:1_909:} 那个2025不出来应该是你第二轮的异或密钥找错了,他在0x10位的时候重新获取了一次密钥。
可以看看我的writeup
还有建议把几个帖子全部合到一起,这样看起来更方便。 安卓中级题在app上提交的flag的格式是什么样的告诉我flag{md5(uid+2025)},提交半天都是错的
提交需要带flag吗?拼接时要带加号吗? 本帖最后由 Command 于 2025-2-13 10:21 编辑
风子09 发表于 2025-2-13 10:09
安卓中级题在app上提交的flag的格式是什么样的告诉我flag{md5(uid+2025)},提交半天都是错的
提交需要 ...
刚试了一下app上好像咋提交都不对; 但按照这个flag的格式往服务端上提交是没问题的 风子09 发表于 2025-2-13 10:09
安卓中级题在app上提交的flag的格式是什么样的告诉我flag{md5(uid+2025)},提交半天都是错的
提交需要 ...
uid+2025 是字符串上的拼接 app里面怎么提交都是错的,不知道为什么 菜鸟围观一下!!!!!
页:
[1]
2