首页
社区
课程
招聘
[原创]看雪.Wifi万能钥匙 CTF 2017 第11题Writeup
2017-6-26 23:15 4206

[原创]看雪.Wifi万能钥匙 CTF 2017 第11题Writeup

2017-6-26 23:15
4206


应某童鞋的要求,写个新手能跟着照做的题解,我想就拿这个题试试看吧。毕竟我也常有这样的苦恼,大神们觉得很普通的操作过程,一句话说完了,我百思不得其道,有点同病相怜吧。

   初探

直接ida载入,发现入口被改,此时入口是直接调用有代码修改功能的函数sub_44701F。此函数先读取程序文件.text区段开始的内容,再进行异或及算术运算,把运算结果复制到当前运行程序的相应地址空间。然后还进行了部分api的加载,最后跳转到像是VC程序OEP特征的入口。部分代码如下:

  memcpy(result, (char *)lpBaseAddress + 0x1000, 0x45000u);
  v1 = 0;
  v2 = Dst;  while ( v1 < 0x11400 )
  {
    *v2 ^= v1 + 0x19930721;
    *v2 += v1;
    *v2 -= 0x13579246;
    ++v2;
    ++v1;
  }
  v3 = (char *)Dst;
  result = (void *)sub_447000(Dst);             // 验证pe标识
  if ( result
    && (ntHeader = (int)&v3[*((_DWORD *)v3      // ntheader
                            + 0xF)],
        v24 = *(_WORD *)(ntHeader + 6),         // section number
        v23 = *(_WORD *)(ntHeader + 0x14),      // SizeOfOptionalHeader
        ImageBase = *(char **)(ntHeader + 0x34),// ImageBase
        Entry = &ImageBase[*(_DWORD *)(ntHeader + 0x28)],// base+AddressOfEntryPoint
        SizeOfImage = *(_DWORD *)(ntHeader + 0x50),// SizeOfImage
        Size = *(_DWORD *)(ntHeader + 0x54),    // SizeOfHeaders
        Buf1 = &ImageBase[*(_DWORD *)(ntHeader + 0x80)],// IMAGE_DATA_DIRECTORY Import
        (result = (void *)VirtualProtect(ImageBase, 0x45000u, 0x40u, &flOldProtect)) != 0) )
  {    memcpy(ImageBase, Dst, Size);
    s_hdr = (IMAGE_SECTION_HEADER *)(v23 + ntHeader + 0x18);// Section header
    v18 = s_hdr;
    v6 = 0;    while ( v6 < v24 )
    {
      v7 = v6;      memcpy(&ImageBase[s_hdr->VirtualAddress], (char *)Dst + s_hdr->PointerToRawData, s_hdr->SizeOfRawData);
      v6 = v7 + 1;
      ++s_hdr;
    }
    v8 = Buf1;    while ( memcmp(v8, &Buf2, 0x14u) )
    {      if ( *(_DWORD *)&ImageBase[*((_DWORD *)v8 + 4)] )
      {
        v9 = LoadLibraryA(&ImageBase[*((_DWORD *)v8 + 3)]);        if ( v9 )
        {
          hModule = v9;
          v10 = *(_DWORD *)v8;          if ( !*(_DWORD *)v8 )
            v10 = *((_DWORD *)v8 + 4);
          v11 = &ImageBase[v10];
          v12 = &ImageBase[*((_DWORD *)v8 + 4)];          while ( *(_DWORD *)v11 )
          {            if ( *(_DWORD *)v11 & 0x80000000 )
              v13 = (const CHAR *)(*(_DWORD *)v11 & 0xFFFF);            else
              v13 = &ImageBase[*(_DWORD *)v11 + 2];
            *(_DWORD *)v12 = GetProcAddress(hModule, v13);
            v11 += 4;
            v12 += 4;
          }
          v8 = (char *)v8 + 0x14;
        }
      }      else
      {
        v8 = (char *)v8 + 20;
      }
    }    if ( Dst )
    {      VirtualFree(Dst, 0, 0x8000u);
      lpBaseAddress = 0;
    }    if ( lpBaseAddress )
    {      UnmapViewOfFile(lpBaseAddress);
      lpBaseAddress = 0;
    }    if ( hFileMappingObject )
    {      CloseHandle(hFileMappingObject);
      hFileMappingObject = 0;
    }    if ( hFile != (HANDLE)-1 )
    {      CloseHandle(hFile);
      hFile = (HANDLE)-1;
    }
    result = (void *)((int (__thiscall *)(int))Entry)(savedregs);//跳转到入口
  }

看到这,我就想如果我直接把计算后的结果dump出来,是不是就可以了。至于加载的api什么的看看情况再说吧。

我就是这么做的,dump出来,程序直接能运行,图标也似乎正常了。没看出哪有区别。但是为了保险起见,静态都是用的这个dump出来的看的,动态都是用的原程序,那也只是多了个加断点的过程,其它并不影响。在最后我特意验证了下,程序功能似乎更改了。

   正确流程寻找

OD载入程序,一路粗跟,最后来到401690函数,检查两个全局变量是否为0,第一个变量439B50为0就直接返回了;第二个变量439B51不为零,后面似乎还有几个函数调用,但是没有启动窗体。

00401690  /$  55            push ebp
00401691  |.  8BEC          mov ebp,esp
00401693  |.  83EC 70       sub esp,0x70
00401696  |.  803D 509B4300>cmp byte ptr ds:[0x439B50],0x0
0040169D  |.  0F84 88000000 je 11-ToBeB.0040172B
004016A3  |.  E8 08FFFFFF   call 11-ToBeB.004015B0
004016A8  |.  803D 519B4300>cmp byte ptr ds:[0x439B51],0x0
004016AF  |.  74 1C         je short 11-ToBeB.004016CD
004016B1  |.  6A 00         push 0x0                                               ; /lParam = NULL
004016B3  |.  68 40174000   push 11-ToBeB.00401740                                 ; |DlgProc = 11-ToBeB.00401740
004016B8  |.  6A 00         push 0x0                                               ; |hOwner = NULL
004016BA  |.  6A 67         push 0x67                                              ; |pTemplate = 0x67
004016BC  |.  FF75 08       push [arg.1]                                           ; |hInst = NULL
004016BF  |.  FF15 94A14200 call dword ptr ds:[0x42A194]                           ; \DialogBoxParamW
004016C5  |.  33C0          xor eax,eax
004016C7  |.  8BE5          mov esp,ebp
004016C9  |.  5D            pop ebp                                                ;  0019FEC4
004016CA  |.  C2 1000       retn 0x10
004016CD  |>  8D4D 90       lea ecx,[local.28]
004016D0  |.  E8 0BF10000   call 11-ToBeB.004107E0
004016D5  |.  E8 C6FEFFFF   call 11-ToBeB.004015A0
004016DA  |.  8D4D EC       lea ecx,[local.5]
004016DD  |.  8B40 30       mov eax,dword ptr ds:[eax+0x30]
004016E0  |.  50            push eax
004016E1  |.  E8 AAEB0000   call 11-ToBeB.00410290
004016E6  |.  8D4D 90       lea ecx,[local.28]
004016E9  |.  E8 C2FE0000   call 11-ToBeB.004115B0
004016EE  |.  8D45 EC       lea eax,[local.5]
004016F1  |.  50            push eax
004016F2  |.  8D4D 94       lea ecx,[local.27]
004016F5  |.  E8 66180000   call 11-ToBeB.00402F60
004016FA  |.  8D4D EC       lea ecx,[local.5]
004016FD  |.  E8 2EEC0000   call 11-ToBeB.00410330
00401702  |.  FF35 A0404300 push dword ptr ds:[0x4340A0]                           ;  11-ToBeB.00401C30
00401708  |.  8D4D 90       lea ecx,[local.28]
0040170B  |.  E8 30FF0000   call 11-ToBeB.00411640                                 ;  !!!!!
00401710  |.  6A 20         push 0x20
00401712  |.  8D4D 90       lea ecx,[local.28]
00401715  |.  E8 26F40000   call 11-ToBeB.00410B40
0040171A  |.  50            push eax
0040171B  |.  8D4D 90       lea ecx,[local.28]
0040171E  |.  E8 EDFC0000   call 11-ToBeB.00411410                                 ;  新线程
00401723  |.  8D4D 90       lea ecx,[local.28]
00401726  |.  E8 F5F10000   call 11-ToBeB.00410920
0040172B  |>  33C0          xor eax,eax
0040172D  |.  8BE5          mov esp,ebp
0040172F  |.  5D            pop ebp                                                ;  0019FEC4
00401730  \.  C2 1000       retn 0x10

继续走,窗体创建,设etDlgItemTextW api断点(至于GetDlgItemTextW肯定不是,看看就知道),输入确定。来到401740,还是看伪代码吧。方便看些:

if ( a2 == 272 )
    return 1;
  if ( a2 != 273 )
    return 0;
  if ( (_WORD)a3 != 1000 )
  {
    if ( (_WORD)a3 == 2 )
      EndDialog(hDlg, 2);
    return 0;
  }
  String = 0;
  memset(&v7, 0, 0x1FCu);
  GetDlgItemTextW(hDlg, 1001, &String, 510);
  v4 = sub_401A40(&String, 0x451E8B10u / dword_437A08);
  *(_DWORD *)Caption = 0x793A63D0;
  if ( v4 )
  {
    memset(&v9, 0, 0x1FAu);
    *(_DWORD *)Text = 0x518C6CE8;
    *(_DWORD *)&Text[2] = 0x529F6210;
    memset(&v11, 0, 0x1F6u);
    MessageBoxW(hDlg, Text, Caption, 0);
    EndDialog(hDlg, 2);
    result = 0;
  }
  else
  {
    memset(&v9, 0, 0x1FAu);
    *(_DWORD *)Text = 0x518C6CE8;
    *(_DWORD *)&Text[2] = 0x8D255931;
    memset(&v11, 0, 0x1F6u);
    MessageBoxW(hDlg, Text, Caption, 0);
    result = 0;
  }
  return result;

很明白,取输入,然后进401A40进行计算,这个计算函数比较简单,主要是算术及异或计算,最后与一定值比较。我不知道别人有没有算,反正我是没有计算的,这个简单得太假,而且当时我猜测可能算不出什么来。在研究这个计算代码的时候,发现401A40下面有函数有明显的字符范围的检查(这个就是常看汇编的好处啊,光盯伪代码肯定发现不了)。这就更肯定了,此处算法为假算法。

再联想到窗体如果不创建,那后面调用的那些函数是干什么的呢,窗体创建了,那串函数就没用了。

先看看控制窗体启动的flag byte_439B51是在哪设置的。只有一个写的地方40145D,在401240函数中。设置过程大概是:先获取本进程快照,然后获取快照中的进程句柄,然后判断此进程的父进程是不是explorer.execmd.exe,如果是并且检查一个堆数据成功则flag为0(检查堆数据这个我一直不理解),否则为1。第一个flag的值也是在此设置的,似乎不影响程序的正常运行,就没有关注。再继续溯源,回到40100041B35A41B28A4181DF,最后来到的函数4181DF是VC入口的___tmainCRTStartup

上面的发现可以推断出两点:一,这应该可以用作反调试,二,此程序有两个进程。

直接运行了下程序,确实有两个进程。我尝试对两个进程进行了附加,一个有窗体,一个没有窗体。没有窗体的可以进行附加,入口为程序壳的入口地址(实际过程中此处还没有发现dump出来的程序不一样,为了保险用的原始程序,包括后面的一些都是用的原始程序);而有窗体的程序不能附加。不能附加说明什么问题:进程已经处于调试状态,调试者当然是父进程。

我又用OD改名和不改名分别跟了下这个过程,因为检查堆数据的问题,flag一直为1。

到这里再思考下,现在不管flag什么条件下为0,有一点可以肯定,正确流程应该是flag先为0,开启另一个进程启动窗体,非窗体进程就是窗体进程的父进程,父进程对子进程进行调试,修改流程。但是由于子进程不能附加,所以是看不了代码的。下一步要做的就是:掌握父进程对子进程的修改或想办法搞到子进程的代码。

考虑到跟踪父进程太累,先使用dump的办法。用petools对刚运行的程序进行dump,没进行任何修复改动,直接进ida,发现本来401740中是这样的

 String = 0;
 memset(&v7, 0, 0x1FCu);
 GetDlgItemTextW(hDlg, 1001, &String, 510);
 v4 = sub_401A40(&String, 0x451E8B10u / dword_437A08);

后来变成了这样:

  sub_418360(&v8, 0, 508);
  v745FB940(a2, 1001, &v7, 510, a1, *(_DWORD *)&v7, v9, v10);
  v5 = sub_401A40(&v7, 0x451E8B10 / 0u);

dword_437A08已经为0。 还是要看父进程,除0异常应该被调试者接收吧。

还有一个办法,如果父进程对子进程进行了更改,然后并未还原呢?抱着试试看的想法,我随便输入并确定,提示错误,然后再dump。果然不一样了。这里提醒一下,第二次dump出来的文件的PE头的dosheader已经被修改了,需要还原下。下面对比下原始与dump出来的变化情况。

为了方便看,我直接上汇编了,开始是这样的:

.text:004017B2                 mov     eax, 451E8B10h 
.text:004017B7                 mov     ecx, dword_437A08
.text:004017BD                 div     ecx
.text:004017BF                 mov     [ebp+hDlg], eax
.text:004017C2                 push    [ebp+hDlg]
.text:004017C5                 lea     eax, [ebp+String]
.text:004017CB                 push    eax
.text:004017CC                 call    sub_401A40


.text:00401A40                 push    ebp
.text:00401A41                 mov     ebp, esp
.text:00401A43                 sub     esp, 0Ch
.text:00401A46                 push    ebx
.text:00401A47                 push    esi
.text:00401A48                 push    edi
.text:00401A49                 mov     edi, [ebp+arg_0]
.text:00401A4C                 xor     edx, edx
.text:00401A4E                 xor     esi, esi
.text:00401A50                 mov     [ebp+var_8], edx
.text:00401A53                 mov     [ebp+var_4], esi
.text:00401A56                 xor     ecx, ecx
.text:00401A58                 lea     ebx, [edi+2]
.text:00401A5B                 jmp     short loc_401A60

第一次dump后是这样的:

.text:004017B2                 mov     eax, 451E8B10h
.text:004017B7                 mov     ecx, ds:dword_437A08
.text:004017BD                 div     ecx
.text:004017BF                 mov     [ebp+arg_0], eax
.text:004017C2                 push    [ebp+arg_0]
.text:004017C5                 lea     eax, [ebp+var_600]
.text:004017CB                 push    eax
.text:004017CC                 call    sub_401A40



.text:00401A40                 push    ebp
.text:00401A41                 mov     ebp, esp
.text:00401A43                 sub     esp, 0Ch
.text:00401A46                 push    ebx
.text:00401A47                 push    esi
.text:00401A48                 push    edi
.text:00401A49                 mov     edi, [ebp+arg_0]
.text:00401A4C                 xor     edx, edx
.text:00401A4E                 xor     esi, esi
.text:00401A50                 mov     [ebp+var_8], edx
.text:00401A53                 mov     [ebp+var_4], esi
.text:00401A56                 xor     ecx, ecx
.text:00401A58                 lea     ebx, [edi+2]
.text:00401A5B                 jmp     short loc_401A60

唯一的区别就是dword_437A08变成了0。第二次dump是这样的:

.text:004017B2                 mov     eax, 451E8B10h
.text:004017B7                 mov     ecx, ds:dword_437A08
.text:004017BD                 nop
.text:004017BE                 nop
.text:004017BF                 mov     [ebp+arg_0], eax
.text:004017C2                 push    [ebp+arg_0]
.text:004017C5                 lea     eax, [ebp+var_600]
.text:004017CB                 push    eax
.text:004017CC                 call    sub_401A40


.text:00401A40                 push    ebp
.text:00401A41                 mov     ebp, esp
.text:00401A43                 jmp     loc_402303


.text:00402300                 push    ebp
.text:00402301                 mov     ebp, esp
.text:00402303
.text:00402303 loc_402303:                             ; CODE XREF: sub_401A40+3j
.text:00402303                 sub     esp, 424h
.text:00402309                 mov     ecx, ds:dword_43AAFC
.text:0040230F                 mov     [ebp+var_80], 0
.text:00402316                 mov     [ebp+var_34], 0C9DDE6h
.text:0040231D                 mov     [ebp+var_3C], 19170839h
.text:00402324                 mov     [ebp+var_38], 0E1C01506h
.text:0040232B                 mov     eax, [ecx+3Ch]
.text:0040232E                 add     eax, 78h
.text:00402331                 mov     [ebp+var_84], 0
.text:0040233B                 add     eax, ecx

这一次变化比较大,除操作已经被nop掉了,401A40调用实际上已经被改成了402300调用。其它地方有没有更改不太清楚,但是更改的可能性比较小,改动开始没有显式调用的函数反倒可能会显露目标,而且这种手法明显只是为了隐藏真实的检验流程,目标已经达到(另外,我当时也简单对比下402300,没看出什么改动)。暂定先尝试手动完成父进程完成的工作,看程序运行情况,及检验流程情况。 直接改成这样:

004017BD      90            nop
004017BE      90            nop
004017BF  |.  8945 08       mov [arg.1],eax
004017C2  |.  FF75 08       push [arg.1]
004017C5  |.  8D85 00FAFFFF lea eax,[local.384]
004017CB  |.  50            push eax
004017CC      E8 2F0B0000   call 11-ToBeB.00402300

    算法

动态前先看下静态的情况,函数过程比较复杂,代码量比较大。我们从输入输出入手,函数参数只有一个输入的字串,返回是True则成功,函数对参数及返回读写的区域在4029D5402B34之间。所以这部分是重点跟的。

动态跟了下这个函数开头,此处利用开始壳读取并修改的文件内容(从这里可以看出,开始从堆中dump出来的程序是不能完成验证的),加载库及其它操作。直接跳过,在下断4029D5

这里开始的算法比较简单。就是用输入前8位作为一个hex数a1,9-10位为一个hex数a2。对一堆区0x5000 byte的数据进行运算,最后与byte_4340B0处的数据比较前0x60个字节。运算部分的代码翻译过来如下:

int j = 0
for (int i = a1; i < a1 +0x5000/4;i++)
{
    output[j] = (a2*0x1010101+i)^output[j];
    j++;
}

反解,得到前10字节,0x75A29C09E1,注意,在unhex的函数中均将输入以大写hex进行转换,反解代码如下:

m1 = 0x83F08EA7
m2 = 0x3F0FBA29
c1 = 0x1070EC81
c2 = 0x55530000
d = 0x1010101
for i in range(0x100):
    if ((i*d+m1)^c1)&0xffffffff == (((i*d+m2)^c2)&0xffffffff)-1:
        print hex(i)        
        print hex(((i*d+m1)^c1)&0xffffffff)

后面memset两个栈区后,将第11位开始的输入unhex(也要求输入是大写hex)。接着读入!HelloHaniella!常量,并将硬编码的19970907写入栈区,并调用前面刚运算完的堆区,原来上面是为后面解码代码的。

进去看了看,算法比较明显,DES。原因有2:一是19970907应该是作为密钥的,进去后立即被变换成56bit;二是再看下变换表,就是DES的表。

后面还有IP变换表,S-BOX变换表,移位表,P变换表等,我一一比对,没有改动。这里有个细节有注意了下,轮密钥是从后面开始用的,说明这是解密过程。下面的代码就是轮密钥与扩展密文的异或过程,轮密钥从后往前用的:

0068101A    8BCA            mov ecx,edx
0068101C    8DB424 300C0000 lea esi,dword ptr ss:[esp+0xC30]
00681023    8DBC24 900C0000 lea edi,dword ptr ss:[esp+0xC90]
0068102A    F3:A5           rep movs dword ptr es:[edi],dword ptr ds>
0068102C    33F6            xor esi,esi
0068102E    8B7C24 14       mov edi,dword ptr ss:[esp+0x14]
00681032    8BCE            mov ecx,esi
00681034    2BCF            sub ecx,edi
00681036    8A8C0C 50100000 mov cl,byte ptr ss:[esp+ecx+0x1050]
0068103D    308C34 900C0000 xor byte ptr ss:[esp+esi+0xC90],cl
00681044    46              inc esi
00681045    83FE 30         cmp esi,0x30
00681048  ^ 7C E4           jl short 0068102E

DES加密完成后,与!HelloHaniella!按byte比较,直到遇到0,结束比较,0也进行了比较。

00402AF2  |> /8A10          /mov dl,byte ptr ds:[eax]   ;加密串
00402AF4  |. |3A11          |cmp dl,byte ptr ds:[ecx]   ;常量
00402AF6  |. |75 30         |jnz short 11-ToBeB.00402B28
00402AF8  |. |84D2          |test dl,dl
00402AFA  |. |74 12         |je short 11-ToBeB.00402B0E
00402AFC  |. |8A50 01       |mov dl,byte ptr ds:[eax+0x1]
00402AFF  |. |3A51 01       |cmp dl,byte ptr ds:[ecx+0x1]
00402B02  |. |75 24         |jnz short 11-ToBeB.00402B28
00402B04  |. |83C0 02       |add eax,0x2
00402B07  |. |83C1 02       |add ecx,0x2
00402B0A  |. |84D2          |test dl,dl
00402B0C  |.^\75 E4         \jnz short 11-ToBeB.00402AF2

到这里,我直接将检验值进行des加密,结果竟然不对。此处我花了太多时间,重新对比变换表,及算法过程。确实没有发现问题。没办法,我直接将改待解密数据为检验值,将轮密钥前后倒置,最后看了结果与我算的还是不一样。因为此,我花了半天时间。

最后直接一步一步跟,并写了des,打出各个过程中值,进行比对。解密完成也没有发现不对。后面再跟,发现最后结果提取作了手脚,结果数组下标为偶数的进行了取反。我的天,这是玩心理学啊。其代码如下:

0068248C    8BD6            mov edx,esi 
0068248E    81E2 01000080   and edx,0x80000001
00682494    79 05           jns short 0068249B
00682496    4A              dec edx
00682497    83CA FE         or edx,-0x2
0068249A    42              inc edx
0068249B    75 11           jnz short 006824AE
0068249D    389C34 10020000 cmp byte ptr ss:[esp+esi+0x210],bl
006824A4    0f95c1          setne cl
006824A7    888C34 10020000 mov byte ptr ss:[esp+esi+0x210],cl
006824AE    46              inc esi 
006824AF    83FE 40         cmp esi,0x40
006824B2  ^ 7C D8           jl short 0068248C

DES的过程如下,数字为偏移:

06cd  轮密钥完成
0f6c  扩展表加载完成
0f99  ip置换完成
101a  R扩展
103d  扩展输入与轮密钥异或  轮密钥从16组开始用
22e4  s盒加载完成
23aa  s盒转换完成
23cc  p盒置换完成
2400  R与L异或完成
2413  R L交换
2426  一次round结束

将检验值!HelloHaniella!\x00对应位取反后得到16进制字串为8BE2CFC6C6C5E2CBC4C3CFC6C6CB8BAA,用19930907加密,得到80217C048420956C15DA309FF2B69170,最终key为75A29C09E180217C048420956C15DA309FF2B69170

    最后

得到了正确的key,试了下最开始从壳入口附近dump的出的程序,果然验证不能通过。 另外,由于最后程序没有验证没有固定长度,开始也没有检验长度,所以后面再加des的分组,就会造成多解,加一组,那多解数量也是十分巨大的。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 112
活跃值: (27)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
伊邪那美 2017-6-27 15:51
2
0
谢谢分享
雪    币: 10072
活跃值: (2918)
能力值: ( LV15,RANK:515 )
在线值:
发帖
回帖
粉丝
anhkgg 7 2017-6-28 09:15
3
0
感谢!
游客
登录 | 注册 方可回帖
返回