SSCTF逆向部分Writeup


作者:代码疯子

原文链接:http://www.programlife.net/ssctf-reverse-writeup.html


11月1号~11月2号两天和队友参加了SSCTF,我主要做了一下里面Reverse相关的题目。SSCTF里面逆向相关的题目全是Windows下的CrackMe程序,除了Web分类里面夹杂了一个病毒分析题目外。总的来说不是很难,不过中间有个CrackMe是易语言写的,直接没有接触过这种题,就觉得有点棘手了,不过看了别人的writeup之后觉得还是非常简单的。

0×01. CrackMe1
通过IDA进行静态分析,可以很快找到关键函数位于00401000,F5得到伪代码,大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
bool __cdecl fnCheckLogin(const char *szUsername, const char *szPassword){
  const char *v2; // esi@1
  unsigned int v3; // kr04_4@1
  int v4; // ebp@1
  bool result; // al@2
  int v6; // eax@3
  int *v7; // edi@4
  const char v8; // bl@5
  const char *v9; // edi@6
  int v10; // ecx@6
  bool v11; // zf@6 
  v2 = szUsername;
  v3 = strlen(szUsername) + 1;
  v4 = v3 - 1;
  if ( v3 - 1 == strlen(szPassword) )           // Username和Password长度要一致
  {
    v6 = 0;
    if ( v4 > 0 )
    {
      v7 = &dword_408030;                       // xor key
      do
      {
        v8 = *(_BYTE *)v7 ^ szPassword[v6];
        ++v7;
        szPassword[v6++] = v8;
      }
      while ( v6 < v4 );
    }
    v9 = szPassword;
    v10 = v3 - 1;
    v11 = 1;
    do
    {
      if ( !v10 )
        break;
      v11 = *v2++ == *v9++;
      --v10;
    }
    while ( v11 );
    result = v11;
  }
  else
  {
    result = 0;
  }
  return result;}

就是一个简单的XOR操作,在dword_408030处定义了一个数组,里面存储了XOR的KEY,提取出来就可以写keygen了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python# -*- coding:utf-8 -*-import sysimport hashlib
 def crack(username):
    key = [1, 2, 3, 4, 1, 5, 1, 5, 1, 3, 6, 5, 
           4, 8, 5, 3, 1, 2, 3, 4, 5, 3, 5, 7, 
           2, 3, 2, 4, 8, 2, 5, 6, 4]
    pwd = []
    for i in xrange(0, len(username)):        pwd.append(chr(ord(username[i]) ^ key[i]))
    return "".join(pwd) def main():
    username = "Wins0n"
    pwd = crack(username)
    print pwd
    print hashlib.md5(pwd).hexdigest().upper() if __name__ == "__main__":
    main()
    # 3FFCD172BE3572F485CA19C3227B1081

0×02. CrackMe2
此题参考“一朵紫桔桦”的Writeup。易语言写的,不太会搞~~这个CrackMe有一些反调试措施,所以如果在OD里直接退出了,就多试几次。
OD载入程序后,Alt+M打开Memory map窗口,在CrackMe的.data上面下一个断点。然后运行程序,程序会在krnln模块中断下,F8单步几次执行完第一个jmp指令就回到Crackme的空间了。
OD跟踪CrackMe在krnln中断下
回到Crackme的空间后进行字符串查找,就可以看到敏感的字符串了,对引用这几个字符串的位置都设置断点,然后进行跟踪分析。
OD查找CrackMe字符串
程序会判断注册码的长度是否为32:
注册码长度必须为32
输入一个32位的注册码进行测试,继续跟踪,在下面的位置可以找到明文注册码:
明文注册码比较
Wins0n对应的注册码为027d88ee0aa1836843632a275c9b6214

0×03. CrackMe3
对话框过程函数DialogFunc伪代码分析:
对话框过程函数分析
函数功能为记录鼠标左键以及鼠标右键点击序列(左键点击为L,右键点击为R),点击次数即为dwClickCount,点击序列即为pbData,则将pbData + dwClickCount – 0×30作为参数传入sub_401C40函数。另外使用PEiD的Krypto ANALyzer可以看到程序使用了MD5算法,sub_401C40函数的伪代码如下:
关键函数伪代码
函数sub_401B90计算传入参数的MD5值并存入Caption位置,这个就是Flag。对传入的序列以8位一个分组进行计算,共6个分组,产生“查水表”对应的HEX值。
CrackMe Flag
Flag: D27789EFCA409B6B6EE297D412334A65

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python# -*- coding:utf-8 -*-import sysimport hashlibimport itertools def crackSingle(map, val):
    dst = 0
    for i in xrange(0, 8):
        dst = (dst * 2) & 0xFF
        if map[i] == 'R':
            dst = (dst + 1) & 0xFF
    return dst == val
 def genMaps():    map = ['L', 'R']
    return list(itertools.product(map, repeat=8)) def crack():
    maps = genMaps()
    vals = [0xB2, 0xE9, 0xCB, 0xAE, 0xB1, 0xED]
    serial = []
    for val in vals:        for map in maps:            if crackSingle(map, val):
                serial.extend(map)
                break
    return "".join(serial) if __name__ == "__main__":
    serial = crack()
    print "Flag: %s" % (hashlib.md5(serial).hexdigest().upper())
    print "Clicks: %s" % ('L'*12 + serial)
    # flag   = D27789EFCA409B6B6EE297D412334A65
    # serial = RLRRLLRLRRRLRLLRRRLLRLRRRLRLRRRLRLRRLLLRRRRLRRLR

0×04. CrackMe4
PEiD查壳,提示FSG 2.0 -> bart/xt [Overlay],下载FSG脱壳器对程序进行脱壳,对脱壳后的程序进行测试,提示“数据已经损坏”。将原始程序的Overlay数据附加到脱壳后的文件的末尾,即从文件偏移0×955处至末尾的数据为Overlay数据,添加数据到脱壳后的文件后即可进行正常的密码验证操作。
第一层密码的验证过程如下:
1. 读取文件末尾的316字节数据,并判断最后的16字节组成的字符串是否为seclover.com;
2. 使用seclover.com作为key对这316字节的数据进行XOR操作;
3. 使用HOWMP拼接密码计算MD5值,然后对MD5再次进行MD5运算;
4. 判断MD5值与数据偏移264处的MD5值是否一致;
5. 解密后的MD5为09B2F924C20C5CA427EED2C5B98BEFBF;
6. 提示密码为6位数字,所以进行枚举即可,得到密码为564987;
CrackMe3密码校验算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python# -*- coding:utf-8 -*-import hashlib
 def crack(val):    for i in xrange(0, 1000000):
        msg = "HOWMP%06d" % i
        msg = hashlib.md5(msg).hexdigest().upper()
        msg = hashlib.md5(msg).hexdigest().upper()
        if msg == val:            return "%06d" % i    return None def main():    print crack("09B2F924C20C5CA427EED2C5B98BEFBF") if __name__ == "__main__":
    main()
    # 564987

释放的文件仍然使用FSG 2.0 -> bart/xt [Overlay]加密,所以可以用同样的方法进行解密。第二层密码的校验方法和第一层是一样的,MD5校验正确后释放logo.gif文件。MD5值为:12A73DEB50334C0B446937B3E31A322D。虽然密码只有六位,但是可能的情况太多了,暴力破解断时间不可行。观察末尾解密后的316字节的数据,发现会创建一个gif文件,同时可以知道文件加密后的数据为316字节数据之前的0x128C字节的数据,而解密的方法即为使用密码对其进行XOR操作。根据GIF文件格式,其头部标志为GIF89a,那么我们可以计算出key为w!q&cs。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python# -*- coding:utf-8 -*-import hashlib
 def getkey():
    src = [0x30, 0x68, 0x37, 0x1E, 0x5A, 0x12]
    dst = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] # GIF98a
    key = []
    n = len(src)
    for i in xrange(0, n):
        key.append(chr(src[i]^dst[i]))
    return "".join(key) def main():
    key = getkey()
    print key
 if __name__ == "__main__":
    main()
    # key = w!q&cs

输入key即可得到解密的logo.gif文件。
SSCTF Crackme4

0×05. CrackMe5
直接运行程序时工作正常,而在OD中调试时遇到int 3指令(00401640函数),对其进行静态分析时发现在OnInitDialog中注册了一个全局异常处理函数。TopLevelExceptionFilter中穿插了大量的花指令,尝试对TopLevelExceptionFilter(004012F0)的代码进行手工恢复。函数004012F0的主要逻辑为判断触发异常时Eip处的字节是否为0xCC,如果是则跳转到下一个字节,根据当前字节计算出一个偏移值作为增量添加给Eip,最后Eip增加1,继续执行代码。将提取出来的反汇编代码整理成一个cpp文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
unsigned int calc(unsigned char arg){
    int res = 0;
    __asm    {
        push    ebx             // ebx = 0
        mov     ebx, 1          // 0xCC
        mov     cl, arg         // 参数值
        mov     al, cl
        shl     al, 6
        shr     cl, 2
        add     al, cl
        xor     al, 0Dh
        mov     cl, al
        shr     cl, 5
        shl     al, 3
        add     cl, al
        add     cl, 11h
        mov     al, cl
        shl     al, 5
        shr     cl, 3
        add     al, cl
        xor     al, 51h
        mov     cl, al
        shl     cl, 7
        shr     al, 1
        add     cl, al
        sub     cl, 6Fh
        and     ecx, 0FFh
        and     ecx, 80000007h
        jns     flag_C
        dec     ecx
        or      ecx, 0FFFFFFF8h
        inc     ecx
flag_C:
        add     ebx, ecx        // ebx = ebx + ecx
        inc     ebx             // ebx = ebx + 1
        mov     res, ebx        // save result
        pop     ebx    }
    return res;}

从进程空间中提取[00401640, 00402591]区间内的数据并保存,对文件中的花指令进行清理,将原始程序文件偏移0×1640处开始的3922字节替换为处理后的数据。之后就可以使用IDA对Patch后的文件进行分析,并将00401640设置为函数,就可以使用F5得到伪代码。最后编写keygen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <stdio.h>#include <string.h> typedef /*unsigned*/ char _BYTE;typedef /*unsigned*/ int _DWORD; unsigned int hash_name(char* msg){
    int v1; // ebx@1
    void *v2; // edi@1
    int result; // eax@3
    int v4; // eax@4
    int v5; // ecx@4
    int v6; // edx@5
    int v7; // ebx@5
    int v8; // eax@6
    int v9; // edx@7
    int v10; // ebx@7
    int v11; // eax@8
    int v12; // edx@9
    int v13; // ebx@9
    int v14; // edx@10
    int v15; // ecx@10
    unsigned int v16; // ebx@10
    int v17; // esi@10
    int v18; // eax@10
    int v19; // eax@11
    int v20; // [sp+Ch] [bp-8h]@1
    int v21; // [sp+10h] [bp-4h]@1 
    v1 = 121314;
    v20 = (_DWORD)msg;
    v21 = strlen(msg); 
    v5 = -1 - v20;
    v4 = v21 + v20 + 1;
    do
    {
      v6 = *(_BYTE *)(v4-- - 2);
      v7 = 123 * (v6 + v1);
      *(_BYTE *)v4 = v7;
      v1 = v7 ^ 0x14AC453A;
      *(_BYTE *)v4 += v1;
    }
    while ( v5 + v4 > 0 );
    v8 = v21 + v20 + 1;
    do
    {
      v9 = *(_BYTE *)(v8-- - 2);
      v10 = 123 * (v9 + v1);
      *(_BYTE *)v8 = v10;
      v1 = v10 & 0xA454A546;
      *(_BYTE *)v8 += v1;
    }
    while ( v8 + v5 > 0 );
    v11 = v21 + v20 + 1;
    do
    {
      v12 = *(_BYTE *)(v11-- - 2);
      v13 = 123 * (v12 + v1);
      *(_BYTE *)v11 = v13;
      v1 = v13 | 0x15472137;
      *(_BYTE *)v11 += v1;
    }
    while ( v11 + v5 > 0 );
    v16 = v1 % 0x15011Fu;     return v16;} // reverse but has a bug...000000unsigned int hash_pwd(char* msg){
    int v14, v15, v16, v17, v18, v19;
    v18 = (_DWORD)msg;
    v15 = 0;
    v14 = strlen(msg) + v18 + 1;
    v17 = -1 - v18;
    do
    {
      v19 = *(_BYTE *)(v14-- - 2);
      v15 = v19 + 10 * v15 - 48;
      *(_BYTE *)v14 = v15;
    }
    while ( v14 + v17 > 0 );
    return v15;} int main(int argc, char** argv){
    char buffer[256] = {0};
    printf("Username: ");
    while (EOF != scanf("%s", buffer))
    {
        int user = hash_name(buffer);
        sprintf(buffer, "%d", user);
        printf("hash(username)=%s\n", buffer);
        //printf("%d\n", hash_pwd(buffer));
        printf("%s\n", strrev(buffer));
    }
    return 0;}

SSCTF CrackMe5

0×06. U盘病毒
使用7z打开img文件,得到两个文件。
SSCTF U盘病毒分析
exe为一个游戏,autorun.inf提示信息:

你真厉害都到这了,看看这个游戏你肯定会喜欢的,但是据说这个游戏是被加了后门的,找到后门操作的文件的内容,取文件内容的16位md5值作为key!祝你好运.......

使用7z打开exe游戏文件,得到三个文件。
SSCTF U盘病毒分析 游戏解压
其中1.exe为后门文件,2.exe为真正的游戏文件。分析1.exe,发现它会在当前目录创建一个test.txt文件,创建的文件内容为:WdubQ4IGEzAG54NfATJTNhI4TLIvPvENyTLLWb3YCNBeK5wad5XCgrSQNOih1F。对这个内容进行16位MD5值:A4620BA0298017B2。

0×07. 排名
SSCTF 2014 Ranklist



评论



个人信息

求注册,求登录!

解题动态

精选练习

精选教程

联系我们