吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1720|回复: 24
收起左侧

[Android CTF] 【2025春节】解题领红包之四 Android 中级题WriteUp

  [复制链接]
GrumpyJellyfish 发表于 2025-2-14 02:53
本帖最后由 GrumpyJellyfish 于 2025-2-14 03:57 编辑

【2025春节】解题领红包之四 Android 中级题WriteUp

0x00 初探虎先锋

题目题干如下:

出题老师:正己

题目简介:《黑神话·虎先锋の猴肉108种烹饪方式》通关失败后,张师傅怒摔手柄,突然瞥见室友王大爷的屏幕金光大作:"无限定身术+金刚不坏+暴击999%!"

"年轻人,听说过科学修仙吗?"王大爷反手一个Alt+F4,显示器残留着神秘代码:

风灵月影宗弟子认证:输入[宗门秘钥]可解锁修改权限

(温馨提示:秘钥格式请参考——flag{我是秘钥},"我是秘钥"的真实内容需要动态运算得出)

虎先锋.jpg

第1步:直接进入战斗!然后败了😭

第2步:根据提示点击右上角风灵月影

第3步:输入密钥查看交互信息

第4步:jadx查找字符串“密钥错误,请重试!”

虎先锋jadx密钥错误.jpg
第5步:找到实现代码如下:

    /* 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函数,直接拿下虎先锋!

拿下虎先锋.jpg
第7步:提交flag!欸不对我的flag呢?

0x01 攻克Check函数

jadx中查找Check的声明发现其只有声明,调用了SO层实现

//声明函数
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函数从他入手

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  jint v2; // r5
  int v3; // r0
  int v5; // [sp+0h] [bp-10h] 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实现经验修改部分变量类型:

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  jint v2; // r5
  JNIEnv *env; // r0
  JNIEnv *v5; // [sp+0h] [bp-10h] 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的汇编地址看到如下内容:

.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的实现位置,反编译结果如下:

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; // [sp+0h] [bp-58h]
  _BYTE v21[16]; // [sp+18h] [bp-40h] BYREF
  _QWORD v22[2]; // [sp+28h] [bp-30h] 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[0] = *(_QWORD *)&off_12FCE8;
    v22[1] = *(_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;
}

根据已知内容更改变量类型与变量名:

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; // [sp+0h] [bp-58h]
  _BYTE v21[16]; // [sp+18h] [bp-40h] BYREF
  _QWORD v22[2]; // [sp+28h] [bp-30h] 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[0] = *(_QWORD *)&off_C16B1CE8;  // 指向函数a的指针
    v22[1] = *(_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;
}

反调试分析

//根据下面这个表达式返回的值确定fun_enc指向的函数地址
(unsigned int)v22 | (4 * (
    (v14 | v13) ^ 
    (sub_CA7C36CC & 1) ^ 
    (((ao ^ a) >> 24) & 1)
))
//返回只有两种可能v22[0]或v22[1],其中v22[0] = *(_QWORD *)&off_C16B1CE8;(指向函数a的指针)v22[1] = *(_QWORD *)&off_C16B1CF0;(指向函数ao的指针)

//下面这个表达式返回的就决定了是v22指向的是[0]还是[1]
    (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

加密函数

//调用代码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[16]; // [sp+0h] [bp-30h] BYREF

  v5 = *((_QWORD *)a1 + 1);
  *(_QWORD *)v11 = *(_QWORD *)a1;        //把a1赋值给v11其实就是上面父函数的v21,输入的时候v21是全0的
  *(_QWORD *)&v11[8] = 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[i] ^ v11[v9];        //v10的值为a2[i]异或v11[v9],其中a2就是flag,v9是0到15,v11是一个16字节的数组,会在v9=0时发生变化。
      *((_BYTE *)a4 + i) = v10;        //赋值a4,就是父函数的v16
      v11[v9] = v10;        //改变v11的第v9位
    }
  }
}

&unk_CA73F0FC的值如下:

.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                 DCB  0xC
.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动态调试

虎先锋v11.jpg
第3步:先异或获取前16位flag

#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第二轮.jpg
发现v11变成了77 70 8A 5F D8 7D B0 5C 90 E6 35 8C D0 4C F9 BB,把新3位放回脚本算出答案

# 输入的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

#UID2121487+2025即2123512
hashlib.md5(str(2123512).encode('utf8')).hexdigest()
'4ac37b3b46367455af865e516fdef7d0'
#得出flag{4ac37b3b46367455af865e516fdef7d0}
#结果提交错误了

最后琢磨了以下应该是字符串相加

#UID2121487+2025即21214872025
hashlib.md5(str(21214872025).encode('utf8')).hexdigest()
'c9ca680ed9f6b0a6f0cd49cebb626bc0'

最终答案flag{c9ca680ed9f6b0a6f0cd49cebb626bc0}

0x05 推广

【2025春节】解题领红包之番外篇一二三WriteUp

《安卓逆向这档事》十二、大佬帮我分析一下

0x06 【新年祝福】致52pojie的所有逆向勇士们

转眼又到新年钟声响起时,回想这一年我们熬过的夜——从OD到x64dbg的丝滑切换,从花指令对抗到反调试过招,发际线在堆栈中稳步后移(bushi)。愿新的一年里:

① 功力暴涨如IDA反编译F5秒出伪代码
② 灵感迸发像VS装好了Resharper般丝滑
③ 遇坑必填,玄学bug自动退散
④ Hook人生如Monitor精准捕获每个高光时刻
⑤ 在逆向的星辰大海里,永远做那个手握IDA和WinHex的追风少年

祝各位:
永无蓝屏,一调就过!
栈不溢出,堆不泄漏!
早日拿下内核,轻松搞定协议栈!
(防杠声明:本祝福已通过ASLR随机化处理,祝大家逆向功力突破熵增定律)

让咱们继续用代码改变世界,用逆向探索未知!新年卷起来~(手动狗头保命

❤❤最后感谢论坛大佬的无私奉献❤❤

免费评分

参与人数 4威望 +2 吾爱币 +102 热心值 +4 收起 理由
正己 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
jackyyue_cn + 1 用心讨论,共获提升!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
leger1210 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| GrumpyJellyfish 发表于 2025-2-16 00:05
本帖最后由 GrumpyJellyfish 于 2025-2-16 17:08 编辑
leger1210 发表于 2025-2-14 07:03
反调试那边能再讲讲吗,v13 | v14 应该随便哪个为0都行吧

关于程序里面的反调试(检测到异常并不会退出,而是引导到错误的加密函数里面去):

0x01 分析校验是否异常的判断逻辑

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)) //那么关键点就在这里了,单独拎出来分析
        )
    );

根据运算优先级“按位异或^”优先级高于“按位或|”给他们分段

(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 下面分析下两个函数的差异

// 这个是这个正确函数
void __fastcall a(_QWORD *a1, int a2, int a3, int a4)
{
    __int64 v5;
    int i;
    _QWORD v9[2];
    v5 = a1[1];
    v9[0] = *a1;
    v9[1] = 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[2];
    v5 = a1[1];
    v9[0] = *a1;
    v9[1] = 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吗?

点评

getenv以及/proc/cpuinfo是针对unidbg的检测 v22那里就是*4,反编译无误  详情 回复 发表于 2025-2-16 20:11
抱薪风雪雾 发表于 2025-2-14 09:28
Hmily 发表于 2025-2-14 06:21
leger1210 发表于 2025-2-14 07:03
反调试那边能再讲讲吗,v13 | v14 应该随便哪个为0都行吧
 楼主| GrumpyJellyfish 发表于 2025-2-14 07:55
Hmily 发表于 2025-2-14 06:21
你这最后是不是gpt生成的,哈哈哈。

大佬早上好呀,这么快就被发现了吗?
这祝福是不是太有梗了,有没有直戳心窝子呀

点评

主要那天我用deepseek生成了一个论坛的祝福有点类似,但更接地气,所以就没觉得太强哈,但最后也没用。。。  详情 回复 发表于 2025-2-15 16:16
 楼主| GrumpyJellyfish 发表于 2025-2-14 08:03
leger1210 发表于 2025-2-14 07:03
反调试那边能再讲讲吗,v13 | v14 应该随便哪个为0都行吧

这个应该是得两个都0,晚上下班回去再仔细分析下
heptamemory 发表于 2025-2-14 09:45
忘记搭了 o(╥﹏╥)o
Dahl 发表于 2025-2-14 18:44
要怎样动态调试呀 有没有啥教程可以看看 刚开始学还不知道怎么动调安卓的so文件
 楼主| GrumpyJellyfish 发表于 2025-2-14 18:46
本帖最后由 GrumpyJellyfish 于 2025-2-14 18:47 编辑
Dahl 发表于 2025-2-14 18:44
要怎样动态调试呀 有没有啥教程可以看看 刚开始学还不知道怎么动调安卓的so文件

嘿嘿,终于有人问了,你看我推广里面的第二个,正己大佬的教程有说动态调试,如果基础不好的话建议重头开始看。加油哦
Dahl 发表于 2025-2-14 19:14
GrumpyJellyfish 发表于 2025-2-14 18:46
嘿嘿,终于有人问了,你看我推广里面的第二个,正己大佬的教程有说动态调试,如果基础不好的话建议重头开 ...

好的好的 这就去学习学习 谢谢大佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-15 17:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表