[WRITE-UP] FRA CTF
The binary appears to be a “DOS/MBR boot sector” with a valid signature according to classical layout, see figure “Structure of a classical generic MBR”1.
00000000: bc00 9c31 c0b0 03cd 1031 d2b9 0d00 b40e ...1.....1......
00000010: b3c8 be64 7cac 30d8 cd10 fecb e2f7 b400 ...d|.0.........
00000020: cd16 aab4 0e3c 0d75 2fcd 10b0 0acd 10be .....<.u/.......
00000030: 717c b91d 00ac cd10 e2fb be8e 7cb9 0900 q|..........|...
00000040: c1c2 0781 c237 13ad 31d0 88e3 b40e cd10 .....7..1.......
00000050: 88d8 cd10 e2ea ebfe 00c2 86d6 b02a cd10 .............*..
00000060: ebbc ebfe 89a4 a5a0 b7b0 e2a2 afdb db87 ................
00000070: 9c53 6b69 636b 6120 6c6f 6573 6e69 6e67 .Skicka loesning
00000080: 206f 6368 2043 5620 7469 6c6c 3a20 29ba och CV till: ).
00000090: 43d7 70cc 666f aa64 b1b5 d786 5042 9e4e C.p.fo.d....PB.N
...
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
The MBRs use Intel 8086 architecture with the x86-16 instruction set, where
the MBR boot sector code expects to be loaded at physical address 0000:7c001.
I began disassemble with radare2 disassembler: r2 -AA -b16 -m 0x7c00 fra and
executed it using qemu-system-i386 -fda fra.
When executed, the BIOS asks the user for an access key that it uses to
derivate a “key” used for the deobfuscation process of the actual e-mail.
I noticed several BIOS interrupt calls int 0x10 with teletype output
(ah = 0x0e)2 and one int 0x16 for obtaining keystrokes from the
keyboard3.
0000:7c25 checks for carriage return in al after the int 0x16 interrupt.
If not, goto 0000:7c58 to truncate (“hash”) the input in al with dx, and
store in register dx.
The dx register is then used as the input key for deobfuscation, starting at
address 0000:7c40. The deobfuscation part overlays the input with a 18-byte
found at address 0000:7c8e.
.--------------------------------------------------------------------------.
| ; CODE XREF from fcn.00007c00 @ +0x6a |
| 0000:7c0e b40e mov ah, 0xe |
| 0000:7c10 b3c8 mov bl, 0xc8 |
| 0000:7c12 be647c mov si, 0x7c64 |
| ; CODE XREF from fcn.00007c00 @ 0x7c1c |
| .-> 0000:7c15 ac lodsb al, byte [si] |
| : 0000:7c16 30d8 xor al, bl |
| : 0000:7c18 cd10 int 0x10 |
| : 0000:7c1a fecb dec bl |
| `=< 0000:7c1c e2f7 loop 0x7c15 |
| ; CODE XREF from fcn.00007c00 @ 0x7c60 |
| .-> 0000:7c1e b400 mov ah, 0 |
| : 0000:7c20 cd16 int 0x16 |
| : 0000:7c22 aa stosb byte es:[di], al |
| : 0000:7c23 b40e mov ah, 0xe |
| : 0000:7c25 3c0d cmp al, 0xd |
| ,==< 0000:7c27 752f jne 0x7c58 |
| |: 0000:7c29 cd10 int 0x10 |
| |: 0000:7c2b b00a mov al, 0xa |
| |: 0000:7c2d cd10 int 0x10 |
| |: 0000:7c2f be717c mov si, 0x7c71 |
| |: 0000:7c32 b91d00 mov cx, 0x1d |
| |: ; CODE XREF from fcn.00007c00 @ 0x7c38 |
| .---> 0000:7c35 ac lodsb al, byte [si] ; 'Skicka loe..'|
| :|: 0000:7c36 cd10 int 0x10 |
| `===< 0000:7c38 e2fb loop 0x7c35 |
| |: 0000:7c3a be8e7c mov si, 0x7c8e ; Overlay |
| |: 0000:7c3d b90900 mov cx, 9 |
| |: ; CODE XREF from fcn.00007c00 @ 0x7c54 |
| .---> 0000:7c40 c1c207 rol dx, 7 |
| :|: 0000:7c43 81c23713 add dx, 0x1337 |
| :|: 0000:7c47 ad lodsw ax, word [si] |
| :|: 0000:7c48 31d0 xor ax, dx |
| :|: 0000:7c4a 88e3 mov bl, ah |
| :|: 0000:7c4c b40e mov ah, 0xe |
| :|: 0000:7c4e cd10 int 0x10 |
| :|: 0000:7c50 88d8 mov al, bl |
| :|: 0000:7c52 cd10 int 0x10 |
| `===< 0000:7c54 e2ea loop 0x7c40 |
| @==-> 0000:7c56 ebfe jmp 0x7c56 |
| :: ; CODE XREF from fcn.00007c00 @ 0x7c27 |
| :: 0000:7c58 00c2 add dl, al |
| :: 0000:7c5a 86d6 xchg dh, dl |
| :: 0000:7c5c b02a mov al, 0x2a ; '*' |
| :: 0000:7c5e cd10 int 0x10 |
| |: ; CODE XREF from fcn.00007c00 @ +0x92 |
| `==< 0000:7c60 ebbc jmp 0x7c1e |
| @=-> 0000:7c62 ebfe jmp 0x7c62 |
| : ; DATA XREF from fcn.00007c00 @ 0x7c12 |
| : 0000:7c64 89a4a5a0 mov word [si - 0x5f5b], sp |
| : 0000:7c68 b7b0 mov bh, 0xb0 |
| `=< 0000:7c6a e2a2 loop 0x7c0e |
`--------------------------------------------------------------------------´
Due to this I can assume that the e-mail is not longer than 18 bytes.
[0000:7c00]> px 18 @ 0x7c8e
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0000:7c8e 29ba 43d7 70cc 666f aa64 b1b5 d786 5042 ).C.p.fo.d....PB
0000:7c9e 9e4e .N
To find the required 16-bit value in dx for the deobfuscation part to output
a “fra.se” e-mail, I implemented the same behavior in python with the overlay
from 0x7c8e.
_0x7c8e = unpack('H'*9, unhexlify("29ba43d770cc666faa64b1b5d78650429e4e"))
def deobfuscate_i(dx, ax):
post_0x7c40_dx = rol(dx, 7, 16) # 0000:7c40 rol dx,7
post_0x7c43_dx = post_0x7c40_dx + 0x1337 # 0000:7c43 add dx,1337
post_0x7c48_ax = post_0x7c43_dx ^ ax # 0000:7c48 xor ax,dx
ah = chr((post_0x7c48_ax & 0xff00) >> 8) # 0000:7c4a mv bl,ah
al = chr(post_0x7c48_ax & 0xff) # 0000:7c4e int 0x10
return post_0x7c43_dx, ah, al
def deobfuscate(dx, byte_array, s = str()):
for byte in byte_array:
dx, ah, al = deobfuscate_i(dx, byte)
s += "%c%c" % (al, ah)
return s
def solve_obfuscator():
for dx in range(0xffff):
s = deobfuscate(dx, _0x7c8e)
if "@fra.se" in s:
return dx, s
I verified my implementation by debugging the binary with
qemu-system-i386 -fda fra -s and gdb.
By searching for the string "@fra.se", I found that the register dx needs
to be 0x1984 to output the e-mail "jobbahososs@fra.se".
To find valid 5-byte printable input values I implemented the truncation function in python, and in parallel brute-forced possible alpha numeric values.
$ ./fra.py
Solved! dx = 0x1984 -> jobbahososs@fra.se
b'30306f547a'[00oTz]
b'3030705479'[00pTy]
b'3030715478'[00qTx]
b'3030725477'[00rTw]
b'3030735476'[00sTv]
b'3030745475'[00tTu]
b'3030755474'[00uTt]
b'3030765473'[00vTs]
b'3030775472'[00wTr]
b'3030785471'[00xTq]
b'3030795470'[00yTp]
b'30307a546f'[00zTo]
b'30316f537a'[01oSz]
...
See python code below for complete solution.
#!/usr/bin/env python3
from itertools import chain, product
from multiprocessing import Process,Queue,cpu_count
from binascii import unhexlify,hexlify
from struct import unpack
MIN_LENGTH = 5
MAX_LENGTH = 5
# - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
# 0000:7c8e 29ba 43d7 70cc 666f aa64 b1b5 d786 5042 ).C.p.fo.d....PB
# 0000:7c9e 9e4e .N
_0x7c8e = unpack('H'*9, unhexlify("29ba43d770cc666faa64b1b5d78650429e4e"))
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
hash_i = lambda dx, al: \
((((dx & 0xff00) | (dx + al) & 0xff) & 0x00ff) << 8) | \
((((dx & 0xff00) | (dx + al) & 0xff) & 0xff00) >> 8)
def deobfuscate_i(dx, ax):
post_0x7c40_dx = rol(dx, 7, 16) # 0000:7c40 rol dx,7
post_0x7c43_dx = post_0x7c40_dx + 0x1337 # 0000:7c43 add dx,1337
post_0x7c48_ax = post_0x7c43_dx ^ ax # 0000:7c48 xor ax,dx
ah = chr((post_0x7c48_ax & 0xff00) >> 8) # 0000:7c4a mv bl,ah
al = chr(post_0x7c48_ax & 0xff)
return post_0x7c43_dx, ah, al
def deobfuscate(dx, b: bytes):
s = str()
for byte in b:
dx, ah, al = deobfuscate_i(dx, byte)
s += "%c%c" % (al, ah) # 0000:7c4e int 0x10 (putchar)
return s
def solve_obfuscator():
for dx in range(0xffff):
s = deobfuscate(dx, _0x7c8e)
if "@fra.se" in s:
return dx, s
def hash_bytes(b: bytes, dx = 0):
for byte in unpack('B' * len(b), b):
dx = hash_i(dx, byte)
return dx
def hash_worker(dx, queue):
while True:
t = bytes(ord(c) for c in list(queue.get()))
if hash_bytes(t) == dx:
print('%s[%s]' % (hexlify(t), ''.join(chr(c) for c in t)))
def test():
assert(hash_bytes(b'z3MQR') == 0x1984)
assert(deobfuscate(0x1984, _0x7c8e) == "jobbahososs@fra.se")
if __name__ == "__main__":
test()
dx, s = solve_obfuscator()
print("Solved! Register dx = 0x%04x -> %s" % (dx, s))
pattern = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
generator = chain.from_iterable(product(pattern, repeat=i)
for i in range(MIN_LENGTH, MAX_LENGTH + 1))
try:
queue = Queue()
processes = []
for i in range(cpu_count()):
p = Process(target=hash_worker, args=(dx, queue))
p.start()
processes.append(p)
for test in generator:
queue.put(test)
queue.join()
except (KeyboardInterrupt, SystemExit):
pass
One of many solutions is 00oTz.
