zoukankan      html  css  js  c++  java
  • MTCTF 2021 Inject Writeup

    美团 CTF 上一道有趣的逆向题目

    主要涉及:代码解密,远程注入,函数劫持

    分析题目

    题目给了一个 inject.exe 和 notepad2.exe

    首先在 inject.exe 中输入正确的 key1 和 key2 后,inject.exe 会使用 key1 和 key2 来解密程序中的资源文件 yyy.a,并将 yyy.a 作为动态库注入到 notepad2.exe 进程中

    接着在启动后的 notepad2.exe 中输入正确的 key3,进程中的 yyy.a 会对 key3 做校验,如果结果正确则将 flag 写入到 flag.txt

    解密动态库

    其中 key1 和 key2 的约束条件如下

    M=0x75D05803
    t=dword(key1/key2)%M
    p1=pow(t,8)%M
    p2=pow(t,2)%M
    if ((2*t-p2-42+p1)%M==1 && gcd(key1,key2)==1) goto right
    

    考虑到这个约束条件不太好解决,用 z3 也没跑出来,所以可以考虑从后面解密 yyy.a 的部分入手

    解密生成 yyy.a 的代码如下

      v9 = fopen(v8, "wb");
      v10 = FindResourceA(0i64, (LPCSTR)0x65, "code");
      v11 = SizeofResource(0i64, v10);
      v12 = LoadResource(0i64, v10);
      v13 = LockResource(v12);
      fwrite(v13, 1ui64, v11, v9);
      v14 = FindResourceA(0i64, (LPCSTR)0x66, "code");
      v15 = SizeofResource(0i64, v14);
      v16 = LoadResource(0i64, v14);
      v17 = LockResource(v16);
      v18 = malloc((unsigned int)v15);
      v19 = 0;
      if ( (_DWORD)v15 )
      {
        v20 = v18;
        v21 = v17 - v18;
        do
        {
          *v20 = v20[v21] ^ *((_BYTE *)&dword_7FF6F30F66B8 + (v19++ & 7));
          ++v20;
        }
        while ( v19 < (unsigned int)v15 );
      }
      fwrite(v18, 1ui64, v15, v9);
      v22 = FindResourceA(0i64, (LPCSTR)0x67, "code");
      v23 = SizeofResource(0i64, v22);
      v24 = LoadResource(0i64, v22);
      v25 = LockResource(v24);
      fwrite(v25, 1ui64, v23, v9);
      fclose(v9);
    

    生成的 yyy.a 内容由 Resource(101) + Decrypt(Resource(102), key1_2) + Resource(103) 三部分组成

    其中 key1_2 是根据 key1 和 key2 生成的 8 字节密钥

    Decrypt 使用 key1_2 的 8 个字节来循环异或 Resource(102) 进行解密

    下断点查看第三部分开头的内容都是 0,所以可以合理猜测解密后第二部分结尾的内容也是 0

    下断点看一下解密前第二部分结尾的几个 8 字节序列确实是相同的,证实了上边的猜测,这样我们就得到了 key1_2 的内容

    key1_2 = [0x17, 0xe3, 0xe3, 0x37, 0x17, 0xe3, 0xe3, 0x00]
    

    改 ip 跳过前边的约束检查,直接动态修改内存中的 key1_2,继续运行就可以得到解密后的 yyy.a 了

    分析动态库

    简单看一下发现 yyy.a 套了个 upx 壳,直接 upx -d 脱掉

    主函数如下,可以看出是 hook 了 notepad2.exe 的 WriteFile 函数来实现对 key3 的校验操作

      MessageBoxA(
        0i64,
        "Now input key3 in notepad like `key3:abcdef`.
    Save it.Reopen the file, you will get the flag.
    ",
        "Message",
        0);
      v1 = GetModuleHandleA("kernel32.dll");
      WriteFile = GetProcAddress(v1, "WriteFile");
      v3 = WriteFile + *(WriteFile + 2);
      qword_180004630 = *(v3 + 6);
      VirtualProtect(v3 + 6, 8ui64, 4u, &flOldProtect);
      qword_180004628 = sub_1800011A0;
      *(v3 + 6) = sub_1800011A0;
      VirtualProtect(v3 + 6, 8ui64, flOldProtect, &flOldProtect);
    

    新的函数入口在 sub_1800011A0,里面有校验 key3 与生成 flag 的代码

    校验 key3 的代码如下

    bool check()
    {
      unsigned __int64 v0; // r8
      unsigned __int64 v1; // r9
      __int64 v2; // rcx
      unsigned __int64 v3; // r11
      unsigned __int64 v4; // r10
      __int64 v5; // rcx
      __int64 v6; // rbx
    
      v0 = 1i64;
      v1 = 1i64;
      v2 = 4i64;
      v3 = input % 0x6440DB83ui64;
      do
      {
        v1 = v3 * v1 % 0x6440DB83;
        --v2;
      }
      while ( v2 );
      v4 = 1i64;
      v5 = 3i64;
      do
      {
        v4 = v3 * v4 % 0x6440DB83;
        --v5;
      }
      while ( v5 );
      v6 = 2i64;
      do
      {
        v0 = v3 * v0 % 0x6440DB83;
        --v6;
      }
      while ( v6 );
      return (2 * v0 - v4 + v1 - 32) % 0x6440DB83 == 1;
    }
    

    生成 flag 的代码如下

          sub_180001010(a2, "key3:%x", &dword_18000463C);
          *(float *)&input = (double)(int)dword_18000463C
                           * 1.818989403545856e-12
                           * 1.818989403545856e-12
                           * 0.00000002980232238769531;
          if ( check() )
          {
            v9 = (char *)malloc(0x2Bui64);
            idx = 5;
            v11 = byte_180003280;
            flag = v9;
            len = 0;
            idx_ = 5i64;
            qmemcpy(v9, "flag{", 5);
            v15 = 0x10842000i64;
            do
            {
              if ( idx_ <= 28 && _bittest64(&v15, idx_) )
              {
                v16 = '-';
              }
              else
              {
                v17 = len % 8;
                v18 = *v11;
                ++len;
                ++v11;
                v16 = v18 ^ *((_BYTE *)&input + v17);
              }
              flag[idx_] = v16;
              ++idx;
              ++idx_;
            }
            while ( len < 32 );
            v19 = 42i64;
            v20 = flag;
            *(_WORD *)&flag[idx] = '}';
          }
          else
          {
            v19 = 23i64;
            v20 = "your key isn't correct.";
          }
    

    爆破密钥

    爆破下面这个 key3 的中间结果,结果为 386499290

          *(float *)&input = (double)(int)dword_18000463C
                           * 1.818989403545856e-12
                           * 1.818989403545856e-12
                           * 0.00000002980232238769531;
    
    #include <cstdio>
    
    bool check(unsigned input)
    {
      unsigned long long v0; // r8
      unsigned long long v1; // r9
      unsigned long long v2; // rcx
      unsigned long long v3; // r11
      unsigned long long v4; // r10
      unsigned long long v5; // rcx
      unsigned long long v6; // rbx
    
      v0 = 1;
      v1 = 1;
      v2 = 4;
      v3 = input % 0x6440DB83u;
      do
      {
        v1 = v3 * v1 % 0x6440DB83;
        --v2;
      }
      while ( v2 );
      v4 = 1;
      v5 = 3;
      do
      {
        v4 = v3 * v4 % 0x6440DB83;
        --v5;
      }
      while ( v5 );
      v6 = 2;
      do
      {
        v0 = v3 * v0 % 0x6440DB83;
        --v6;
      }
      while ( v6 );
      return (2 * v0 - v4 + v1 - 32) % 0x6440DB83 == 1;
    }
    
    int main(){
        printf("%d
    ",check(386499290));
        for (unsigned int i=0;i<0xffffffff;i++){
            if (check(i)) {
                printf("%u
    ",i);
                getchar();
            }
        }
    }
    

    使用上面的中间结果爆破 key3,结果为 4505965

    #include <cstdio>
    
    unsigned int input=0;
    
    int main(){
        for (unsigned int i=0;i<0xffffffff;i++){
            *(float *)&input = (double)i
                           * 1.818989403545856e-12
                           * 1.818989403545856e-12
                           * 0.00000002980232238769531;
            if (input==386499290) printf("%u",i);
        }
    }
    

    调用动态库

    写一个程序直接从外部调用 yyy.a 的校验函数 sub_1800011A0 (偏移量为 0x11A0),传入 "key3:44c16d" (4505965)

    #include <stdio.h>
    #include <windows.h>
    #include <map>
    using namespace std;
    typedef int (*func1)(__int64,char*, __int64, __int64, __int64);
    int main() {
        HMODULE hdll = NULL;
        hdll = LoadLibrary(L"yyy_unpack.dll");
        if (hdll != NULL) {
            printf("YES
    ");
            func1 myfunc = ((func1)((PBYTE)hdll + 0x11A0));
            for (int i = 0; i < 128; i++)
            {
                printf("%02x", *((unsigned char*)myfunc + i));
            }
            myfunc(0, (char*)"key3:44c16d", 0, 0, 0);
        }
        else {
            printf("NO
    ");
        }
        FreeLibrary(hdll);
        return 0;
    }
    

    在 myfunc 这里下断点跟进去,在解密完成后的地方再下断点,就可以在内存中找到 flag

  • 相关阅读:
    Semaphore类
    我的java学习之路五:java的循环和条件语句
    我的java学习之路四:java的基础类型和变量
    第一节 线性表
    我的java学习之路三:java的类与对象
    我的Java学习之路二:Java基础语法
    算法分析一:基本定义
    我的java学习之路一:java的安装以及环境配置
    【2019 CCPC 江西省赛】Cotree 树重心
    【2018 icpc 南京站】G
  • 原文地址:https://www.cnblogs.com/algonote/p/14802566.html
Copyright © 2011-2022 走看看