【2025春节】解题领红包之四 Android 中级题WriteUp
本帖最后由 GrumpyJellyfish 于 2025-2-14 03:57 编辑# 【2025春节】解题领红包之四 Android 中级题WriteUp
# 0x00 初探虎先锋
> **题目题干如下:**
>
> 出题老师:正己
>
> 题目简介:《黑神话·虎先锋の猴肉108种烹饪方式》通关失败后,张师傅怒摔手柄,突然瞥见室友王大爷的屏幕金光大作:"无限定身术+金刚不坏+暴击999%!"
>
> "年轻人,听说过科学修仙吗?"王大爷反手一个Alt+F4,显示器残留着神秘代码:
>
> 风灵月影宗弟子认证:输入[宗门秘钥]可解锁修改权限
>
> (温馨提示:秘钥格式请参考——flag{我是秘钥},"我是秘钥"的真实内容需要动态运算得出)
第1步:直接进入战斗!然后败了😭
第2步:根据提示点击右上角风灵月影
第3步:输入密钥查看交互信息
第4步:jadx查找字符串“密钥错误,请重试!”
第5步:找到实现代码如下:
```java
/* renamed from: invoke */
public final void m5invoke() {
String BattleScreen$lambda$21;
BattleScreen$lambda$21 = BattleActivityKt.BattleScreen$lambda$21(this.$activationCode$delegate);
//只要Check函数返回True就能激活风灵月影
if (!BattleActivityKt.Check(BattleScreen$lambda$21)) {
Toast.makeText(this.$context, "秘钥错误,请重试!", 0).show();
return;
}
BattleActivityKt.BattleScreen$lambda$7(this.$playerHp$delegate, BattleActivityKt.BattleScreen$lambda$3(this.$maxHp$delegate));
BattleActivityKt.BattleScreen$lambda$10(this.$enemyHp$delegate, BattleActivityKt.BattleScreen$lambda$3(this.$maxHp$delegate));
this.$battleResult$delegate.setValue("");
this.$context.clearBattleLog();
BattleActivityKt.BattleScreen$lambda$25(this.$playerAttackPower$delegate, 9999);
this.$playerDefense.f5315i = 999;
BattleActivityKt.updateLog(this.$context, "════════════════════");
BattleActivityKt.updateLog(this.$context, "★ 风灵月影已激活 ★");
BattleActivityKt.updateLog(this.$context, "➤ 攻击力提升至9999");
BattleActivityKt.updateLog(this.$context, "➤ 防御力提升至999");
BattleActivityKt.updateLog(this.$context, "➤ 生命值已重置");
BattleActivityKt.updateLog(this.$context, "════════════════════");
BattleActivityKt.BattleScreen$lambda$19(this.$showActivationDialog$delegate, false);
}
}
```
第6步:算法助手直接hook这个Check函数,直接拿下虎先锋!
第7步:提交flag!欸不对我的flag呢?
# 0x01 攻克Check函数
**jadx中查找Check的声明发现其只有声明,调用了SO层实现**
```JAVA
//声明函数
public static final native boolean Check(String str);
//调用SO
static {
System.loadLibrary("wuaipojie2025_game");
}
```
解压缩APK拿到libwuaipojie2025_game.so文件,直接上IDA
>分析了arm64-v8a、X86、armeabi-v7a感觉armeabi-v7a的难度较低,推荐armeabi-v7a
# 0x02 IDA分析libwuaipojie2025_game.so
第1步:找到函数实现
> 在export中搜索java没有找到相关函数,初步判定为JNI动态注册。
>
> 看到有JNI_OnLoad函数从他入手
```cpp
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
jint v2; // r5
int v3; // r0
int v5; // BYREF
//此处v3、v5应该都是JNIEnv *类型;off_134D10应该就是g_methods函数
if ( (*vm)->GetEnv(vm, (void **)&v5, 65542) )
return -1;
v3 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)v5 + 24))(
v5,
"com/zj/wuaipojie2025_game/ui/BattleActivityKt");
v2 = -1;
if ( v3 && (*(int (__fastcall **)(int, int, char **, int))(*(_DWORD *)v5 + 860))(v5, v3, off_134D10, 1) > -1 )
return 65542;
return v2;
}
```
根据JNI实现经验修改部分变量类型:
```cpp
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
jint v2; // r5
JNIEnv *env; // r0
JNIEnv *v5; // BYREF
//此处v3、v5应该都是JNIEnv *类型;off_134D10应该就是g_methods函数
if ( (*vm)->GetEnv(vm, (void **)&v5, 65542) )
return -1;
env = (JNIEnv *)(*v5)->FindClass(v5, "com/zj/wuaipojie2025_game/ui/BattleActivityKt");
v2 = -1;
if ( env && (*v5)->RegisterNatives(v5, env, (const JNINativeMethod *)g_methods, 1) > -1 )
return 65542;
return v2;
}
```
转到g_methods的汇编地址看到如下内容:
```assembly
.data:00134D10 off_134D10 DCD aCheck ; DATA XREF: LOAD:0000009C↑o
.data:00134D10 ; JNI_OnLoad+46↑o ...
.data:00134D10 ; "Check"
.data:00134D14 DCD aLjavaLangStrin ; "(Ljava/lang/String;)Z"
.data:00134D18 DCD sub_BE440+1
.data:00134D1C unk_134D1C DCB 0x73 ; s ; DATA XREF: A(void)+20↑o
.data:00134D1C ; A(void)+2E↑o ...
```
可知sub_BE440函数就是Check的实现位置,反编译结果如下:
```cpp
bool __fastcall sub_BE440(int a1, int a2, int a3)
{
int v5; // r4
int v6; // r0
int v7; // r9
int v8; // r4
int v9; // r6
int v10; // r1
unsigned int v11; // r6
char *v12; // r4
_BOOL4 v13; // r8
int v14; // r0
void (__fastcall *v15)(_BYTE *, int, int, void *); // r8
void *v16; // r5
int v17; // r4
const std::nothrow_t *v18; // r1
unsigned __int64 v20; //
_BYTE v21; // BYREF
_QWORD v22; // BYREF
v5 = 0;
v6 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
if ( v6 )
{
v7 = v6;
HIDWORD(v20) = a3;
v8 = A();
v9 = CNJAK();
if ( !byte_134E49 )
{
afdm::decrypt_buffer((afdm *)byte_134D7E, &byte_4, 0xA8FC3415, v20);
byte_134E49 = 1;
}
v10 = -1;
if ( v8 )
v10 = 1;
v11 = v9 + v10;
v12 = getenv(byte_134D7E);
v13 = v12 == 0 || v11 < 3;
v14 = jgbjkb();
if ( v11 <= 2 && v12 )
{
v13 = 1;
dword_134D90 = -559038669;
}
v22 = *(_QWORD *)&off_12FCE8;
v22 = *(_QWORD *)&off_12FCF0;
v15 = (void (__fastcall *)(_BYTE *, int, int, void *))nullsub_9(*(_DWORD *)((unsigned int)v22 | (4 * ((v14 | v13) ^ (unsigned int)sub_BE6CC & 1 ^ (((unsigned int)ao ^ (unsigned int)a) >> 24) & 1))));
dword_134D90 = -559038669;
memset(v21, 0, sizeof(v21));
v16 = (void *)operator new[](0x13u);
v15(v21, v7, 19, v16);
v17 = memcmp(v16, &unk_3A0FC, 0x13u);
operator delete[](v16, v18);
(*(void (__fastcall **)(int, _DWORD, int))(*(_DWORD *)a1 + 680))(a1, HIDWORD(v20), v7);
return v17 == 0;
}
return v5;
}
```
根据已知内容更改变量类型与变量名:
```cpp
jboolean __fastcall check(JNIEnv *env, jobject obj, jstring jkey) //此处根据JNI实现和Check声明修改变量类型
{
jboolean v5; // r4
const char *key; // r0
const char *key1; // r9
int v8; // r4
int v9; // r6
int v10; // r1
unsigned int v11; // r6
char *v12; // r4
_BOOL4 v13; // r8
int v14; // r0
void (__fastcall *fun_enc)(_BYTE *, const char *, int, void *); // r8
void *v16; // r5
int v17; // r4
const std::nothrow_t *v18; // r1
unsigned __int64 v20; //
_BYTE v21; // BYREF
_QWORD v22; // BYREF
v5 = 0;
key = (*env)->GetStringUTFChars(env, jkey, 0);
if ( key )
{
key1 = key;
HIDWORD(v20) = jkey; // 让v20变量的前8位赋值位jkey
v8 = A();
v9 = CNJAK();
if ( !byte_CA839E49 )
{
afdm::decrypt_buffer((afdm *)byte_CA839D7E, (char *)4, 0xA8FC3415, v20);
byte_CA839E49 = 1;
}
v10 = -1;
if ( v8 )
v10 = 1;
v11 = v9 + v10;
v12 = getenv(byte_CA839D7E);
v13 = v12 == 0 || v11 < 3;
v14 = jgbjkb();
if ( v11 <= 2 && v12 )
{
v13 = 1;
dword_CA839D90 = -559038669;
}
//上面这一大段都搞不清楚是干嘛的,一开始看名字还以为是解密函数,浪费了好多时间。
//正己大佬说这个是反调试,如果没过就会走错误的ao方法,过了走a正确方法
v22 = *(_QWORD *)&off_C16B1CE8;// 指向函数a的指针
v22 = *(_QWORD *)&off_C16B1CF0;// 指向函数ao的指针
//这个应该就是加密函数,通过动态计算赋值fun_enc的地址 fun_enc = (void (__fastcall *)(_BYTE *, const char *, int, void *))nullsub_9(*(_DWORD *)((unsigned int)v22 | (4 * ((v14 | v13) ^ (unsigned int)sub_CA7C36CC & 1 ^ (((unsigned int)ao ^ (unsigned int)a) >> 24) & 1))));
//fun_enc = (void (__fastcall *)(...)) nullsub_9(*(_DWORD *)( (unsigned int)v22 | (4 * ( ... )) ));
//关键逻辑:表达式 (unsigned int)v22 | (4 * (...)) 用于计算 v22 数组的索引偏移
//v22 是数组基址,4 * (...) 是索引偏移(QWORD 类型占8字节,但此处可能因对齐或混淆设计为4字节步进)
//实际偏移由 (v14 | v13) ^ ... 的位运算结果决定,最终目标为 0(选a)或 1(选ao)
dword_CA839D90 = -559038669;
memset(v21, 0, sizeof(v21)); //把v21的值全部赋0
v16 = (void *)operator new[](0x13u); //调用加密函数,这里新申请了一段0x13u的内存v16应该是把结果返回到v16
fun_enc(v21, key1, 19, v16); //这里判断v16和&unk_CA73F0FC,共0x13(十进制19)位应该就是校验flag是否正确,如果两值前19位相等的话的话v17赋值0
v17 = memcmp(v16, &unk_CA73F0FC, 0x13u);
operator delete[](v16, v18);
(*env)->ReleaseStringUTFChars(env, (jstring)HIDWORD(v20), key1);
//当v17的值0时返回true
return v17 == 0;
}
return v5;
}
```
反调试分析
```cpp
//根据下面这个表达式返回的值确定fun_enc指向的函数地址
(unsigned int)v22 | (4 * (
(v14 | v13) ^
(sub_CA7C36CC & 1) ^
(((ao ^ a) >> 24) & 1)
))
//返回只有两种可能v22或v22,其中v22 = *(_QWORD *)&off_C16B1CE8;(指向函数a的指针)v22 = *(_QWORD *)&off_C16B1CF0;(指向函数ao的指针)
//下面这个表达式返回的就决定了是v22指向的是还是
(v14 | v13) ^ //其中v13、v14在上面进行了赋值;v13=(getenv()==0),v14=jgbjkb()
(sub_CA7C36CC & 1) ^ //sub_CA7C36CC只return 0,所以&1结果为0
(((ao ^ a) >> 24) & 1) //ao ^ a默认没改的话为0,“>>24”取高8位也为0,所以&1结果为0
```
所有这里的反调试关键点在于上面需要满足以下两个条件:
- getenv()!=0
- jgbjkb()==0
加密函数
```cpp
//调用代码fun_enc(v21, key1, 19, v16);
//这里times为19
void __fastcall fun_enc(_BYTE *a1, char *a2, int times, void *a4)
{
__int64 v5; // d17
int i; // r6
int v9; // r5
char v10; // r0
_BYTE v11; // BYREF
v5 = *((_QWORD *)a1 + 1);
*(_QWORD *)v11 = *(_QWORD *)a1; //把a1赋值给v11其实就是上面父函数的v21,输入的时候v21是全0的
*(_QWORD *)&v11 = v5;
if ( times )
{
for ( i = 0; i != times; ++i )
{
v9 = i & 0xF; //当循环到i=0、i=16时v9会等于0进入下面的if
if ( (i & 0xF) == 0 )
fun_enc2(v11); //这个函数十分复杂(分析了大半天看不懂放弃了),会改变v11的内容
v10 = a2 ^ v11; //v10的值为a2异或v11,其中a2就是flag,v9是0到15,v11是一个16字节的数组,会在v9=0时发生变化。
*((_BYTE *)a4 + i) = v10; //赋值a4,就是父函数的v16
v11 = v10; //改变v11的第v9位
}
}
}
```
&unk_CA73F0FC的值如下:
```assembly
.rodata:0003A0FC unk_3A0FC DCB 0x48 ; H ; DATA XREF: sub_BE440+216↓o
.rodata:0003A0FD DCB 0x27 ; '
.rodata:0003A0FE DCB 0x8F
.rodata:0003A0FF DCB 0xAF
.rodata:0003A100 DCB 0x9B
.rodata:0003A101 DCB 0xF8
.rodata:0003A102 DCB 0xEC
.rodata:0003A103 DCB 0x72 ; r
.rodata:0003A104 DCB 0x98
.rodata:0003A105 DCB 7
.rodata:0003A106 DCB 0x72 ; r
.rodata:0003A107 DCB0xC
.rodata:0003A108 DCB 0x6B ; k
.rodata:0003A109 DCB 0xE2
.rodata:0003A10A DCB 0x3A ; :
.rodata:0003A10B DCB 0xB6
.rodata:0003A10C DCB 0x42 ; B
.rodata:0003A10D DCB 0x59 ; Y
.rodata:0003A10E DCB 0xF7
//48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6 42 59 F7
```
**结论:**
> 要想Check返回True需要v16=48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6 42 59 F7
>
> v16的值在函数fun_enc中赋值,其赋值形式是flag与fun_enc内的数组v11异或(可逆)
>
> 那么只需要知道v11的内容与48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6 42 59 F7异或回去即可得到flag。
**需注意!!!v11是一个16个字节元素的数组,48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6 42 59 F7(有19个字节)。而v11在第1次和17次使用时会被fun_enc2改变,所以动态调试的关键点在v11的两次变化过程的值**
# 0x03 上动态调试
第1步:改apk包,lib目录只留下armeabi-v7a那个文件夹,然后更改权限增加下面两个权限。
> android:debuggable="true"
>
> android:extractNativeLibs="true"
第2步:IDA动态调试
第3步:先异或获取前16位flag
```python
#i=0时v11的值为
#2E 4B EE C8 E0 95 88 47 B0 72 1B 68 40 D0 0A 84
#与&unk_CA73F0FC异或
#48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6 42 59 F7
# 输入的16进制字符串
hex_str1 = "2E 4B EE C8 E0 95 88 47 B0 72 1B 68 40 D0 0A 84"
hex_str2 = "48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6"
# 将16进制字符串转换为字节列表
bytes1 = bytes.fromhex(hex_str1.replace(" ", ""))
bytes2 = bytes.fromhex(hex_str2.replace(" ", ""))
# 确保两个字节序列长度相同
assert len(bytes1) == len(bytes2), "字节序列长度不匹配"
# 异或操作并转换为字符
result = ''.join(chr(b1 ^ b2) for b1, b2 in zip(bytes1, bytes2))
print("XOR结果转换为字符:", result)
#输出结果:XOR结果转换为字符: flag{md5(uid+202
xor_bytes = bytes(b1 ^ b2 for b1, b2 in zip(bytes1, bytes2))
print("XOR结果的16进制表示:", ' '.join(f'{b:02X}' for b in xor_bytes))
#输出结果:XOR结果的16进制表示: 66 6C 61 67 7B 6D 64 35 28 75 69 64 2B 32 30 32
```
**获取到前16位flag为flag{md5(uid+202:**
第4步:获取剩下的3位flag
把“flag{md5(uid+202123”加上随机的3位输入到程序中,查看第17轮即i=16时fun_enc会把v11改成什么
发现v11变成了77 70 8A 5F D8 7D B0 5C 90 E6 35 8C D0 4C F9 BB,把新3位放回脚本算出答案
```python
# 输入的16进制字符串
hex_str1 = "2E 4B EE C8 E0 95 88 47 B0 72 1B 68 40 D0 0A 84 77 70 8A"
hex_str2 = "48 27 8F AF 9B F8 EC 72 98 07 72 0C 6B E2 3A B6 42 59 F7"
# 将16进制字符串转换为字节列表
bytes1 = bytes.fromhex(hex_str1.replace(" ", ""))
bytes2 = bytes.fromhex(hex_str2.replace(" ", ""))
# 确保两个字节序列长度相同
assert len(bytes1) == len(bytes2), "字节序列长度不匹配"
# 异或操作并转换为字符
result = ''.join(chr(b1 ^ b2) for b1, b2 in zip(bytes1, bytes2))
print("XOR结果转换为字符:", result)
#XOR结果转换为字符: flag{md5(uid+2025)}
```
**得出最后答案 flag{md5(uid+2025)}**
# 0x04 拿下flag
**flag的坑**
算出答案非常开心,就去算uid+2025的md5
```python
#UID2121487+2025即2123512
hashlib.md5(str(2123512).encode('utf8')).hexdigest()
'4ac37b3b46367455af865e516fdef7d0'
#得出flag{4ac37b3b46367455af865e516fdef7d0}
#结果提交错误了
```
最后琢磨了以下应该是字符串相加
```python
#UID2121487+2025即21214872025
hashlib.md5(str(21214872025).encode('utf8')).hexdigest()
'c9ca680ed9f6b0a6f0cd49cebb626bc0'
```
最终答案flag{c9ca680ed9f6b0a6f0cd49cebb626bc0}
# 0x05 推广
[**【2025春节】解题领红包之番外篇一二三WriteUp**](https://www.52pojie.cn/thread-2005805-1-1.html)
[**《安卓逆向这档事》十二、大佬帮我分析一下**](https://www.52pojie.cn/thread-1809646-1-1.html)
# 0x06 【新年祝福】致52pojie的所有逆向勇士们
转眼又到新年钟声响起时,回想这一年我们熬过的夜——从OD到x64dbg的丝滑切换,从花指令对抗到反调试过招,发际线在堆栈中稳步后移(~~bushi~~)。愿新的一年里:
> ① 功力暴涨如IDA反编译F5秒出伪代码
> ② 灵感迸发像VS装好了Resharper般丝滑
> ③ 遇坑必填,玄学bug自动退散
> ④ Hook人生如Monitor精准捕获每个高光时刻
> ⑤ 在逆向的星辰大海里,永远做那个手握IDA和WinHex的追风少年
祝各位:
**永无蓝屏,一调就过!**
**栈不溢出,堆不泄漏!**
**早日拿下内核,轻松搞定协议栈!**
(防杠声明:本祝福已通过ASLR随机化处理,祝大家逆向功力突破熵增定律)
让咱们继续用代码改变世界,用逆向探索未知!新年卷起来~(*手动狗头保命*)
❤❤最后感谢论坛大佬的无私奉献❤❤ 本帖最后由 GrumpyJellyfish 于 2025-2-16 17:08 编辑
leger1210 发表于 2025-2-14 07:03
反调试那边能再讲讲吗,v13 | v14 应该随便哪个为0都行吧
# 关于程序里面的反调试(检测到异常并不会退出,而是引导到错误的加密函数里面去):
## 0x01 分析校验是否异常的判断逻辑
``` cpp
fun_enc = (void(__fastcall *) // 函数返回情况声明
(_BYTE *, const char *, int, void *) // 函数参数需求声明
)
nullsub_9(//函数实现方式,这里这个nullsub_9就是个假函数,其直接返回给他的参数值也就是下面这一串
*(_DWORD *)(
(unsigned int)v22 | (4 * ((v14 | v13) ^ (unsigned int)sub_C173F6CC & 1 ^ (((unsigned int)ao ^ (unsigned int)a) >> 24) & 1)) //那么关键点就在这里了,单独拎出来分析
)
);
```
**根据运算优先级“按位异或^”优先级高于“按位或|”给他们分段**
```cpp
(unsigned int)v22 | //第四步:这里是通过“数组地址 ‘或’ 偏移量”达到指向数组元素的目的。这里反编译应该是有点问题,v22这个数组应该是4字节的,那么0号元素是a,1号元素应该是ao。
(4 * (
(v14 | v13) ^ //第三步:所以结果看这里。因为是异或,所以v14|v13的结果决定了这个括号的返回
(unsigned int)sub_C173F6CC & 1 ^ //第一步:这个函数地址是0xC173F6CC,所以&1结果是1
(((unsigned int)ao ^ (unsigned int)a) >> 24) & 1) //第二步:因为ao跟a声明的地址挨的很近,所以他们两个函数地址异或后的的高8位(应该一共32位,右移24位即高8位)应该都是0。0与1结果还是0,a的函数C173FCEC,ao的函数C17402F0
)
//所以当(v14|v13)=1才能走a,那么需要v14=1或v13=1
//根据上面代码
//v14 = jgbjkb(); 这个函数不知道是干啥的,他会返回1
//v13= (v12 == 0 || v11 < 3);后面这个v11应该是一共很大的数,它不会小于3的,所以只看v12
//v12 = getenv(byte_C17B5D7E); 这个byte_C17B5D7E会变成“/proc/cpuinfo”没太搞懂这个什么意思,他在我的环境里面会返回0,所以v13就是1了
```
## 0x02 下面分析下两个函数的差异
```cpp
// 这个是这个正确函数
void __fastcall a(_QWORD *a1, int a2, int a3, int a4)
{
__int64 v5;
int i;
_QWORD v9;
v5 = a1;
v9 = *a1;
v9 = v5;
if (a3)
{
for (i = 0; i != a3; ++i)
{
if ((i & 0xF) == 0)
sub_BED58((uint8_t *)v9);
*(_BYTE *)(a4 + i) = *(_BYTE *)(a2 + i) ^ *((_BYTE *)v9 + (i & 0xF));
*((_BYTE *)v9 + (i & 0xF)) = *(_BYTE *)(a2 + i) ^ *((_BYTE *)v9 + (i & 0xF));
// 正确函数这里多了一个对v9的赋值,如果v9不变那么第16次循环v9新的值前三位会有问题
}
}
}
// 这个是这个是校验没过的时候跑的错误函数
void __fastcall ao(_QWORD *a1, int a2, int a3, int a4)
{
__int64 v5;
int i;
_QWORD v9;
v5 = a1;
v9 = *a1;
v9 = v5;
if (a3)
{
for (i = 0; i != a3; ++i)
{
if ((i & 0xF) == 0)
sub_BED58((uint8_t *)v9);
*(_BYTE *)(a4 + i) = *(_BYTE *)(a2 + i) ^ *((_BYTE *)v9 + (i & 0xF));
}
}
}
```
大佬帮忙看下我这样分析对不对@正己 ,也请教下这两个问题:
1、getenv(/proc/cpuinfo)这个东西是干嘛的
2、v22这个数组这里是乘4,不应该乘8吗? 晓年过了,看到了这个红包 你这最后是不是gpt生成的,哈哈哈。 反调试那边能再讲讲吗,v13 | v14 应该随便哪个为0都行吧 Hmily 发表于 2025-2-14 06:21
你这最后是不是gpt生成的,哈哈哈。
大佬早上好呀,这么快就被发现了吗?{:1_886:}
这祝福是不是太有梗了,有没有直戳心窝子呀:lol leger1210 发表于 2025-2-14 07:03
反调试那边能再讲讲吗,v13 | v14 应该随便哪个为0都行吧
这个应该是得两个都0,晚上下班回去再仔细分析下 忘记搭了 o(╥﹏╥)o 要怎样动态调试呀 有没有啥教程可以看看 刚开始学还不知道怎么动调安卓的so文件 {:1_923:} 本帖最后由 GrumpyJellyfish 于 2025-2-14 18:47 编辑
Dahl 发表于 2025-2-14 18:44
要怎样动态调试呀 有没有啥教程可以看看 刚开始学还不知道怎么动调安卓的so文件
嘿嘿,终于有人问了,你看我推广里面的第二个,正己大佬的教程有说动态调试,如果基础不好的话建议重头开始看。加油哦 GrumpyJellyfish 发表于 2025-2-14 18:46
嘿嘿,终于有人问了,你看我推广里面的第二个,正己大佬的教程有说动态调试,如果基础不好的话建议重头开 ...
好的好的 这就去学习学习 谢谢大佬{:1_919:}