01--AFCTF--re2(SMC)

SMC(self-Modifying Code)

自修改代码,程序在执行某段代码的过程中会对程序的代码进行修改,只有在修改后的代码才是可汇编,可执行的。在程序未对该段代码进行修改之前,在静态分析状态下,均是不可读的字节码。

调试时遇到的一个问题

在使用IDA进行调试的过程中可能会遇到Create Process Fail这个错误,这是因为被调试程序的路径不是全英文的。

IDA调试常用快捷键

  • F2:在选中位置下断点
  • F4:选中某条指令,按下F4,程序会执行到该处。如果对某条指令按F4,实际上执行了下面一系列操作:在该处设置硬件断点—>启动程序使之运行到该处暂停—>删除该处的硬件断点。
  • F5:反编译成伪C代码
  • F7:单步执行,遇到函数调用跟进函数内部
  • CTRL+F7:运行至返回。继续执行当前函数,知道该函数返回或者遇到一个断点时停止。
  • F8:单步执行,遇到函数调用不跟进
  • F9:执行代码,遇到断点则停

解题过程

选择动态调试。
这里写图片描述
选择 Local Windows debugger。
01--AFCTF--re2(SMC)_第1张图片
在主函数的第一条指令下一个断点。
01--AFCTF--re2(SMC)_第2张图片
点击开始调试图标进行调试
这里写图片描述
此时程序停在主函数第一条指令上面,按F5反编译成伪C代码,如下:

int wmain()
{
  char *v0; // eax
  int v1; // ST24_4
  __int16 v2; // ST28_2
  char v4; // [esp+1Ch] [ebp-40h]
  char v5; // [esp+36h] [ebp-26h]
  int v6; // [esp+38h] [ebp-24h]
  __int16 v7; // [esp+3Ch] [ebp-20h]
  int v8; // [esp+40h] [ebp-1Ch]
  __int16 v9; // [esp+44h] [ebp-18h]
  int v10; // [esp+48h] [ebp-14h]
  char *v11; // [esp+4Ch] [ebp-10h]
  int i; // [esp+50h] [ebp-Ch]
  int v13; // [esp+54h] [ebp-8h]
  __int16 v14; // [esp+58h] [ebp-4h]

  i = 0;
  printf("Welcome to AFCTF,I hope you have a good time!\n");
  scanf("%s", &v4);
  if ( strlen(&v4) == 27 )
  {
    if ( !strncmp(&v4, "afctf{", 6u) && v5 == 125 )
    {
      v5 = 0;
      v0 = strtok(&v4, "_");
      v11 = v0;
      v0 += 6;
      v8 = *(_DWORD *)v0;
      v9 = *((_WORD *)v0 + 2);
      v13 = *(_DWORD *)(v11 + 6);
      v14 = *((_WORD *)v11 + 5);
      v11 = strtok(0, "_");
      v6 = *(_DWORD *)v11;
      v7 = *((_WORD *)v11 + 2);
      v11 = strtok(0, "_");
      v1 = *(_DWORD *)v11;
      v2 = *((_WORD *)v11 + 2);
      dword_122EF70 = (int)dword_1237F50;
      if ( ((int (__cdecl *)(int *))dword_1237F50[0])(&v8) )
      {
        v10 = SHIBYTE(v14) ^ (char)v13 ^ (char)v14 ^ SHIBYTE(v13) ^ SBYTE2(v13) ^ SBYTE1(v13);
        for ( i = 256; i < 496; ++i )
          byte_1238048[i] = v10 ^ *(_BYTE *)(i + 19103816);
        JUMPOUT(__CS__, &byte_1238048[256]);
      }
      printf("Wrong\n");
    }
    else
    {
      printf("Wrong\n");
    }
  }
  else
  {
    printf("Wrong\n");
  }
  return 0;
}

这段代码的意思是:首先判断输入的flag长度是否为27、开头6个字母是否为“afctff{”、结尾字符是否为“}”;接下来将afctf{……}中间的内容取出来,以’_’为分界符将其分成三段,每段6个字节。然后进入if判断。
在39行下一个断点,按F9执行到这里,在终端随便输入一个符合格式的字符串:afctf{123456_123456_123456},按下F7跟进验证函数。
01--AFCTF--re2(SMC)_第3张图片
接下来看到汇编代码如下:

.data:01237F50 add     esi, 43AEF761h
.data:01237F56 call    $+5
.data:01237F5B pop     esi
.data:01237F5C push    edi
.data:01237F5D xor     edi, edi
.data:01237F5F
.data:01237F5F loc_1237F5F:                            ; CODE XREF: .data:01237F75↓j
.data:01237F5F cmp     edi, 0C0h
.data:01237F65 jg      short loc_1237F7B
.data:01237F67 mov     bl, [esi+edi+25h]
.data:01237F6B xor     bl, 73h
.data:01237F6E mov     [esi+edi+25h], bl
.data:01237F72 add     edi, 1
.data:01237F75 jmp     short loc_1237F5F
.data:01237F75 ; ---------------------------------------------------------------------------
.data:01237F77 db  65h ; e
.data:01237F78 db  61h ; a
.data:01237F79 db  73h ; s
.data:01237F7A db  79h ; y
.data:01237F7B ; ---------------------------------------------------------------------------
.data:01237F7B
.data:01237F7B loc_1237F7B:                            ; CODE XREF: .data:01237F65↑j
.data:01237F7B pop     edi
.data:01237F7C nop
.data:01237F7D nop
.data:01237F7E nop
.data:01237F7F nop
.data:01237F80 db      26h
.data:01237F80 clc
.data:01237F82 lahf
.data:01237F83 lock lahf
.data:01237F85 imul    esp, ds:0F8336B5h, -4Bh
.data:01237F8C adc     byte ptr ss:[edx], 0B5h
.data:01237F90 sub     dword ptr ss:[ebx-4Bh], 0B5698036h
.data:01237F98 xchg    edi, ss:[edx]
.data:01237F9B mov     ch, 36h
.data:01237F9D xchg    cl, [edx-4Bh]
.data:01237FA0 mov     edi, ss:[edi]
.data:01237FA3 mov     ch, 36h
.data:01237FA5 mov     cl, [edi-4Bh]
.data:01237FA8 mov     ss:[ecx-4Bh], esp
.data:01237FAC mov     ss:[edi], dh
.data:01237FAF mov     ch, 36h

call $+5; pop esi;这两句指令获得pop esi;这句指令的地址并存入esi寄存器中,即01237F5B。
loc_1237F5F中代码是一个循环,这段汇编的意思是将 01237F80 地址开始,长度为0xC0的所有数据全部异或0x73,从而实现对关键代码的解密。按下01237F7F处的指令,F4执行到这里。选中从 01237F80 地址开始,长度为0xC0的代码段,按C键重新分析。ALT键+L可以进行选中。然后在01237F80 位置按P键建立函数,按下F5反汇编成伪代码:

BOOL __cdecl sub_1027F80(int a1)
{
  signed int i; // [esp+4h] [ebp-18h]
  int v3; // [esp+8h] [ebp-14h]
  char v4; // [esp+Ch] [ebp-10h]
  char v5; // [esp+Dh] [ebp-Fh]
  char v6; // [esp+Eh] [ebp-Eh]
  char v7; // [esp+Fh] [ebp-Dh]
  char v8; // [esp+10h] [ebp-Ch]
  char v9; // [esp+11h] [ebp-Bh]
  char v10; // [esp+14h] [ebp-8h]
  char v11; // [esp+15h] [ebp-7h]
  char v12; // [esp+16h] [ebp-6h]
  char v13; // [esp+17h] [ebp-5h]
  char v14; // [esp+18h] [ebp-4h]
  char v15; // [esp+19h] [ebp-3h]

  v4 = 124;
  v5 = 97;
  v6 = 24;
  v7 = 26;
  v8 = 73;
  v9 = 57;
  v10 = 76;
  v11 = 60;
  v12 = 18;
  v13 = 68;
  v14 = 92;
  v15 = 42;
  v3 = 0;
  for ( i = 0; i < 6; ++i )
  {
    *(_BYTE *)(i + a1) ^= *(&v4 + (i + 2) % 6) - 18;
    if ( *(char *)(i + a1) == *(&v10 + i) )
      ++v3;
  }
  return v3 == 6;
}

根据这段算法写出解题代码:

arr1=[124,97,24,26,73,57]
arr2=[76,60,18,68,92,42]
s1=""
for i in range(6):
    s1+=chr(arr2[i]^(arr1[(i+2)%6]-18))

print(s1)

输出第一段为:J4%c6e
继续调试,发现输出wrong,重新调试,并把第一段改成正确的:afctf{J4%c6e_123456_123456}。
此时由于第一段已经解出来了,我们可以直接找到ret然后F4、F8跳过第一段的解密代码,进入第二段。

.text:0100114F movsx   ecx, byte ptr [ebp+var_8+1]
.text:01001153 movsx   edx, byte ptr [ebp+var_8+2]
.text:01001157 xor     ecx, edx
.text:01001159 movsx   eax, byte ptr [ebp+var_8+3]
.text:0100115D xor     ecx, eax
.text:0100115F movsx   edx, byte ptr [ebp+var_4]
.text:01001163 xor     ecx, edx
.text:01001165 movsx   eax, byte ptr [ebp+var_8]
.text:01001169 xor     ecx, eax
.text:0100116B movsx   edx, byte ptr [ebp+var_4+1]
.text:0100116F xor     ecx, edx
.text:01001171 mov     [ebp+var_14], ecx
.text:01001174 mov     [ebp+var_C], 100h
.text:0100117B jmp     short loc_1001186
.text:0100117D ; ---------------------------------------------------------------------------
.text:0100117D
.text:0100117D loc_100117D:                            ; CODE XREF: _wmain+1A5j
.text:0100117D mov     eax, [ebp+var_C]
.text:01001180 add     eax, 1
.text:01001183 mov     [ebp+var_C], eax
.text:01001186
.text:01001186 loc_1001186:                            ; CODE XREF: _wmain+17Bj
.text:01001186 cmp     [ebp+var_C], 1F0h
.text:0100118D jge     short loc_10011A7
.text:0100118F mov     ecx, [ebp+var_C]
.text:01001192 movsx   edx, byte ptr [ecx+1028048h]
.text:01001199 xor     edx, [ebp+var_14]
.text:0100119C mov     eax, [ebp+var_C]
.text:0100119F mov     byte_1028048[eax], dl
.text:010011A5 jmp     short loc_100117D
.text:010011A7 ; ---------------------------------------------------------------------------
.text:010011A7
.text:010011A7 loc_10011A7:                            ; CODE XREF: _wmain+18Dj
.text:010011A7 push    eax
.text:010011A8 push    ecx
.text:010011A9 push    esi
.text:010011AA call    $+5
.text:010011AF pop     ecx
.text:010011B0 lea     eax, byte_1028048
.text:010011B6 lea     esi, [esp+68h+var_4C]
.text:010011BA add     eax, 100h
.text:010011BF jmp     eax

这段代码从地址0100114F到01001171的意思是将刚刚已解出的6个字符一起异或后放在[ebp+var_14]中,然后将在100h+1028048h=1028148h处存放的长度为1F0h-100h=F0h的代码与[ebp+var_14]中的数据进行异或还原出第二段的解密代码。然后通过 jmp eax 跳到解密代码。

.data:01028148 mov     esi, [esi]
.data:0102814A xor     edi, edi
.data:0102814C push    ecx
.data:0102814D
.data:0102814D loc_102814D:                            ; CODE XREF: .data:0102816F↓j
.data:0102814D cmp     edi, 6
.data:01028150 jge     short loc_1028173
.data:01028152 xor     ecx, ecx
.data:01028154 mov     cl, [esi+edi]
.data:01028157 add     cl, 1
.data:0102815A ror     cl, 2
.data:0102815D jmp     short loc_1028171
.data:0102815D ; ---------------------------------------------------------------------------
.data:0102815F db  12h
.data:01028160 db  34h ; 4
.data:01028161 db  56h ; V
.data:01028162 db  78h ; x
.data:01028163 db  90h
.data:01028164 db 0ABh
.data:01028165 ; ---------------------------------------------------------------------------
.data:01028165
.data:01028165 loc_1028165:                            ; CODE XREF: .data:loc_1028171↓j
.data:01028165 xor     cl, [eax+edi+17h]
.data:01028169 mov     [esi+edi], cl
.data:0102816C add     edi, 1
.data:0102816F jmp     short loc_102814D
.data:01028171 ; ---------------------------------------------------------------------------
.data:01028171
.data:01028171 loc_1028171:                            ; CODE XREF: .data:0102815D↑j
.data:01028171 jmp     short loc_1028165
.data:01028173 ; ---------------------------------------------------------------------------
.data:01028173
.data:01028173 loc_1028173:                            ; CODE XREF: .data:01028150↑j
.data:01028173 xor     edi, edi
.data:01028175
.data:01028175 loc_1028175:                            ; CODE XREF: .data:010281B9↓j
.data:01028175 cmp     edi, 6
.data:01028178 jge     short loc_10281BB
.data:0102817A mov     cl, [esi+edi]
.data:0102817D mov     ch, cl
.data:0102817F and     ch, 0F0h
.data:01028182 ror     ch, 4
.data:01028185 and     cl, 0Fh
.data:01028188 sub     eax, 100h
.data:0102818D rol     cl, 4
.data:01028190 push    ebx
.data:01028191 mov     bl, cl
.data:01028193 add     bl, ch
.data:01028195 and     ebx, 0FFh
.data:0102819B mov     bl, [eax+ebx]
.data:0102819E jmp     short loc_10281A7
.data:0102819E ; ---------------------------------------------------------------------------
.data:010281A0 db  91h
.data:010281A1 db  3Eh ; >
.data:010281A2 db  16h
.data:010281A3 db  64h ; d
.data:010281A4 db 0CDh
.data:010281A5 db  86h
.data:010281A6 db  90h
.data:010281A7 ; ---------------------------------------------------------------------------
.data:010281A7
.data:010281A7 loc_10281A7:                            ; CODE XREF: .data:0102819E↑j
.data:010281A7 cmp     bl, [eax+edi+158h]
.data:010281AE jnz     short loc_10281C6
.data:010281B0 pop     ebx
.data:010281B1 add     edi, 1
.data:010281B4 add     eax, 100h
.data:010281B9 jmp     short loc_1028175
.data:010281BB ; ---------------------------------------------------------------------------
.data:010281BB
.data:010281BB loc_10281BB:                            ; CODE XREF: .data:01028178↑j
.data:010281BB pop     ecx
.data:010281BC mov     eax, 1
.data:010281C1 lea     ecx, [ecx+12h]
.data:010281C4 jmp     ecx
.data:010281C6 ; ---------------------------------------------------------------------------
.data:010281C6
.data:010281C6 loc_10281C6:                            ; CODE XREF: .data:010281AE↑j
.data:010281C6 xor     eax, eax
.data:010281C8 pop     ecx
.data:010281C9 pop     ecx
.data:010281CA add     ecx, 12h
.data:010281CD jmp     ecx

这段代码从地址0102814D到0102816F是将输入的6个字节每个都加1且不带符号循环右移2位,然后再按顺序与[eax+edi+17h]中的数据异或。从地址01028175到是将每个字节的高四位与第四位进行位置交换,然后在地址1028048处进行查表替换,然后依次与[eax+edi+158h]中的数据进行对比,如果相等就通过第二段。
计算过程:值为:91h,3Eh,16h,64h,0CDh,86h得数据在表中的位置依次为:98h,ABh,B9h,67h,DCh,7Eh,将高四位与第四位交换后得到:89h,BAh,9Bh,76h,CDh,E7h,与[eax+edi+17h]中的数据异或后得到:0x9b,0x8e,0xcd,0x0e,0x5d,0x4c,循环左移后得到:6Eh,3Ah,37h,38h,75h,31h,减一后得到:6Dh,39h,36h,37h,74h,30h,即第二段为“m967t0”。
重新调试,正确输入前两段,进入第三段,代码如下:

.text:010011DC push    6                               ; size_t
.text:010011DE push    offset a50sa                    ; "50Sa*^"
.text:010011E3 lea     ecx, [ebp+var_48]
.text:010011E6 push    ecx                             ; char *
.text:010011E7 call    _strncmp
.text:010011EC add     esp, 0Ch
.text:010011EF test    eax, eax
.text:010011F1 jz      short loc_1001202
.text:010011F3 push    offset aWrong                   ; "Wrong\n"
.text:010011F8 call    _printf
.text:010011FD add     esp, 4
.text:01001200 jmp     short loc_1001217
.text:01001202 ; ---------------------------------------------------------------------------
.text:01001202
.text:01001202 loc_1001202:                            ; CODE XREF: _wmain+1F1↑j
.text:01001202 push    offset aCongYouWin              ; "Cong,You win!"
.text:01001207 call    _printf
.text:0100120C add     esp, 4
.text:0100120F xor     edx, edx
.text:01001211 jnz     loc_100102C
.text:01001217
.text:01001217 loc_1001217:                            ; CODE XREF: _wmain+69↑j
.text:01001217                                         ; _wmain+9B↑j ...
.text:01001217 xor     eax, eax
.text:01001219 pop     esi
.text:0100121A mov     esp, ebp
.text:0100121C pop     ebp
.text:0100121D retn
.text:0100121D _wmain endp

第三段代码很简单,就是跟字符串”50Sa*^”做匹配就好了,正确会输出”Cong,You win!”。
于是综合三段可以得到flag为:afctf{J4%c6e_m967t0_50Sa*^}

总结

代码和数据转换

  1. IDA中会反汇编程序运行处的代码,如果确定某段十六进制数据是一段指令,把光标移动到第一个字节的偏移位置(或者直接选中)按C键(code),识别为代码,按P键可以把某段代码定义为子程序,参数调用会列出。取消定义执行菜单命令Edit/Undefine或按U键,数据重新以16进制数据显示。通常会在使用CP键之后F5查看伪C代码。(经过测试,在某段16进制数据使用CP键成为子程序之后再对这段代码进行分析会出“sp-analysis failed”的错误)。
  2. 在数据行按D键,数据类型会在db、dw与dd间转换,执行菜单”Options/Setup data types”可以设置更多的数据类型。

F4的工作原理

F4的作用是跳到指定地址,但是其工作过程是先在指定地址处下硬件断点,程序运行到该断点处停止,最后再把该处的硬件断点删掉。

在本次调试过程中,因为在一个循环的第一条指令处手动下了一个断点,然后再结尾jmp指令处使用F4。这个错误的过程导致按下F4之后并没有执行jmp跳转,而是直接停在了第一条指令这里。

判断是SMC的经验

  1. 代码表面含义不明显。
  2. 程序在执行过程中会对某些无意义的数据或者指令进行修改。我们知道,在冯诺依曼机中,指令和数据都是以二进制形式存放在存储器中,CPU区分他们的依据是指令周期的不同阶段。

你可能感兴趣的:(Reverse)