首页
论坛
课程
招聘
[原创]Flare-ON 8th 之第九题evil
2021-10-28 13:46 11161

[原创]Flare-ON 8th 之第九题evil

2021-10-28 13:46
11161

概述

今年由火眼举办的flare-on 8th CTF刚刚结束,接下来准备介绍令我印象比较深刻的第九题evil的解题方法。
图片描述

反混淆

evil混淆了API调用,导致IDA分析失败,以此来对抗静态分析。

确定混淆模式

主体的API调用基本都会被混淆成这种模式:
图片描述
引发异常后,会跳到异常处理程序,异常处理会取出edx,ecx,通过hash得到对应API地址,给eax赋值后,将当前EIP+3的地方修改为call eax,并继续执行。
图片描述
当然引发异常不仅仅是xor eax,eax;mov eax,[eax]这种指令序列,还有如下指令序列:

1
2
3
4
5
6
7
8
9
10
11
.text:0040245E 33 C0                          xor     eax, eax
.text:00402460 8B 00                          mov     eax, [eax]
------------------------------------------------------------
.text:00402773 33 C0                          xor     eax, eax
.text:00402775 F7 F0                          div     eax
------------------------------------------------------------
.text:0040279E 33 F6                          xor     esi, esi
.text:004027A0 F7 F6                          div     esi
------------------------------------------------------------
.text:00402A80 33 FF                          xor     edi, edi
.text:00402A82 F7 F7                          div     edi

修复API肯定得找到所有模式,并计算出API进行修复。

寻找被混淆的API调用:

根据以上特征码很容易找到被混淆的地方,同时为了确定调用了哪个API,必须获取EDX,ECX的值,值得注意的是,这两个寄存器不是直接进行立即数赋值,它是把立即数保存到临时变量中,再由临时变量赋值到寄存器中。
图片描述
为了追踪这一过程,我编写了一个IDApython脚本,来获取所有API调用混淆的地方,同时找到对应的EDX,ECX的值:

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
from idc import *
import idaapi
 
def get_hash_args(addr):
    v_map={}
    h_map={}
    reg=['ecx','edx']
    while len(v_map)<2:
        prevaddr=prev_head(addr)
        if addr -prevaddr>7:
            print('error1 addr :0x%x'%(addr))
        op=print_insn_mnem(prevaddr)
        if op=='mov':
            v1=print_operand(prevaddr,0)
            v2=print_operand(prevaddr,1)
            if v1 in reg:
                v_map[v2]=v1
        addr=prevaddr
    #print(v_map)
    v_keys=list(v_map.keys())
    while len(h_map)<2:
        prevaddr=prev_head(addr)
        if addr -prevaddr>7:
            print('error2 addr :0x%x'%(addr))
        op=print_insn_mnem(prevaddr)
        if op=='mov':
            v1=print_operand(prevaddr,0)
            v2=get_operand_value(prevaddr,1)
            for i in range(2):
                if v_keys[i] in v1:
                    r=v_map[v_keys[i]]
                    h_map[r]=v2
        addr=prevaddr
    return h_map
 
if __name__=='__main__':
    bad_code=['33 C0 8B 00','33 C0 F7 F0','33 F6 F7 F6','33 FF F7 F7']
 
    end= 0x00445000
 
    for bc in bad_code:
        ea = 0x00401000
        while True:
            res = idaapi.find_binary(ea, end, bc, 16, idaapi.SEARCH_DOWN)
 
            if res==BADADDR: break
            hash_args=get_hash_args(res)
            print('[0x%x,0x%x,0x%x],'%(res,hash_args['edx'],hash_args['ecx']))
            ea=res+1

通过hash得到函数名

众所周知,通过hash来调用API是非常常见的技术,在这题也不例外,我们得分析它的哈希算法。
EDX里面存放着的是dll的名称的key,在程序中直接硬编码,一一对应:

1
2
3
4
5
6
7
8
9
10
dll_name_map={
    0x176684:'ntdll.dll',
    0x246132:'kernel32.dll',
    0x52325:'ws2_32.dll',
    0x234324:'user32.dll',
    0x523422:'advapi32.dll',
    0x43493856:'gdi32.dll',
    0x4258672:'ole32.dll',
    0x7468951:'oleaut32.dll'
}

ECX存放着的函数名称的hash,hash算法如下:
图片描述
写成py如下:

1
2
3
4
5
6
def hash_api_name(s):
    s=bytearray(s)
    v=0x40
    for i in range(len(s)):
        v=(s[i]- 0x45523F21*v)&0xffffffff
    return v

下面就很简单了,直接解析windows下的动态库,找到对应的hash:

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import pefile
dll_name_map={
    0x176684:'ntdll.dll',
    0x246132:'kernel32.dll',
    0x52325:'ws2_32.dll',
    0x234324:'user32.dll',
    0x523422:'advapi32.dll',
    0x43493856:'gdi32.dll',
    0x4258672:'ole32.dll',
    0x7468951:'oleaut32.dll'
}
 
fix_api_addr=[
[0x40245e,0x246132,0x66fff672],
[0x4027bc,0x246132,0xed79e920],
[0x402803,0x246132,0x3ea79291],
[0x402898,0x246132,0x3ea79291],
[0x4028e3,0x246132,0x3ea79291],
[0x402aed,0x523422,0xf0271154],
[0x402b21,0x523422,0x4c9945c7],
[0x402b7a,0x523422,0x59504677],
[0x402ba2,0x246132,0x3ea79291],
[0x402bc0,0x246132,0xed79e920],
[0x402c99,0x246132,0x3ea79291],
[0x402cf6,0x246132,0x4f2e84b7],
[0x402d39,0x246132,0x3ea79291],
[0x402dc1,0x246132,0x1c5d2c5e],
[0x402ec5,0x246132,0x4f2e84b7],
[0x402efc,0x246132,0x3ea79291],
[0x4030a1,0x7468951,0x15cf2779],
[0x4030c2,0x7468951,0x15cf2779],
[0x4030e3,0x7468951,0x15cf2779],
[0x40310a,0x7468951,0x7492cdd5],
[0x4032d1,0x523422,0xe2b0c97a],
[0x403377,0x7468951,0x72154b28],
[0x403395,0x7468951,0x3b224daa],
[0x4033b3,0x7468951,0x3b224daa],
[0x4033d1,0x7468951,0x3b224daa],
[0x403450,0x7468951,0x15cf2779],
[0x403470,0x4258672,0x9913c77c],
[0x403512,0x4258672,0x3a4d550b],
[0x40360d,0x246132,0x3ea79291],
[0x403753,0x246132,0x3ea79291],
[0x403ac0,0x52325,0x97e90ebc],
[0x403b1b,0x52325,0xd5af7bf3],
[0x403b5b,0x52325,0x3ad795fd],
[0x403c30,0x52325,0xd5af7bf3],
[0x403cdd,0x246132,0x1c5d2c5e],
[0x403dc2,0x52325,0xf0b9c6a8],
[0x403e04,0x52325,0xf0b9c6a8],
[0x403e41,0x52325,0xf0b9c6a8],
[0x403ea7,0x52325,0xf0b9c6a8],
[0x403ed2,0x52325,0xf0b9c6a8],
[0x404002,0x246132,0x30fb5637],
[0x404062,0x234324,0x7c1b5535],
[0x40408b,0x43493856,0xc496854f],
[0x40419b,0x43493856,0xaac30c],
[0x4042ac,0x43493856,0x52e698ca],
[0x4042d0,0x43493856,0xdc5bd1aa],
[0x404440,0x52325,0x8f04bc74],
[0x404477,0x52325,0x8f04bc74],
[0x4044a4,0x52325,0x8f04bc74],
[0x40461c,0x246132,0x5bb9ce5d],
[0x404646,0x246132,0x53805498],
[0x404781,0x246132,0x53805498],
[0x40650b,0x246132,0x1c5d2c5e],
[0x40671d,0x52325,0x88efa52b],
[0x40674c,0x52325,0x88efa52b],
[0x40692a,0x523422,0x4fbdc973],
[0x402773,0x246132,0x5d22746],
[0x40284a,0x246132,0xb243fe0c],
[0x402a52,0x246132,0x307d41cd],
[0x402a9f,0x246132,0xb3d0027c],
[0x402ac1,0x246132,0xb243fe0c],
[0x402c26,0x246132,0x307d41cd],
[0x402cd3,0x246132,0xb87be91c],
[0x402d11,0x246132,0xb87be91c],
[0x402de6,0x246132,0xb3d0027c],
[0x402e21,0x246132,0x5d22746],
[0x402e57,0x246132,0x5d22746],
[0x4031ab,0x523422,0xcac815fa],
[0x4031db,0x523422,0xcac815fa],
[0x40320e,0x523422,0xcac815fa],
[0x403246,0x523422,0x555cd98],
[0x4032ad,0x523422,0x15547697],
[0x4034b2,0x4258672,0x36e2fadd],
[0x40371d,0x246132,0xb3d0027c],
[0x403baf,0x52325,0xeb68c9d0],
[0x403c06,0x52325,0xa572514d],
[0x403c74,0x52325,0xa572514d],
[0x403fa7,0x52325,0xa4e84503],
[0x404039,0x246132,0xf8395491],
[0x40411b,0x43493856,0x6cdb8a4],
[0x4041e6,0x43493856,0x58f1cfd],
[0x4043b2,0x52325,0x774bbdd0],
[0x4043d2,0x52325,0xa0b3da21],
[0x404ffd,0x52325,0xa0b3da21],
[0x406530,0x246132,0xb3d0027c],
[0x406567,0x246132,0x5d22746],
[0x40659d,0x246132,0x5d22746],
[0x406638,0x246132,0x5d22746],
[0x406671,0x246132,0x5d22746],
[0x406767,0x52325,0xc3e4c63f],
[0x40682d,0x523422,0xcac815fa],
[0x40685d,0x523422,0xcac815fa],
[0x406890,0x523422,0xcac815fa],
[0x4068c5,0x523422,0x94f7a04c],
[0x4068f3,0x523422,0x151b52df],
[0x40279e,0x246132,0x277d84bb],
[0x402874,0x246132,0x3f2eef6c],
[0x402c53,0x246132,0xcfe0d62f],
[0x402d83,0x246132,0x277d84bb],
[0x4032f7,0x523422,0x539dda96],
[0x403af2,0x52325,0xddc03158],
[0x40416a,0x43493856,0x90ee9f3b],
[0x4042f7,0x234324,0x2cae18a6],
[0x404557,0x246132,0x277d84bb],
[0x4046c7,0x246132,0x277d84bb],
[0x4046fe,0x246132,0x277d84bb],
[0x406950,0x523422,0x539dda96],
[0x4024ae,0x246132,0xa31beaa4],
[0x4024e4,0x246132,0xa31beaa4],
[0x40255d,0x246132,0xa31beaa4],
[0x4025aa,0x246132,0xa31beaa4],
[0x402a80,0x523422,0x613a1fc5],
[0x403275,0x523422,0xf2ae646],
[0x403ca0,0x246132,0xd6c07d79],
[0x4066a7,0x246132,0xf5d407d0],
]
 
pe_info_cache_hit={}
#缓存命中
def pe_cache_hit(dll_name,api_hash):
    if dll_name in pe_info_cache_hit:
        if api_hash in pe_info_cache_hit[dll_name]:
            return pe_info_cache_hit[dll_name][api_hash]
    return None
#将已经计算的api值放入缓存中
def add_pe_cache(dll_name,api_hash,api_name):
    if dll_name in pe_info_cache_hit:
        if api_hash in pe_info_cache_hit[dll_name]:
            return
        else:
            pe_info_cache_hit[dll_name][api_hash]=api_name
    else:
        pe_info_cache_hit[dll_name]={api_hash:api_name}
#计算hash      
def hash_api_name(s):
    s=bytearray(s)
    v=0x40
    for i in range(len(s)):
        v=(s[i]- 0x45523F21*v)&0xffffffff
    return v
#通过hash获取api的符号名称
def get_api_symbols(dll_hash,api_hash):
    system_dir = "C:\\Windows\\SysWOW64\\"
    dll_name=dll_name_map[dll_hash]
    path=system_dir+dll_name
    api_name=pe_cache_hit(dll_name,api_hash) #命中缓存,直接返回
    if api_name:
        #print('[+]hit cache')
        return api_name
 
    pe = pefile.PE(path)
    for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
        api_name=exp.name
        func_hash = hash_api_name(api_name)
        add_pe_cache(dll_name,func_hash,api_name.decode('utf-8'))
        if  func_hash == api_hash:
            #print("Find symbols:" + " " + api_name.decode('utf-8'))
            return api_name.decode('utf-8')
 
 
if __name__=='__main__':
    for i in fix_api_addr:
        api_name=get_api_symbols(i[1],i[2])
        print('[0x%x,"%s","%s"],'%(i[0],dll_name_map[i[1]],api_name))
        #print('0x%x,%s'%(i[0],api_name))

得到的输出如下:

1
2
3
4
5
[0x40245e,"kernel32.dll","GetSystemTime"],
[0x4027bc,"kernel32.dll","CloseHandle"],
[0x402803,"kernel32.dll","ExitProcess"],
......
......

修复PE,把混淆的地方替换成call IAT的形式即可,在call之前,必须在导入表添加对应的函数引用:

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
import lief
import struct
 
binary = lief.parse("evil_in.bin")
 
# Disable ASLR
binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE
 
# Disable NX protection
binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.NX_COMPAT
 
api_table={}
for i in fix_map:
        if i[1] not in api_table:
                binary.add_library(i[1]).add_entry(i[2])
                api_table[i[1]]={i[2]:1}
        else:
                if i[2] not in api_table[i[1]]:
                        binary.get_import(i[1]).add_entry(i[2])
                        api_table[i[1]][i[2]]=1
 
 
# Invoke the builder
builder = lief.PE.Builder(binary)
 
# Configure it to rebuild and patch the imports
builder.build_imports(True).patch_imports(True)
 
# Build !
builder.build()
 
add_import_bin="lief_pe32.bin"
# Save the result
builder.write(add_import_bin)
 
binary = lief.parse(add_import_bin)
imports = binary.imports
first=True
iat_fix_map={}
for import_ in imports:
        if first:
                first=False
                continue
 
        print(import_.name)
        entries = import_.entries
        f_value = "  {:<33} 0x{:<14x} 0x{:<14x} 0x{:<16x}"
        for entry in entries:
                print(f_value.format(entry.name, entry.data, entry.iat_address, entry.hint))
                iat_fix_map[entry.name]=entry.iat_address
code=binary.get_section('.text').content
for i in fix_map:
        offset=i[0]-0x400000-0x1000
        iat_addr=iat_fix_map[i[2]]
        #patch 成call IAT形式
        code[offset:offset+7]=list(b'\x90\xff\x15'+struct.pack('I',iat_addr+0x400000))
binary.get_section('.text').content=code
 
add_import_bin="lief_fix.bin"
binary.write(add_import_bin)

修复完成!!!

坑点:

这题有几个坑点:

大量的反虚拟机和反调试

patch过掉即可
图片描述

hook

在我反混淆后,我立刻定位到了解密flag的地方,令我奇怪的是,CALG_SEAL这个算法微软并不支持,这段代码会调用失败。
图片描述
但是我以0x6802作为立即数搜索,问题立刻有了答案,这是一个Hook,实际调用的是6801——RC4算法:
图片描述

flag

把这些坑踩过去之后,分析了相关的算法,我找到了立刻编写了flag的解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import struct
import hexdump
import base64
import binascii
from Crypto.Cipher import ARC4
 
if __name__ == '__main__':
    key=[b'L0ve\x00',b's3cret\x00',b'5Ex\x00',b'g0d\x00']
    rc4_key=b''
    for i in range(4):
 
        crc=zlib.crc32(key[i])&0xffffffff
 
        rc4_key+=struct.pack('>I',crc)
    hexdump.hexdump(rc4_key)
 
 
    rc4=ARC4.new(rc4_key)
    enc=base64.b64decode('MjinAnDf5yv3enf1dikboofkwvlTzD9u6Jqmggy9pNGW6HqJAMX1')
 
    print(rc4.decrypt(enc))

FLAG: n0_mOr3_eXcEpti0n$_p1ea$e@flare-on.com

后话

今年的flareon的题目大量和一些国外经典电影相关,这题也不例外,
'Love', 'Sex', 'Secret' and 'God' 来源于 1995电影《Hackers》,其中说明了这4个单词是当时最常见的密码:

1
2
3
4
5
6
7
8
9
The Plague : Our recent unknown intruder penetrated using the superuser account, giving him access to our whole system.
 
Margo : Precisely what you're paid to prevent.
 
The Plague : Someone didn't bother reading my carefully prepared memo on commonly-used passwords. Now, then, as I so meticulously pointed out, the four most-used passwords are: love, sex, secret, and...
 
Margo : [glares at The Plague]
 
The Plague : god. So, would your holiness care to change her password?

恭喜ID[飞翔的猫咪]获看雪安卓应用安全能力认证高级安全工程师!!

收藏
点赞4
打赏
分享
最新回复 (5)
雪    币: 806
活跃值: 活跃值 (3814)
能力值: ( LV12,RANK:223 )
在线值:
发帖
回帖
粉丝
零加一 活跃值 2 2021-10-28 14:06
2
0
wmsuper!!!
雪    币: 6283
活跃值: 活跃值 (1527)
能力值: ( LV12,RANK:662 )
在线值:
发帖
回帖
粉丝
梦游枪手 活跃值 2021-10-28 14:07
3
0
wmsuper!!!
雪    币: 680
活跃值: 活跃值 (4219)
能力值: ( LV12,RANK:267 )
在线值:
发帖
回帖
粉丝
L0x1c 活跃值 3 2021-10-28 14:08
4
0
wmsuper!!!
雪    币: 3622
活跃值: 活跃值 (1767)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
Dyingchen 活跃值 2021-10-28 14:57
5
0
晴天师傅tql
雪    币: 229
活跃值: 活跃值 (141)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
5keeter 活跃值 2021-11-15 09:31
6
0
wmsuper!!!
游客
登录 | 注册 方可回帖
返回