吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3107|回复: 6
收起左侧

[CTF] NCTF2021 - re - 狗狗的秘密

[复制链接]
dreamingctf 发表于 2021-12-6 00:22
这个题的考点主要是两个:
1)patch 掉 SMC 和 反调试
2)算法爆破姿势

把感谢写在前面
1)感谢出题人非常耐心的解答。一开始是在OD里一直下断停不下来,修改的字节有些许问题,导致在关键处停不下来。然后又是OD停下来了之后,IDA动态停不下来,各种细节问题都是出题人帮忙解决的。实名感谢NCTF出题人。
2)感谢分享,算法爆破写得非常清楚,在此再做一些补充。其链接如下:
https://www.52pojie.cn/thread-1553124-1-1.html

1)部分分析:
1.1 如何 patch

拿到题 main 是这样的
[Asm] 纯文本查看 复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+0h] [ebp-68h]
  int v5; // [esp+0h] [ebp-68h]
  HANDLE hHandle; // [esp+8h] [ebp-60h]
  char v7[80]; // [esp+14h] [ebp-54h] BYREF

  memset(v7, 0, sizeof(v7));
  sub_401510("Input:\n", v4);
  sub_401550("%50s", (char)v7);
  v5 = &v7[strlen(v7) + 1] - &v7[1];
  if ( v5 != 42 )
  {
    sub_401510("Wrong!\n", v5);
    exit(0);
  }
  hHandle = CreateThread(0, 0, StartAddress, 0, 0, 0);
  WaitForSingleObject(hHandle, 0xFFFFFFFF);
  CloseHandle(hHandle);
  off_405014(v7);
  return 0;
}


长度为 42 个字节,然后就进入了一个 off_的流程,off_ 好像是个地址
图片.png

两个都分析一下,发现一个是403000里面是一些奇奇怪怪没有写完的代码,一个是假的flag
那我们当然要走到403000,奇奇怪怪一定是作者搞的,结合SMC,一定有解密的部分
[Asm] 纯文本查看 复制代码
void __stdcall TlsCallback_0(int a1, int a2, int a3)
{
  BOOL (__stdcall *CheckRemoteDebuggerPresent)(HANDLE, PBOOL); // [esp+4h] [ebp-20h]
  HANDLE v4; // [esp+8h] [ebp-1Ch]
  HMODULE hModule; // [esp+Ch] [ebp-18h]
  int j; // [esp+10h] [ebp-14h]
  int i; // [esp+14h] [ebp-10h]
  char *v8; // [esp+18h] [ebp-Ch]
  int v9; // [esp+1Ch] [ebp-8h] BYREF

  if ( a2 == 3 )
  {
    v9 = 0;
    hModule = GetModuleHandleA("Kernel32");
    CheckRemoteDebuggerPresent = (BOOL (__stdcall *)(HANDLE, PBOOL))GetProcAddress(
                                                                      hModule,
                                                                      "CheckRemoteDebuggerPresent");
    v4 = GetCurrentProcess();
    CheckRemoteDebuggerPresent(v4, &v9);
    if ( !v9 && !IsDebuggerPresent() )
    {
      off_405014 = sub_403000;
      v8 = (char *)sub_403000 + 256;
      for ( i = 0; i < 24; ++i )
        v8 += 8;
      for ( j = 0; j < 24; ++j )
      {
        v8 -= 8;
        sub_4011F0(v8);
      }
    }
  }
}

可以看到,观察v8,和403000有关,而且是连续的,403000的数据被计算了(一定要想清楚,我们不需要搞懂是怎么加密怎么解密的,不重要,因为我们让程序自己解密完了之后,跳到已经解密之后的403000之后继续处理即可)
可以判断:403000是关键函数 且 回调函数中的反调试需要 patch 掉
图片.png
这里选择把标红的6个字节修改成  nop,这样绕过反调试,可以继续到解密部分

这样处理还不够,因为还有这样一个函数
[Asm] 纯文本查看 复制代码
int sub_4011C0()
{
  if ( NtCurrentPeb()->BeingDebugged )
    exit(0);
  return 0;
}

这个是线程的 PEB 的被调试指针,也要 patch 掉
图片.png
这个作为对比参考。至此,patch 问题解决

1.2 如何得到正确的 403000 函数
patch成功后,可保存一份,在main起始处+403000处下断,F2运行起来即可
图片.png
但会存在这样一个问题:函数识别不全
使用 u 将对应坐标处数据转换为IDA里的未知类型数据,再按快捷键C转换为代码,IDA就可以将他转换为汇编代码
图片.png
这个时候,F5 是没有效果的,因为 403000 函数不完整,堆栈不平衡,但是可以看到正确完整的反汇编代码了。通过IDA 菜单栏上Edit - Function - Edit function 功能,将上方函数结尾修改到正确的结尾。将鼠标拖放到函数最后
[Asm] 纯文本查看 复制代码
.SMC:002C344B mov     esp, ebp
.SMC:002C344D pop     ebp
.SMC:002C344E retn
.SMC:002C344E sub_2C3000 endp
.SMC:002C344E
.SMC:002C344E ; ---------------------------------------------------------------------------
.SMC:002C344F align 200h
.SMC:002C3600 dd 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

图片.png
这时候可以使用 F5 大法了


2)算法部分
[Asm] 纯文本查看 复制代码
void __cdecl sub_2C3000(const char *flag)
{
  signed int v1; // [esp+0h] [ebp-98h]
  unsigned int v2; // [esp+10h] [ebp-88h]
  signed int v3; // [esp+1Ch] [ebp-7Ch]
  int v4; // [esp+2Ch] [ebp-6Ch]
  int v5; // [esp+2Ch] [ebp-6Ch]
  char v6; // [esp+32h] [ebp-66h]
  signed int Size; // [esp+34h] [ebp-64h]
  unsigned int v8; // [esp+38h] [ebp-60h]
  int k; // [esp+38h] [ebp-60h]
  unsigned __int8 *v10; // [esp+3Ch] [ebp-5Ch]
  int i; // [esp+40h] [ebp-58h]
  signed int j; // [esp+40h] [ebp-58h]
  signed int l; // [esp+40h] [ebp-58h]
  signed int m; // [esp+40h] [ebp-58h]
  signed int n; // [esp+40h] [ebp-58h]
  char v16[62]; // [esp+44h] [ebp-54h]
  int v17; // [esp+82h] [ebp-16h]
  int v18; // [esp+86h] [ebp-12h]
  int v19; // [esp+8Ah] [ebp-Eh]
  int v20; // [esp+8Eh] [ebp-Ah]
  __int16 v21; // [esp+92h] [ebp-6h]

  v2 = strlen(flag);
  Size = 0x92 * v2 / 0x64 + 1;
  v3 = 0;
  v10 = (unsigned __int8 *)malloc(Size);
  v16[0] = 82;
  v16[1] = -61;
  v16[2] = 26;
  v16[3] = -32;
  v16[4] = 22;
  v16[5] = 93;
  v16[6] = 94;
  v16[7] = -30;
  v16[8] = 103;
  v16[9] = 31;
  v16[10] = 31;
  v16[11] = 6;
  v16[12] = 6;
  v16[13] = 31;
  v16[14] = 23;
  v16[15] = 6;
  v16[16] = 15;
  v16[17] = -7;
  v16[18] = 6;
  v16[19] = 103;
  v16[20] = 88;
  v16[21] = -78;
  v16[22] = -30;
  v16[23] = -116;
  v16[24] = 15;
  v16[25] = 42;
  v16[26] = 6;
  v16[27] = -119;
  v16[28] = -49;
  v16[29] = 42;
  v16[30] = 6;
  v16[31] = 31;
  v16[32] = -104;
  v16[33] = 26;
  v16[34] = 62;
  v16[35] = 23;
  v16[36] = 103;
  v16[37] = 31;
  v16[38] = -9;
  v16[39] = 58;
  v16[40] = 68;
  v16[41] = -61;
  v16[42] = 22;
  v16[43] = 51;
  v16[44] = 105;
  v16[45] = 26;
  v16[46] = 117;
  v16[47] = 22;
  v16[48] = 62;
  v16[49] = 23;
  v16[50] = -43;
  v16[51] = 105;
  v16[52] = 122;
  v16[53] = 27;
  v16[54] = 68;
  v16[55] = 68;
  v16[56] = 62;
  v16[57] = 103;
  v16[58] = -9;
  v16[59] = -119;
  v16[60] = 103;
  v16[61] = -61;
  v17 = 0;
  v18 = 0;
  v19 = 0;
  v20 = 0;
  v21 = 0;
  memset(v10, 0, Size);
  v8 = 0;
  for ( i = 0; i < 256; ++i )
  {
    v6 = byte_2C5018[i];
    byte_2C5018[i] = byte_2C5018[(i + *((unsigned __int8 *)&dword_2C5168 + i % 4)) % 256];
    byte_2C5018[(i + *((unsigned __int8 *)&dword_2C5168 + i % 4)) % 256] = v6;
  }
  while ( v8 < strlen(flag) )
  {
    v4 = flag[v8];
    for ( j = 0x92 * v2 / 0x64; ; --j )
    {
      v5 = v4 + (v10[j] << 8);
      v10[j] = v5 % 47;
      v4 = v5 / 47;
      if ( j < v3 )
        v3 = j;
      if ( !v4 && j <= v3 )
        break;
    }
    ++v8;
  }
  for ( k = 0; !v10[k]; ++k )
    ;
  for ( l = 0; l < Size; ++l )
    v10[l] = byte_2C5118[v10[k++]];
  while ( l < Size )
    v10[l++] = 0;
  v1 = strlen((const char *)v10);
  for ( m = 0; m < v1; ++m )
    v10[m] ^= byte_2C5018[v10[m]];
  for ( n = 0; n < v1; ++n )
  {
    if ( v10[n] != (unsigned __int8)v16[n] )
    {
      sub_2C1510("Wrong!\n", v1);
      exit(0);
    }
  }
  sub_2C1510("Right!\n", v1);
}

一定要看清楚,我们的输入是什么时候参与运算的,在之前的数据,都可以下断,当成常数值
byte_2C5018 可以在 104 行 while 之前下断得到具体数据
dword_2C5168 和 v16 都未经过修改,都是常数数据
line 104 - line 118 是把输入从字符串转为 47 进制的过程

所以,line121 - 127 行的代码逆向起来很容易!我们正向没法算,因为数据太大。但是我们可以逐个位置爆破。
拿数据说话(具体数据要么自己逆向得,要么看感谢链接)
[Python] 纯文本查看 复制代码
for v17 in byte_v17:
    for i in range(256):
        c = byte_405018[i] ^ i
        if c == v17:
            # print(hex(i))
            if i in byte_405118:
                print(byte_405118.index(i), end=" ")
    print("")

这个代码的意思是,我们枚举一个字节的所有值(00 - FF),每个字节可以正向算出来最终的需要的 v17 的结果,那么就是 47 进制中的可以用的字符
爆破完了之后,得到这样一个表格
[Asm] 纯文本查看 复制代码
2
0
33 45
44
30
40
8
23
22 11 7
37 34
37 34
19 20 43
19 20 43
37 34
24
19 20 43
31 4
29
19 20 43
22 11 7
13
5
23
41
31 4
35
19 20 43
9
14
35
19 20 43
37 34
3
33 45
10
24
22 11 7
37 34
38
1
25
0
30
6
42
33 45
36
30
10
24
21
42
26
28
25
25
10
22 11 7
38
9
22 11 7

这个表格也在 感谢2 的链接里有

那是怎么转化为最终的 flag 的呢?有的行有多个数,有的行只有一个数,为什么呢?
答案是:继续爆破 + 合理性
因为输入需要转为 47 进制,有的是不可输入字符,有的会让结果非常非常奇怪。而且!
一个数是有高低位的,当顺着顺序爆破的时候,如果对了,那字符串的前面会是非常漂亮的输出格式,如果错了,那输出的东西会非常不舒服。拿别人的代码举个例子:
第一个多选项是 33、45
图片.png
跑出来的对比结果是这样的
[Asm] 纯文本查看 复制代码
b'NCTF{ADF0E239-D911-3781-7E40-A575A19E5835}'
b"N\rx^-\x80\xf7\xbf\xd1\xd3\xddu\x8dU\x06\xdcK\xd7\xcc\xe8\xfd\xec\x9aE\x83C\x05\xc0\x89'\xea\x05gxF\xcbW^\xd3\x14[\xf1"

这就说明,高位更重要,高位对了,就“好看”,高位错了,就“不好看”,所以就可以顺着一个个试出来。
有选项的(2个的、3个的)并不多,一会儿就整出来了。
完结撒花。

把附件上传一下:
attachment_1.exe 是 patch 后的
attachment_1.idb 是 patch 后的
attachment_1_original_oo.exe 是原题
gougou.zip (95.9 KB, 下载次数: 10)

免费评分

参与人数 4威望 +1 吾爱币 +22 热心值 +3 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
隔壁老王Orz + 1 谢谢@Thanks!
KylinYang + 1 + 1 用心讨论,共获提升!
lucid + 1 我很赞同!

查看全部评分

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

alongzhenggang 发表于 2021-12-6 23:51
我是狗子,祝你幸福(^o^)o

。。。。。。。。某大佬

lhl0235 发表于 2021-12-7 09:25
ctf101 发表于 2021-12-8 10:03
Hmily 发表于 2021-12-9 15:27
@dreamingctf 凤毛麟角是用户组别,不是账号名称,他的账号名称在左侧:zsky。
 楼主| dreamingctf 发表于 2021-12-10 19:30
Hmily 发表于 2021-12-9 15:27
@dreamingctf 凤毛麟角是用户组别,不是账号名称,他的账号名称在左侧:zsky。

知道了,感谢提醒,我找找编辑在哪,改一下
头像被屏蔽
hua111 发表于 2021-12-28 11:58
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-16 02:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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