吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1402|回复: 22
上一主题 下一主题
收起左侧

[Android CTF] N1CTF-reverse-ezapk wp

  [复制链接]
跳转到指定楼层
楼主
jbczzz 发表于 2024-11-12 02:06 回帖奖励
本帖最后由 jbczzz 于 2024-11-12 14:24 编辑

0x0 前言
前几天忙别的去了,完全忘了N1CTF,今天想着看看题学习一下,先只下了个最简单的ezapk,结果晚上网站直接关服务器,下不了其他题了。。
这个是入门题,无混淆,反调和检测,flag格式为n1ctf{xxxxxxxxxxxxxxxxxxxxx}
0x1 frIDA+jadx、ida静态分析
我这台手机ida动调不知道为啥一直用不了,所以这里只用frida。
首先安装apk,打开就是一个输入框和确认按钮,先拖进jadx里看一眼,入口就是MainActivity
[Java] 纯文本查看 复制代码
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    public native String enc(String str);
    public native String stringFromJNI();
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
        this.binding = inflate;
        setContentView(inflate.getRoot());
        this.binding.CheckButton.setOnClickListener(new View.OnClickListener() { // from class: com.n1ctf2024.ezapk.MainActivity$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                MainActivity.this.m157lambda$onCreate$0$comn1ctf2024ezapkMainActivity(view);
            }
        });
    }
    public /* synthetic */ void m157lambda$onCreate$0$comn1ctf2024ezapkMainActivity(View view) {
        String obj = this.binding.flagText.getText().toString();
        if (obj.startsWith("n1ctf{") && obj.endsWith("}")) {
            if (enc(obj.substring(6, obj.length() - 1)).equals("iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A=")) {
                Toast.makeText(this, "Congratulations!", 1).show();
                return;
            } else {
                Toast.makeText(this, "Try again.", 0).show();
                return;
            }
        }
        Toast.makeText(this, "Try again.", 0).show();
    }
    static {
        System.loadLibrary("native2");
        System.loadLibrary("native1");
    }
}

很容易得出,他是加载了libnative1.so和libnative2.so,然后这个enc应该就是加密的函数,是在加载的so里实现的,输入是把n1ctf{}去掉,取括号中间的字串,处理完返回跟"iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A= "这个字串做对比,java层就没啥好看的了,主要看看native里enc是怎么实现的。
但是在导出里面并没有看到enc这个函数,首先想到的是一般jni传输字串会用GetStringUTFChars,hook libart的这个调用,hook脚本:
[JavaScript] 纯文本查看 复制代码
 
    const lib_art = Process.findModuleByName('libart.so');
    const symbols = lib_art.enumerateSymbols();
    for (let symbol of symbols) {
        var name = symbol.name;
        if (name.indexOf("art") >= 0) {
            if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) {
                if (name.indexOf("GetStringUTFChars") >= 0) {
                    console.log('start hook', symbol.name);
                    Interceptor.attach(symbol.address, {
                        onEnter: function (arg) {
                            console.log('GetStringUTFChars called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
                        },
                        onLeave: function (retval) {
                            console.log('onLeave GetStringUTFChars:', ptr(retval).readCString())
                        }
                    })
                }
            }
        }
    }

果然给我逮到了,我的输入是n1ctf{testinput}
[Asm] 纯文本查看 复制代码
GetStringUTFChars called from:
0x796711117c libnative1.so!0x1b17c
0x796711117c libnative1.so!0x1b17c
0x7967eb9084 base.odex!0x1e084
0x7967eb9084 base.odex!0x1e084
onLeave GetStringUTFChars: testinput

于是能定位到native1的sub_1B148这个函数就是enc,然后大概看了一眼native2,发现里面都是一些加密算法,以下是函数名
[Asm] 纯文本查看 复制代码
iusp9aVAyoMI_XOR        .text        000000000000106C        000000B4        00000050                R        .        .        .        .        B        T        .
eeg0QuqIZtRO        .text        0000000000001120        000000D0        00000050                R        .        .        .        .        B        T        .
zWfl19ATrZaj        .text        00000000000011F0        000000D0        00000050                R        .        .        .        .        B        T        .
SZ3pMtlDTA7Q_RC4_0        .text        00000000000012C0        000002BC        000001A0                R        .        .        .        .        B        T        .
H4AQFGSOe2Df_RC4_1        .text        000000000000157C        00000284        00000190                R        .        .        .        .        B        T        .
MaR0Ssaa7zE9_RC4_2        .text        0000000000001800        000002B0        000001A0                R        .        .        .        .        B        T        .
UqhYy0F049n5_Base64_0        .text        0000000000001AB0        000002FC        000000A0                R        .        .        .        .        B        T        .
T6AAHJ6ZpxWI_Base64_1        .text        0000000000001DAC        000002FC        000000A0                R        .        .        .        .        B        T        .
.

对这些函数批量frida-trace hook,发现调用的是iusp9aVAyoMI->0x106c和SZ3pMtlDTA7Q->0x12c0,分别对应EOR和RC4加密
[Asm] 纯文本查看 复制代码
Started tracing 15 functions. Web UI available at http://localhost:12651/
           /* TID 0x3874 */
  1056 ms  sub_106c()
  1056 ms  Backtrace:
0x796711127c libnative1.so!0x1b27c
0x7967eb9084 base.odex!0x1e084
0x7967eb9084 base.odex!0x1e084
  1246 ms  sub_12c0()
  1246 ms  Backtrace:
0x796711136c libnative1.so!0x1b36c
0x7fece42cf80x7fece42cf8

但是其中有个地方很奇怪,他加密用的key是rand()返回的随机数
[Asm] 纯文本查看 复制代码
_BYTE *__fastcall iusp9aVAyoMI_XOR(__int64 a1, size_t a2)
{
  size_t i; // [xsp+0h] [xbp-40h]
  _BYTE *v4; // [xsp+8h] [xbp-38h]

  v4 = malloc(a2);
  __memcpy_chk(v4, a1, a2, -1LL);
  for ( i = 0LL; i < a2; ++i )
    v4[i] ^= rand();  //这里不应该是rand(),应该是个固定的数
  return v4;
}
_BYTE *__fastcall SZ3pMtlDTA7Q_RC4_0(__int64 a1, int a2)
{
...
0x133c:
for ( i = 0; i < 16; ++i )
*((_BYTE *)v20 + i) = rand(); //这里不应该是rand(),应该是个固定的数
...

}

理论上来说key不应该是随机的,于是用CE去看了看内存,发现果然rand被修改过,修改的是native2的got表里的rand_ptr,修改为了native1里的sub_1B140的地址(现在好像上传不了图片,后续再补几张CE的图),这个函数返回的是key:0xE9。他替换应该是在native1里的1B540里,是被声明为了__attribute((constructor))的,so一加载就会调用这个函数,通过maps定位到native2的起始地址并对40f70做初始化,然后通过字串找到rand的地址并进行替换。

最后附一个enc的修复吧
[Asm] 纯文本查看 复制代码
targetString = (const char *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 0x548LL))(
                                 a1,
                                 a3,
                                 0LL);          // GetStringUTFChars
  targetString_1 = targetString;
  if ( (((1LL << (0xB9F51095uLL >> unk_40FA4)) | 0x200000) & ~*(_QWORD *)(qword_40FA8 + 8LL * (0x2E7D442u % unk_40FA0))) == 0 )
  {
    v7 = *(_DWORD *)(qword_40FB0 + 4LL * (0xB9F51095 % dword_40F98));// v7 = 14
    if ( v7 )
    {
      v8 = *(_DWORD *)(qword_40FB8 + 4LL * (unsigned int)(v7 - dword_40F9C));// v7=14 40F9C=11
      if ( (v8 ^ 0xB9F51094) >= 2 )
      {
        v5 = 1LL;
        while ( (v8 & 1) == 0 )
        {
          v9 = 1 - dword_40F9C + v7;            // v9 = 4
          v5 = (unsigned int)++v7;
          v8 = *(_DWORD *)(qword_40FB8 + 4LL * v9);
          if ( (v8 ^ 0xB9F51094) < 2 )
            goto LABEL_9;
        }
      }
      else
      {
        LODWORD(v5) = *(_DWORD *)(qword_40FB0 + 4LL * (0xB9F51095 % dword_40F98));
LABEL_9:                                        // v7 = 152, v8 = 0x1
        v5 = *(_QWORD *)(qword_40F78 + 0x18LL * (unsigned int)v5 + 8);// v5 = 152 ,v5 = 0x1a9f37e8eb090108
      }
    }
  }
  libnative2_iusp9aVAyoM_XOR = (__int64 (__fastcall *)(const char *, __int64))(elfHead_40F70 + v5);
  v11 = __strlen_chk(targetString, 0xFFFFFFFF);
  v12 = (const char *)libnative2_iusp9aVAyoM_XOR(targetString_1, v11);// rand被替换成了sub_1b140 key 233
  v14 = v12;
  if ( (((1LL << (0xD0C97EE3uLL >> unk_40FA4)) | 0x800000000LL) & ~*(_QWORD *)(qword_40FA8
                                                                             + 8LL * (0x34325FBu % unk_40FA0))) == 0 )
  {
    v15 = *(_DWORD *)(qword_40FB0 + 4LL * (0xD0C97EE3 % dword_40F98));
    if ( v15 )
    {
      v16 = *(_DWORD *)(qword_40FB8 + 4LL * (unsigned int)(v15 - dword_40F9C));
      if ( (v16 ^ 0xD0C97EE2) >= 2 )
      {
        v13 = 1LL;
        while ( (v16 & 1) == 0 )
        {
          v17 = 1 - dword_40F9C + v15;
          v13 = (unsigned int)++v15;
          v16 = *(_DWORD *)(qword_40FB8 + 4LL * v17);
          if ( (v16 ^ 0xD0C97EE2) < 2 )
            goto LABEL_18;
        }
      }
      else
      {
        LODWORD(v13) = *(_DWORD *)(qword_40FB0 + 4LL * (0xD0C97EE3 % dword_40F98));
LABEL_18:
        v13 = *(_QWORD *)(qword_40F78 + 0x18LL * (unsigned int)v13 + 8);
      }
    }
  }
  libnative2_SZ3pMtlDTA7Q_RC4 = (__int64 (__fastcall *)(const char *, __int64))(elfHead_40F70 + v13);// Key 233
  v19 = __strlen_chk(v12, 0xFFFFFFFF);
  v20 = (const char *)libnative2_SZ3pMtlDTA7Q_RC4(v14, v19);
  v22 = v20;
  if ( (((1LL << (0x5BBF417BuLL >> unk_40FA4)) | 0x800000000000000LL) & ~*(_QWORD *)(qword_40FA8
                                                                                   + 8LL * (0x16EFD05u % unk_40FA0))) == 0 )
  {
    v23 = *(_DWORD *)(qword_40FB0 + 4LL * (0x5BBF417Bu % dword_40F98));
    if ( v23 )
    {
      v24 = *(_DWORD *)(qword_40FB8 + 4LL * (unsigned int)(v23 - dword_40F9C));
      if ( (v24 ^ 0x5BBF417Au) >= 2 )
      {
        v21 = 1LL;
        while ( (v24 & 1) == 0 )
        {
          v25 = 1 - dword_40F9C + v23;
          v21 = (unsigned int)++v23;
          v24 = *(_DWORD *)(qword_40FB8 + 4LL * v25);
          if ( (v24 ^ 0x5BBF417Au) < 2 )
            goto LABEL_27;
        }
      }
      else
      {
        LODWORD(v21) = *(_DWORD *)(qword_40FB0 + 4LL * (0x5BBF417Bu % dword_40F98));
LABEL_27:
        v21 = *(_QWORD *)(qword_40F78 + 24LL * (unsigned int)v21 + 8);
      }
    }
  }
  libnative2_UqhYy0F049n5_Base64 = (__int64 (__fastcall *)(const char *, __int64))(elfHead_40F70 + v21);
  v27 = __strlen_chk(v20, 0xFFFFFFFF);
  v28 = libnative2_UqhYy0F049n5_Base64(v22, v27);
  return (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 0x538LL))(a1, v28);

拿flag也很简单,用iRrL63tve+H72wjr/HHiwlVu5RZU9XDcI7A=先base64解密,得到891ACBEB7B6F7BE1FBDB08EBFC71E2C2556EE51654F570DC23B0,用rc4解密,key是0xe9,得到A4909ABDDA9BD8D99C9AB6AAD98DDAB6DBD9DBDDA7D8AABDAFC8,再用异或解密,key也是0xe9,得到MysT3r10us_C0d3_2024N1CTF!,所以最后的flag就是n1ctf{MysT3r10us_C0d3_2024N1CTF!}
0x3 小结
https://wwuz.lanzouv.com/iXmd02euer8b
这是修复过的so和apk本体,有兴趣的可以看看,总体来说还是比较简单的。不过这个rand()被修改让我想起来之前看到过的一个wg样本,有人为了过代码段的校验,也是替换tp那个tersafe got表的memcpy_ptr,通过检查src和长度是否覆盖到已修改的代码,如果修改了的话就把src替换成自己备份的原版代码的地址,这样他在调用的时候就检测不到代码被修改了,不过这种方式现在已和谐。

免费评分

参与人数 5威望 +1 吾爱币 +25 热心值 +5 收起 理由
Mickey01 + 1 + 1 我很赞同!
Tree24 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
fengbolee + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
melooon + 1 + 1 我很赞同!

查看全部评分

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

沙发
baishiting 发表于 2024-11-12 05:08
好样的,太牛了
3#
Onewings 发表于 2024-11-12 08:42
4#
dabai233 发表于 2024-11-12 09:21
5#
zhougoudaner 发表于 2024-11-12 09:28
大佬谢谢分享
6#
dapiaoliang 发表于 2024-11-12 09:33
太棒了,厉害
7#
kafeifish 发表于 2024-11-12 09:49
着实看不懂啊,能不能用ai试试呢?
8#
realrock 发表于 2024-11-12 10:02
厉害厉害
9#
shuyeone 发表于 2024-11-12 10:05
太棒了,谢谢分享
10#
yq991739 发表于 2024-11-12 10:45
好样的,太牛了~
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-24 07:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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