首页
社区
课程
招聘
[原创]看雪CTF2017 第七题 CrakeMe逆向分析
2017-6-14 22:54 3075

[原创]看雪CTF2017 第七题 CrakeMe逆向分析

2017-6-14 22:54
3075

1.用OD加载程序,直接崩溃

看到开头是这样,明显不正常,经过检查,发现TLS中有代码:

char __stdcall TlsCallback_0(int a1, int a2, int a3)
{
  int v3; // et1@2
  int v4; // eax@2
  _BYTE *v5; // ST2C_4@2
  char result; // al@2
  unsigned int v7; // [sp+10h] [bp-Ch]@2
  __int16 v8; // [sp+14h] [bp-8h]@2
  if ( a2 == 1 )
  {
    v3 = *MK_FP(__FS__, 24);
    v4 = *(_DWORD *)(*(_DWORD *)(__readfsdword(24) + 48) + 8);
    v5 = (_BYTE *)(v4 + *(_DWORD *)(v4 + *(_DWORD *)(v4 + 60) + 40));
    v7 = 0xCC5874EB;
    v8 = 0x75E8;
   //v5 =0x414429
    result = decode_410DD6(v5, 200, (int)&v7, 6);
  }
  return result;
}

TLS中通过PEB结构拿到当前程序的入口点0x414429,然后执行异或解密程序,用OD加载的可以勾选strongOD中下图所示的选项:

重启就会停在TLS入口点了,同时要删掉OD自动在入口添加的断点:

Breakpoints, 条目 3
 地址=00414422 <crackme.start>
 模块=crackme
 激活=仅一次
 反汇编=ADD     ESP, DWORD PTR DS:[EDX+0x5D]

不然解密之后就是图1的结果。

输入注册码断在这:

00410285 >  CC              INT3
00410286    6A 01           PUSH    0x1
00410288    FF15 343E4500   CALL    NEAR DWORD PTR DS:[<PostQuitMess>; user32.PostQuitMessage
0041028E    33C0            XOR     EAX, EAX
00410290    C2 1000         RETN    0x10

这明显是程序内设置的断点,查找异常处理:

.text:0040F2CA                 push    offset TopLevelExceptionFilter ; lpTopLevelExceptionFilter
.text:0040F2CF                 call    ds:SetUnhandledExceptionFilter

可以找到上边这个全局异常处理,同时可以看到异常处理函数:

signed int __stdcall TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  signed int result; // eax@2
  if ( ExceptionInfo->ExceptionRecord->ExceptionCode == 0x80000003 )
  {
    PostMessageW(0, 0x464u, 0, 0);
    ExceptionInfo->ContextRecord->Eip += 9;
    result = -1;
  }
  else
  {
    result = 0;
  }
  return result;
}

结合前面的断点就很清楚了,产生Int3 断点之后,程序自己处理,把eip+9,程序跳到:

0041028E    33C0            XOR     EAX, EAX

继续处理,同时会发送一个自定义消息,消息处理函数:

sub_40E37A,该函数重要内容如下:

1.限定编辑框最长长度为20

signed int __thiscall onInitDialog_4102F7(int this, int a2, int a3, int a4, int a5)
{
....
  // 0x3e8 edit
  // 0x3e9 button
  v13_edit = GetDlgItem(*v6, 0x3E8);
  SendMessageW(v13_edit, EM_SETLIMITTEXT, 0x14u, 0);
  .........
}

2.验证函数:

int __userpurge yanzheng_4104CD@<eax>(int a1@<ebp>, int a2, int a3, int a4, int a5)
{
  int v5; // ecx@1
  int v6; // esi@1
  int v7; // ecx@1
  int v9; // [sp-1Ch] [bp-1Ch]@1
  int v10; // [sp-18h] [bp-18h]@1
  int v11; // [sp-14h] [bp-14h]@1
  signed int v12; // [sp-10h] [bp-10h]@1
  int v13; // [sp-Ch] [bp-Ch]@1
  signed int v14; // [sp-8h] [bp-8h]@1
  signed int v15; // [sp-4h] [bp-4h]@1
  v15 = 144;
  sub_413FD7();
  v6 = v5;
  sub_4156A0((_BYTE *)(a1 - 128), 0, 101);
  // 获取用户输入的字符串
  GetDlgItemTextA(*(HWND *)(v6 + 4), 1000, (LPSTR)(a1 - 128), 100);
  string_string_40FD2D(a1 - 152, a1 - 128);
  *(_DWORD *)(a1 - 4) = 0;
  v14 = 5;
  v13 = a1 - 24;
  *(_DWORD *)(a1 - 24) = 0x49444550;
  v12 = 0x1AA;
  *(_BYTE *)(a1 - 20) = 89;
  // 解密代码
  decode_410DD6(sub_411B30, v12, v13, v14);
  v10 = v7;
  v9 = v7;
  *(_DWORD *)(a1 - 156) = &v9;
  string_string_1_40FD07((int)&v9, a1 - 152);
  *(_BYTE *)(a1 - 4) = 1;
  *(_BYTE *)(a1 - 4) = 0;
  // 用输入字符串计算
  sub_411B30(a1, v9, v10, v11, v12, v13, v14);
  decode_410DD6(sub_411B30, 0x1AA, a1 - 24, 5);
  // 验证结果,解密shellcode执行
  check_411825(a1);
  *(_DWORD *)(a1 - 4) = -1;
  sub_410AE3(a1 - 152, 1, 0);
  return sub_413F81(v14, v15);
}

3.第一步验证:

sub_411B30

主要分3步,

第一步把输入的字符串每一位异或0xcc

第二步,查表计算,这个不怎么好描述,用python描述大致如下:

data=[0x89, 0xbc, 0x95, 0xfc, 0xfb, 0xba, 0xed, 0x9a, 0xbb, 0xae, 0xfe, 0x99, 0xa2, 0x98, 0xb9, 0xf9,
		0x9f, 0x84, 0x9c, 0xfd, 0x83, 0xad, 0xb6, 0xa9, 0xa5, 0xf5, 0x8c, 0xa7, 0x9e, 0x96, 0x8a, 0xf4,
		0x85, 0xbe, 0xa8, 0x8f, 0x86, 0xaf, 0x88, 0x9d, 0x87, 0xbf, 0xff, 0xa1, 0x8b, 0x81, 0xa0, 0xab,
		0x8e, 0xbd, 0xb5, 0xaa, 0x82, 0x94, 0xa4, 0x8d, 0xa3, 0xf8, 0xb4, 0xfa, 0x9b, 0xa6, 0xb8, 0x80]
strinput="BwnsAtPediy2017KX9Ok"
strList=[]
#第一步
for x in strinput:
    strList.append(ord(x)^0xcc)
#print hex(strList[0])
print "\nfinal calc"
#第二步
nDataLen=len(data)
calcList=[]
for i,x in enumerate(strList):
    index=data.index(x)
    print hex(index),
    y=i+1
    while True:        
        nCount=index/5+5
        nNum=0
        while True:
            index+=1
            if index==64:
                index=0
            nNum+=1
            if nCount==nNum:
                break
        print hex(index),
        y-=1
        if y<=0:
            break
    index=index%nDataLen
    calcList.append(data[index])

第三部,处理第二步的结果:

char __cdecl sub_410F75(_BYTE *a1, unsigned int a2)
{
  _BYTE *v2; // edx@1
  char result; // al@1
  unsigned int v4; // esi@1
  int v5; // edi@1
  v2 = a1;
  result = 0;
  v4 = a2 - (_DWORD)a1;
  v5 = 0;
  if ( (unsigned int)a1 > a2 )
    v4 = 0;
  if ( v4 )
  {
    do
    {
      result = 8 * (*v2 ^ 0xCC) | ((char)(*v2 ^ 0xCC) >> 5);
      *v2++ = result;
      ++v5;
    }
    while ( v5 != v4 );
  }
  return result;
}

验证函数:

sub__411825

主要函数:

sub_411975

先把之前处理的结果由16进制转成字符串--

00411987    E8 93FFFFFF     CALL    <crackme.hex2string_41191F>

假设字符串为:

A30B1B828ABB4A9BA9BB93AAA36B82AA024AB243

然后求得每一个字符在下面数组中的下标

004119F4    E8 53F5FFFF     CALL    <crackme.strstr_410F4C>

0012FB34  30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46  0123456789ABCDEF

比如A的下标为index=A

然后把low=index/4  ,hgt=index%4的结果保存起来,

最后与一个保存好的数组比较。

00411A55    50              PUSH    EAX
00411A56    FF75 B8         PUSH    DWORD PTR SS:[EBP-0x48]
00411A59    FF76 18         PUSH    DWORD PTR DS:[ESI+0x18]
00411A5C    E8 C9460000     CALL    <crackme.cmp_is_equ_41612A>

数组:
MyArray=[0x02, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02,
		0x02, 0x00, 0x02, 0x02, 0x02, 0x03, 0x02, 0x03, 0x01, 0x00, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03,
		0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02,
		0x02, 0x02, 0x00, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
		0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03]

如果相等返回1,不相等返回0

相等则进行下边的shellcode解码:

shellCode=[0x17, 0xfc, 0x82, 0x19, 0xbe, 0x1c, 0xb8, 0x60, 0x21, 0x69, 0x1d, 0x93, 0x30, 0x31, 0x37, 0x4b,0x08, 0xba, 0xa3, 0x4f,
         0xe3, 0xf3, 0x6a, 0x33, 0x41, 0x47, 0x95, 0xec, 0x21, 0x99, 0x29, 0xbf, 0x75, 0xc5, 0x53, 0xe8, 0x58, 0x39, 0x4f, 0x6b,
         0xc1, 0x12, 0xb6, 0x73, 0xcc, 0x31, 0x88, 0x35, 0xe9, 0x24, 0xa5, 0xf5, 0x75, 0xed, 0x75, 0x1c, 0x16, 0x6a, 0x1e, 0xe6,
         0x07, 0x9a, 0xa9, 0x36, 0xa1, 0x61, 0x62, 0x47, 0x5b, 0x39, 0xf4, 0x77, 0xd0, 0xf6, 0x72, 0xaf, 0x3a, 0x0a, 0x6e, 0x56,
	 0x12, 0xfa, 0x2b, 0xa3, 0x86, 0x31, 0xb8, 0x42, 0x12, 0x1d, 0x03, 0x62, 0xf6, 0x74, 0xdb, 0x09, 0xb0, 0x2b, 0xb9, 0x94,
         0xbd, 0xf4, 0xaa, 0x67, 0xcc, 0x31, 0xb0, 0x0f, 0x24, 0x01, 0x64, 0x0f, 0x70, 0x31, 0x67, 0x21, 0x58, 0xc6, 0x5a, 0x9b,
         0x7f, 0x32, 0x6e, 0xf8, 0x0c, 0x80, 0x34, 0xec, 0x69, 0x69, 0x79, 0x32, 0x30, 0x68, 0xbc, 0x06, 0xa8, 0x0a, 0x82, 0x83,
         0x6d, 0x53, 0x6e, 0x73, 0xca, 0x91, 0x0d, 0xa6]

解码算法大概:

int runShellCode(char* psz,int nLen)
{
    unsigned char szBuf[sizeof(g_shellCode)]={0};
    memcpy_s(szBuf,sizeof(g_shellCode),g_shellCode,sizeof(g_shellCode));
 //void (*func)(void);  
    for (int i=0;i<sizeof(g_shellCode);i++)
    {
        int nIndex=i%nLen;
        szBuf[i]=szBuf[i]^psz[nIndex];
    }
    int nRet=1;
    try
    {
        ((void(*)(void))&szBuf)();
    }
    catch (...)
    {
    	nRet=0;
    }
    return nRet;
}
char* psz="BwnsAtPediy2017KX9Ok";
runShellCode(psz,strlen(psz));

shellcode解码正确为:

00C40000    55              PUSH    EBP
00C40001    8BEC            MOV     EBP, ESP
00C40003    6A FF           PUSH    -0x1
00C40005    68 E8054500     PUSH    0x4505E8
00C4000A    64:A1 00000000  MOV     EAX, DWORD PTR FS:[0]
00C40010    50              PUSH    EAX
00C40011    83EC 24         SUB     ESP, 0x24
00C40014    A1 84044000     MOV     EAX, DWORD PTR DS:[<crackme.___s>
00C40019    33C5            XOR     EAX, EBP
00C4001B    8945 F0         MOV     DWORD PTR SS:[EBP-0x10], EAX
00C4001E    50              PUSH    EAX
00C4001F    8D45 F4         LEA     EAX, DWORD PTR SS:[EBP-0xC]
00C40022    64:A3 00000000  MOV     DWORD PTR FS:[0], EAX
00C40028    8365 D8 00      AND     DWORD PTR SS:[EBP-0x28], 0x0
00C4002C    8D45 D8         LEA     EAX, DWORD PTR SS:[EBP-0x28]
00C4002F    50              PUSH    EAX
00C40030    8D4D DC         LEA     ECX, DWORD PTR SS:[EBP-0x24]
00C40033    C745 DC 42574E5>MOV     DWORD PTR SS:[EBP-0x24], 0x534E5>
00C4003A    51              PUSH    ECX
00C4003B    8D45 ED         LEA     EAX, DWORD PTR SS:[EBP-0x13]
00C4003E    C745 E0 1532223>MOV     DWORD PTR SS:[EBP-0x20], 0x3F223>
00C40045    50              PUSH    EAX
00C40046    8D45 E0         LEA     EAX, DWORD PTR SS:[EBP-0x20]
00C40049    C745 E4 6233213>MOV     DWORD PTR SS:[EBP-0x1C], 0x3D213>
00C40050    50              PUSH    EAX
00C40051    8D45 D0         LEA     EAX, DWORD PTR SS:[EBP-0x30]
00C40054    C745 E8 2776747>MOV     DWORD PTR SS:[EBP-0x18], 0x7A747>
00C4005B    50              PUSH    EAX
00C4005C    C645 EC 42      MOV     BYTE PTR SS:[EBP-0x14], 0x42
00C40060    E8 AE107DFF     CALL    crackme.00411113
00C40065    83C4 14         ADD     ESP, 0x14
00C40068    8D45 E0         LEA     EAX, DWORD PTR SS:[EBP-0x20]
00C4006B    6A 40           PUSH    0x40
00C4006D    68 1D3D4000     PUSH    0x403D1D
00C40072    50              PUSH    EAX
00C40073    6A 00           PUSH    0x0
00C40075    FF15 F03D4500   CALL    NEAR DWORD PTR DS:[<crackme.Mess>; user32.MessageBoxA
00C4007B    8B4D F4         MOV     ECX, DWORD PTR SS:[EBP-0xC]
00C4007E    64:890D 0000000>MOV     DWORD PTR FS:[0], ECX
00C40085    59              POP     ECX
00C40086    8B4D F0         MOV     ECX, DWORD PTR SS:[EBP-0x10]
00C40089    33CD            XOR     ECX, EBP
00C4008B    E8 CB3E7DFF     CALL    crackme.00413F5B
00C40090    8BE5            MOV     ESP, EBP
00C40092    5D              POP     EBP
00C40093    C3              RETN


反推过程:

1.根据下属函数的比较结果以及数组可以推出来sub_411B30 计算后正确字符串

00411A5C    E8 C9460000     CALL    <crackme.cmp_is_equ_41612A>
MyArray=[0x02, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02,
		0x02, 0x00, 0x02, 0x02, 0x02, 0x03, 0x02, 0x03, 0x01, 0x00, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03,
		0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x03, 0x02, 0x01, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02,
		0x02, 0x02, 0x00, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
		0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03]
stringRet=""
nLen=len(MyArray)
for x in range(0,nLen/2):
    index=MyArray[x*2]*4+MyArray[x*2+1]
    stringRet+=strCode[index]
print stringRet	

//out  A30B1B828ABB4A9BA9BB93AAA36B82AA024AB243


根据这个串能计算出sub_410f75之前的数组:

def getNum(findNum):
    for x in range(0,256):
        num=(8*(x^0xcc) | ((x^0xcc)>>5))%256
        if num==findNum:
            return x
testList=[]
print len(stringRet)/2
for i in range(0,len(stringRet)/2):
    findNum=stringRet[i*2:i*2+2]
    hexNum=int(findNum,16)
    testList.append(getNum(hexNum))

正确序列 #0xb8 0xad 0xaf 0x9c 0x9d 0xbb 0x85 0xbf 0xf9 0xbb 0xbe 0x99 0xb8 0xa1 0x9c 0x99 0x8c 0x85 0x9a 0xa4   正确的

根据上述字符串爆破:

data=[0x89, 0xbc, 0x95, 0xfc, 0xfb, 0xba, 0xed, 0x9a, 0xbb, 0xae, 0xfe, 0x99, 0xa2, 0x98, 0xb9, 0xf9,
		0x9f, 0x84, 0x9c, 0xfd, 0x83, 0xad, 0xb6, 0xa9, 0xa5, 0xf5, 0x8c, 0xa7, 0x9e, 0x96, 0x8a, 0xf4,
		0x85, 0xbe, 0xa8, 0x8f, 0x86, 0xaf, 0x88, 0x9d, 0x87, 0xbf, 0xff, 0xa1, 0x8b, 0x81, 0xa0, 0xab,
		0x8e, 0xbd, 0xb5, 0xaa, 0x82, 0x94, 0xa4, 0x8d, 0xa3, 0xf8, 0xb4, 0xfa, 0x9b, 0xa6, 0xb8, 0x80]
# strBox这个序列可以根据data异或0xcc 推出
strBox="!0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def baoPao(n,value):
    nDataLen=len(data)
    for c in strBox:
        index=data.index(ord(c)^0xcc)
        y=n+1
        while True:        
            nCount=index/5+5
            nNum=0
            while True:
                index+=1
                if index==64:
                    index=0
                nNum+=1
                if nCount==nNum:
                    break
            #print index,
            y-=1
            if y<=0:
                break
        index=index%nDataLen
        if data[index]==value:
            print n,value,c,hex(ord(c))
            #return c
rightList=[0xb8,0xad,0xaf,0x9c,0x9d,0xbb,0x85,0xbf,0xf9,0xbb,0xbe,0x99,0xb8,0xa1,0x9c,0x99,0x8c,0x85,0x9a,0xa4]
flag=""
for i,x in enumerate(rightList):
    baoPao(i,x)
    print "\n"
print flag

爆破结果:

0 B 0x42
1 j 0x6a
1 w 0x77
2 n 0x6e
3 d 0x64
3 s 0x73
4 A 0x41
4 Y 0x59
4 l 0x6c
5 b 0x62
5 t 0x74
6 P 0x50
6 i 0x69
7 H 0x48
7 e 0x65
8 c 0x63
8 d 0x64
8 s 0x73
9 P 0x50
9 i 0x69
10 y 0x79
11 2 0x32
11 5 0x35
12 0 0x30
12 g 0x67
12 o 0x6f
13 1 0x31
13 a 0x61
14 4 0x34
14 7 0x37
14 B 0x42
15 @ 0x40
15 K 0x4b
15 r 0x72
16 G 0x47
16 J 0x4a
16 X 0x58
17 9 0x39
17 D 0x44
17 I 0x49
18 O 0x4f
19 F 0x46
19 k 0x6b

以上有两个以上数字的有多种可能。

结合一般的函数开头是:

//开头  55 8B EC
push ebp
mov ebp,esp

//结尾 8B E5 5D C3
mov esp,ebp
retn

可以推出前三位为:

Bwn

5-8为AtPe

加上猜测:

1.作者ID: 不问少年 Bwsn-->Bwns

2.Pe -->Pediy

再结合下述特征:


最终推得

xorCode=[0x17, 0xfc, 0x82, 0x19, 0xbe, 0x1c, 0xb8, 0x60, 0x21, 0x69, 0x1d, 0x93, 0x30, 0x31, 0x37, 0x4b,0x08, 0xba, 0xa3, 0x4f,
         0xe3, 0xf3, 0x6a, 0x33, 0x41, 0x47, 0x95, 0xec, 0x21, 0x99, 0x29, 0xbf, 0x75, 0xc5, 0x53, 0xe8, 0x58, 0x39, 0x4f, 0x6b,
         0xc1, 0x12, 0xb6, 0x73, 0xcc, 0x31, 0x88, 0x35, 0xe9, 0x24, 0xa5, 0xf5, 0x75, 0xed, 0x75, 0x1c, 0x16, 0x6a, 0x1e, 0xe6,
         0x07, 0x9a, 0xa9, 0x36, 0xa1, 0x61, 0x62, 0x47, 0x5b, 0x39, 0xf4, 0x77, 0xd0, 0xf6, 0x72, 0xaf, 0x3a, 0x0a, 0x6e, 0x56,
     0x12, 0xfa, 0x2b, 0xa3, 0x86, 0x31, 0xb8, 0x42, 0x12, 0x1d, 0x03, 0x62, 0xf6, 0x74, 0xdb, 0x09, 0xb0, 0x2b, 0xb9, 0x94,
         0xbd, 0xf4, 0xaa, 0x67, 0xcc, 0x31, 0xb0, 0x0f, 0x24, 0x01, 0x64, 0x0f, 0x70, 0x31, 0x67, 0x21, 0x58, 0xc6, 0x5a, 0x9b,
         0x7f, 0x32, 0x6e, 0xf8, 0x0c, 0x80, 0x34, 0xec, 0x69, 0x69, 0x79, 0x32, 0x30, 0x68, 0xbc, 0x06, 0xa8, 0x0a, 0x82, 0x83,
         0x6d, 0x53, 0x6e, 0x73, 0xca, 0x91, 0x0d, 0xa6]
xxDecode=[0x55, 0x8b, 0xec, 0x6a, 0xff, 0x68, 0xe8, 0x05, 0x45, 0x00, 0x64, 0xa1, 0x00, 0x00, 0x00, 0x00,
	 0x50, 0x83, 0xec, 0x24]
#print len(xxDecode)
#print hex(ord('O')^xorCode[18])
strKey=""
for i,x in enumerate(xxDecode):
    c=x^xorCode[i]
    strKey+=chr(c)
    #print i,hex(c),c,chr(c)
print strKey



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

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回