吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3536|回复: 5
收起左侧

[Android CTF] 《攻防世界》MOBILE--黑客精神

[复制链接]
HNHuangJingYU 发表于 2021-9-29 21:55
本帖最后由 HNHuangJingYU 于 2021-9-30 18:24 编辑

因为刚接触ctf的题很多题目也是比较新颖的这里记录下我的解题思路,个人理解,大神理解下>.<说下自己这题的解题思路吧,这题倒是学到了不少东西。(C++基础重要啊!!)嗯,把 黑客精神.apk 拉进JEB,我们先按照App执行代码流程分析思路:

1. 这里给App类下的的m赋值为0,并加载了so,然后就是调用了so层initSN()函数再打印了m的值

1

1

2. 来到MainActivity进来就是对App类的m进行了判断(划重点),最后点击按钮监听,因为刚才初始化的时候m为0所以果断进入doRegister()接着就来到了RegActivity,这里就干了一件事把输入的字符串传入so层saveSN(arg)3. 打开IDA分析so,进入JNI_OnLoad,发现什么也没有

2

2

4. 就是打印了一些logcat,然后回到IDA VIEW-A试图看看.data(ctrl+s)里面果然发现了函数,这里我重命名了n1就是initSN,n2就是saveSN,n3就是work

3

3

5. 按照我刚才整理的执行流程,先进入initSN函数(个人喜欢在代码旁边注释思路),总结出来的思路就是这个函数判断是否有/sdcard/reg.dat这个文件有的话setValue(env,文件流指针),给App类的m字段设置为1,如果找不到文件的话那么fopen函数返回值就为0,这里就对应了刚才JEB分析对App.m字段的判断
[C++] 纯文本查看 复制代码
void __fastcall initSN(_JNIEnv *env)
{
  _JNIEnv *env_1; // r6
  FILE *stream; // r0
  FILE *stream_1; // r4
  _JNIEnv *env_2; // r0
  int stream_2; // r1
  int stream_len; // r7
  void *v7; // r5
  _JNIEnv *env_3; // r0
  int v9; // r1

  env_1 = env;
  stream = fopen("/sdcard/reg.dat", "r+");      // 打开一个用于更新的文件,可读取也可写入。该文件必须存在。返回file指针,失败返回NULL
  stream_1 = stream;
  if ( !stream )                                // 操作失败,将m设值为0
  {
    env_2 = env_1;
    stream_2 = stream_1;
LABEL_5:
    setValue(env_2, stream_2);                  // 此时file为0
    return;
  }
  fseek(stream, 0, 2);                          // 以SEEK_END(2)为基准,偏移0(指针偏移量)个字节的位置 
  stream_len = ftell(stream_1);                 // 得到file指针的字节长度
  v7 = malloc(stream_len + 1);                  // 开辟内存空间返回指向头的指针
  if ( !v7 )                                    // 操作失败,将m设值为0
  {
    fclose(stream_1);
    env_2 = env_1;
    stream_2 = 0;
    goto LABEL_5;
  }
  fseek(stream_1, 0, 0);
  fread(v7, stream_len, 1u, stream_1);          // 读取打开的文件。
  *(v7 + stream_len) = 0;
  if ( !strcmp(v7, "EoPAoY62@ElRD") )
  {
    env_3 = env_1;
    v9 = 1;                                     // 字符匹配成功 v9 = 1
  }
  else
  {
    env_3 = env_1;
    v9 = 0;                                     // 失败 v9 = 0
  }
  setValue(env_3, v9);
  j_fclose(stream_1);
}
SetValue()函数
[C++] 纯文本查看 复制代码
void __fastcall setValue(_JNIEnv *env, int file)
{
  int file_1; // r7
  _JNIEnv *env_1; // r4
  jclass myApp_class; // r0
  void *myApp_class_1; // r5
  struct _jfieldID *myApp_class$m; // r0

  file_1 = file;
  env_1 = env;
  myApp_class = env->functions->FindClass(&env->functions, "com/gdufs/xman/MyApp");
  myApp_class_1 = myApp_class;
  myApp_class$m = env_1->functions->GetStaticFieldID(&env_1->functions, myApp_class, "m", "I");// 返回类的动态域的域ID。由其名字(m)和签名(I)指定。
  env_1->functions->SetStaticIntField(&env_1->functions, myApp_class_1, myApp_class$m, file_1);// 此函数设置对象的静态字段的值。
}
6. 再来到saveSN函数,就是这里我被我手机坑了,因为我没有给App存储的权限导致我每次注册完后文件一直没有写进去,然后这个函数就一直走不下去,我还一直以为自己的思路错了(认为注册成功才会有reg.dat文件>.<),大家注意一下把
[C++] 纯文本查看 复制代码
int __fastcall saveSN(int a1, int a2, int a3)
{
  _DWORD *v3; // r6
  int inputStr; // r9
  FILE *stream; // r7
  int *v7; // r4
  const char *v8; // r3
  int v9; // r0
  int v10; // r1
  _WORD *v11; // r5
  _DWORD *v12; // r0
  int v13; // r4
  int v14; // r3
  signed int v15; // r6
  const char *string; // r9
  const char *v17; // r5
  signed int string_lenght; // r10
  char v19; // r2
  char v20; // r3
  int v21; // [sp+0h] [bp-38h]
  int v22; // [sp+14h] [bp-24h]
  char v23; // [sp+18h] [bp-20h]

  v3 = a1;
  inputStr = a3;
  stream = fopen("/sdcard/reg.dat", "w+");      // 创建一个用于读写的空文件。返回一个 FILE 指针
  if ( stream )
  {
    v7 = &v21;
    v8 = "W3_arE_whO_we_ARE";
    do
    {
      v9 = *v8;                                 // v9  = W
      v8 += 8;                                  // v8 = hO_we_ARE
      v10 = *(v8 - 1);                          // v10 = w
      *v7 = v9;                                 // v7[0] = W
      v7[1] = v10;
      v11 = v7 + 2;
      v7 += 2;
    }
    while ( v8 != "E" );                        
    v12 = v3;
    v13 = 2016;
    *v11 = *v8;
    v14 = *v3;
    v15 = 0;
    string = (*(v14 + 676))(v12, inputStr, 0);      //这里就是我输入的字符串
    v17 = string;
    string_lenght = strlen(string);
    while ( v15 < string_lenght )       //循环我输入字符串的长度次数
    {
      if ( v15 % 3 == 1 )               //当索引位置为1、4、7、10的时候v19为w
      {
        v13 = (v13 + 5) % 16;
        v19 = *(&v23 + v13 - 23);
      }
      else if ( v15 % 3 == 2 )           //当索引位置为2、5、8、11的时候v19为_
      {
        v13 = (v13 + 7) % 15;
        v19 = *(&v23 + v13 - 22);
      }
      else                              //当索引位置为其他那么就是3、6、9、12的时候v19为a(这里是我配合IDA动态得出来的结果)
      {
        v13 = (v13 + 3) % 13;
        v19 = *(&v23 + v13 - 21);
      }
      v20 = *v17;           //取v17也就是我们输入的字符串的每一次字符(下面v17会自增)  
      ++v15;
      *(++v17 - 1) = v20 ^ v19;           //因为这里是异或所以我们输入的长度和加密的长度是一样的
    }
    fputs(string, stream);                      // 把加密后字符串写入到指定的流 stream 中
  }
  else if ( v22 == _stack_chk_guard )
  {
    return j___android_log_print(3, "com.gdufs.xman", &dword_2DCA);
  }
  return j_fclose(stream);
}
7. 嗯,经过这个函数后不管验证对否程序都退出,再次打开程序的时候就又进入了initSN函数,如果给了App权限的话那么你应该可以在你的文件目录下找到/sdcard/reg.dat这个文件那么就进入这个流程了,如图: 这里很简单就是对文件里面的字符串进行比较是否为EoPAoY62@ElRD如果是则v9=1,之后调用setValue函数后那么App类下的m字段的值就为1,就会显示已经注册。

4

4

8.正向流程分析完了,那么逆向就比较好完成了,因为我们注册码比较的字符串EoPAoY62@ElRD加密后长度是不变的,所以按照上面总结的索引处输入的字符一个个异或w_aw_aw_aw_aw就对了脚本如图:得到flag =201608Am!2333

5

5

9.输入正确的201608Am!2333注册码后,再次打开App弹出提示要采用xman{}格式,然后在这里调用了so层的work()进入发现没有干什么,就是给我们赋值了弹tost的那句提示格式,最后提交flag “xman{201608Am!2333}”

免费评分

参与人数 3威望 +1 吾爱币 +21 热心值 +3 收起 理由
lingyun011 + 1 我很赞同!
qtfreet00 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Shuaiju696 + 1 + 1 用心讨论,共获提升!

查看全部评分

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

怜渠客 发表于 2021-9-30 08:01
感谢分享,共同学习
dreamfrog 发表于 2021-9-30 17:10
专业敬业!感谢分享了思路和解决方案,太赞了
ale0320 发表于 2021-11-18 14:15
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-4-29 12:28

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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