NuitDuhack - Matriochka+Invest

We finish rank 8 in Nuit du hack, and we qualified for the finals in Paris - Disneyland ^^/. Here are some quick writeups.

Matriochka 1,2,3,4

This was a series of reverse engineering challenges, each binary if solved gives the next one in base64.

stage 1

Password in strings. Run and gives base64 stage2 binary

[~/nuit/matryochka]$ strings stage1.bin | grep Much
Much_secure__So_safe__Wow  
[~/nuit/matryochka]$ ./stage1.bin "Much_secure__So_safe__Wow" 2>&1|base64 -d >stage2.bin

stage 2

At address 0x4076b we have the password checking code. We translate it to python and solve with SAT/z3.

[0x0040076b]> pd 10
│           |
│           0x0040076b      c745ec010000.  mov dword [rbp - good], 1
│           0x00400772      488b45d0       mov rax, qword [rbp - local_in]
│           0x00400776      4883c008       add rax, 8
│           0x0040077a      488b00         mov rax, qword [rax]
│           0x0040077d      0fb600         movzx eax, byte [rax]
│           0x00400780      3c50           cmp al, 0x50                ; 'P'
│       ┌─< 0x00400782      7407           je 0x40078b
│       │   |
│       │   0x00400784      c745ec000000.  mov dword [rbp - good], 0
│       │   |
│       │   ; JMP XREF from 0x00400782 (main)
│       └─> 0x0040078b      488b45d0       mov rax, qword [rbp - local_in]
│           0x0040078f      4883c008       add rax, 8
.....etc
import z3

def main():  
    s = z3.Solver()
    argv = [z3.BitVec("argv_%d" % i, 16) for i in range(11)]
    s.add(argv[0] == 80)
    s.add(2 * argv[3] == 200)
    s.add(argv[0] + 16 == argv[6]-16)
    s.add(argv[5] == (9 * 11) - 4) # check?
    s.add(argv[1] == argv[7])
    s.add(argv[1] == argv[10])
    s.add(argv[1] - 17 == argv[0])
    s.add(argv[3] == argv[9])
    s.add(argv[4] == ord("i"))
    s.add(argv[2] - argv[1] == 13)
    s.add(argv[8] - argv[7] == 13)
    if s.check() == z3.sat:
        print "Satisifed"
        r = ""
        for x in argv:
            r += chr(int(str(s.model()[x])))
        print r
    else:
        print "Not satisifed"

if __name__ == "__main__":  
    main()

Gives us : Pandi_panda and stage3

stage 3

It create SIGSEGV handler and SIGFPE handler, the signals are used to move to the password checking parts, each signal raised causes the next handler to run. If all checks are passed a SIGFPE is raised and calls the win function. Same as with stage2 we translate logic to python and solve with SAT/z3.

import z3

def main():  
    s = z3.Solver()
    b = [z3.BitVec("b_%d" % i, 32) for i in range(21)]
    for i in range(0, len(b)):
        s.add(b[i] != 0)
        s.add(b[i] > 0, b[i] < 127)
    ns = [68, 105, 100, 95, 121, 111, 117, 95,
            108, 105, 107, 101, 95, 115, 105, 103, 110, 97,
            108, 115, 63]
    assert len(b) == len(ns)
    for i in range(0, len(b)):
        s.add(z3.And(
            (b[i]*1000)/ns[i] <= 1000,
            (b[i]*1000)/ns[i] > 999))
    if s.check() == z3.sat:
        r = ""
        for i in range(0, len(b)):
            r += chr(int(str(s.model()[b[i]])))
        print r
    else: print ":("

if __name__ == "__main__":  
    main()

Gives us : Did_you_like_signals?, and binary for last stage

stage 4

An MBR boot sector is given.

[~/nuit/matryochka]$ file step4.base
step4.base: DOS/MBR boot sector  

We load the binary in r2 and check for strings comparison, xoring, and some instructions.

[~/nuit/matryochka]$ r2 -b 32 -AAA -e io.va=true ./step4.base
 -- I script in C, because I can.
[0x00000000]> /c cmp cl
0x0000060f   # 3: cmp cl, 0  
0x000006ab   # 3: cmp cl, 0x47  
0x000006b3   # 3: cmp cl, 0x6f  
0x000006bb   # 3: cmp cl, 0x6f  
0x000006e6   # 3: cmp cl, 0x64  
0x000006ee   # 3: cmp cl, 0x5f  
0x000006f6   # 3: cmp cl, 0x47  
0x0000070e   # 3: cmp cl, 0x61  
0x00000716   # 3: cmp cl, 0x6d  
0x0000071e   # 3: cmp cl, 0x65  
0x00000749   # 3: cmp cl, 0x5f  
0x00000751   # 3: cmp cl, 0x21  
0x00000759   # 3: cmp cl, 0  
0x0000076e   # 3: cmp cl, 0x28  
0x00000778   # 3: cmp cl, 0x68  
0x00000780   # 3: cmp cl, 0xdf  
0x00000788   # 3: cmp cl, 0x2c  
0x000008a6   # 3: cmp cl, 0x37  
0x000008b0   # 3: cmp cl, 0x5b  
0x000008ba   # 3: cmp cl, 0x31  
0x000008c4   # 3: cmp cl, 0xb9  
0x000008f6   # 3: cmp cl, 0x77  
0x00000900   # 3: cmp cl, 0x90  
0x0000090a   # 3: cmp cl, 0xd4  

Giving us Good_Game_!, we run the binary in qemu
[~/nuit/matryochka]$ qemu-system-x86_64 -S -s -hda ./step4.base -curses attach gdb, continue, and this actually wins, but is not the Magic word. We find a bunch of places in the binary that do comparisons but with non-printable characters, for example

  • 00000765-0000078B
  • 0000089A-000008C7
  • 000008EA-0000090D
  • ..etc

each one of them looks like :

╒ (fcn) fcn.00000765 47
│           0x00000765      fa             cli
│           0x00000766      8b1dc7190000   mov ebx, dword [0x19c7]     ; [0x19c7:4]=-1
│           0x0000076c      8a0b           mov cl, byte [ebx]
│           0x0000076e      80f928         cmp cl, 0x28                ; '('
│       ┌─< 0x00000771      751c           jne 0x78f
│       │   |
│       │   0x00000773      83c307         add ebx, 7
│       │   0x00000776      8a0b           mov cl, byte [ebx]
│       │   0x00000778      80f968         cmp cl, 0x68                ; 'h'
│      ┌──< 0x0000077b      7512           jne 0x78f
│      ││   |
│      ││   0x0000077d      43             inc ebx
│      ││   0x0000077e      8a0b           mov cl, byte [ebx]
│      ││   0x00000780      80f9df         cmp cl, 0xdf
│     ┌───< 0x00000783      750a           jne 0x78f
│     │││   |
│     │││   0x00000785      43             inc ebx
│     │││   0x00000786      8a0b           mov cl, byte [ebx]
│     │││   0x00000788      80f92c         cmp cl, 0x2c                ; ','
│    ┌────< 0x0000078b      7502           jne 0x78f
│    ││││   |
│    ││││   0x0000078d      fb             sti
│    ││││   0x0000078e      cf             iretd
│    ││││   ; --------------------------------------
│    ││││   |
│    └└└└─> 0x0000078f      fa             cli
│           0x00000790      cd1f           int 0x1f
│           0x00000792      fb             sti
╘           0x00000793      cf             iretd
╘           ; --------------------------------------

Where [0x19c7] is our input, we use the index ebx, and the comparison, to recreate the required memory to win. We also find three calls to an xor function.

[0x00000000]> pd 1500 | grep fcn.00000667 -B 4
        │   0x00000665      5f             pop edi
        │   0x00000666      c3             ret
╘       │   ; --------------------------------------
│       │   |
╒ (fcn) fcn.00000667 32
--
        │   0x0000079b      bffc190000     mov edi, 0x19fc
        │   0x000007a0      b829000000     mov eax, 0x29
        │   0x000007a5      becb190000     mov esi, 0x19cb
        │   0x000007aa      bb10000000     mov ebx, 0x10
        │   0x000007af      e8b3feffff     call fcn.00000667
--
        │   ;-- hit0_11:
        │   0x000007db  ~   b832000000     mov eax, 0x32
        │   0x000007e0      becb190000     mov esi, 0x19cb
        │   0x000007e5      bb10000000     mov ebx, 0x10
        │   0x000007ea      e878feffff     call fcn.00000667
--
        │   0x0000087e      8b3dc7190000   mov edi, dword [0x19c7]
        │   0x00000884      b818000000     mov eax, 0x18
        │   0x00000889      becb190000     mov esi, 0x19cb
        │   0x0000088e      bb10000000     mov ebx, 0x10
        │   0x00000893      e8cffdffff     call fcn.00000667

What caught my attention here is the third call which xors 0x18 bytes from our input with 0x18 bytes from 0x19cb. Now we know what the key should be. we xor that with the memory mentioned above and we get the key.

from pwn import *  
def main():  
  mem_edi = [0x00 for x in range(len("Good_Game_!"))] # input mem
  ebx = 0;  mem_edi[ebx] = 0x28
  ebx += 7; mem_edi[ebx] = 0x68
  ebx += 1; mem_edi[ebx] = 0xdf
  ebx += 1; mem_edi[ebx] = 0x2c
  ebx = 1;  mem_edi[ebx] = 0x37
  ebx += 2; mem_edi[ebx] = 0x5b
  ebx += 1; mem_edi[ebx] = 0x31
  ebx += 6; mem_edi[ebx] = 0xb9
  ebx = 2;  mem_edi[ebx] = 0x77
  ebx += 3; mem_edi[ebx] = 0x90
  ebx += 1; mem_edi[ebx] = 0xd4
  # the rest we get from memory
  mem_edi.append(0x0e)
  mem_edi.append(0xad)
  mem_edi.append(0x4a)
  mem_edi.append(0xb9)
  mem_edi.append(0x93)
  mem_edi.append(0xad)
  mem_edi.append(0x3d)
  mem_edi.append(0x16)
  mem_edi.append(0x33)
  mem_edi.append(0x14)
  mem_edi.append(0xde)
  mem_edi.append(0x45)
  mem_edi.append(0x4a)
  mem_edi.append(0x12)
  mem_edi.append(0x78)
  mem_edi.append(0xe8)
  mem_edi.append(0xa0)
  mem_edi.append(0xff)
  mem_edi.append(0x00)
  mem_edi.append(0xb9)
  mem_edi.append(0x01)
  mem_edi.append(0xad)
  mem_edi.append(0x56)
  mem_edi.append(0x11)
  mem_edi.append(0x93)
  mem_edi.append(0x00)
  mem_edi.append(0xe4)
  mem_edi.append(0x00)
  mem_edi.append(0x00)
  mem_edi.append(0x3a)
  mem_edi.append(0x00)
  mem_edi.append(0x00)
  mem_edi.append(0xf3)
  mem_edi.append(0x00)
  mem_edi.append(0x00)
  mem_edi.append(0x44)
  mem_edi.append(0x91)
  mem_edi.append(0x83)
  mem_edi.append(0x00)
  mem_edi.append(0x75)
  mem_edi.append(0xaf)
  mem_edi.append(0x00)
  mem_edi.append(0x41)
  mem_edi.append(0x68)
  mem_edi.append(0x20)
  mem_edi.append(0x61)
  mem_edi.append(0x68)
  mem_edi.append(0x20)
  mem_esi = [] # bytes from 0x19cb-(0x19cb+0x18)
  mem_esi.append(0x6c)
  mem_esi.append(0x53)
  mem_esi.append(0x05)
  mem_esi.append(0x6a)
  mem_esi.append(0x5c)
  mem_esi.append(0xfc)
  mem_esi.append(0xfb)
  mem_esi.append(0x0e)
  mem_esi.append(0xad)
  mem_esi.append(0x4a)
  mem_esi.append(0xb9)
  mem_esi.append(0x93)
  mem_esi.append(0xad)
  mem_esi.append(0x3d)
  mem_esi.append(0x16)
  mem_esi.append(0x33)
  mem_esi.append(0x14)
  mem_esi.append(0xde)
  mem_esi.append(0x45)
  mem_esi.append(0x4a)
  mem_esi.append(0x12)
  mem_esi.append(0x78)
  mem_esi.append(0xe8)
  mem_esi.append(0xa0)
  mem_esi.append(0xff)
  mem_esi.append(0x00)
  mem_esi.append(0xb9)
  mem_esi.append(0x01)
  mem_esi.append(0xad)
  mem_esi.append(0x56)
  mem_esi.append(0x11)
  mem_esi.append(0x93)
  mem_esi.append(0x00)
  mem_esi.append(0xe4)
  mem_esi.append(0x00)
  mem_esi.append(0x00)
  mem_esi.append(0x3a)
  mem_esi.append(0x00)
  mem_esi.append(0x00)
  mem_esi.append(0xf3)
  mem_esi.append(0x00)
  print xor(mem_edi, mem_esi)[0:12-2]

if __name__ == "__main__":  
    main()

Gives us : Ddr1ml/frf which is the magic word.

Invest

A pcap-ng file is given, we dump http objects from wireshark. We get a bunch of encryptXX splitted file we unsplit them cat encrypt* > encrypt.bin which is an encrypted file.

[~/nuit/invest/dump]$ binwalk encrypt.bin;(strings encrypt.bin|head -n 1)

DECIMAL       HEXADECIMAL     DESCRIPTION  
--------------------------------------------------------------------------------
0             0x0             OpenSSL encryption, salted, salt: 0x7DD883F026435AB8

Salted__}  

next we have this image

and this key from key.txt

010001110101111001100011011011100100100100111001010111100100011101000111001110010100011100111001010001110011100101000111001110010101111001100011011011100100100101101110010010010011100100110101010111100110001100111001001101010110111001001001011011100100100101000111010111100011100100110101011011100100100101011110011000110100011101011110001110010011010101011110011000110101111001100011010111100110001101000111010111100101111001100011011011100100100101000111010111100011100100110101010001110101111001101110010010010101111001100011010111100110001101101110010010010101111001100011010111100110001100111001001101010100011101011110010111100110001101011110011000110101111001100011010001110101111001000111010111100101111001100011011011100100100101101110010010010101111001100011  

we translate the simple circuit to python and decode the key.

#!/usr/bin/env python2

import sys

def main():  
    key = open("key.txt").read()
    key = [key[i:i+8] for i in range(0, len(key), 8)]
    key = [[ord(y)-48 for y  in x] for x in key]
    for i in range(8*12):
        c = calculate(key[i])
        assert c == 0 or c == 1
        sys.stdout.write(str(c))

def calculate(bs):  
    assert len(bs) == 8
    b0 = bs[0]
    b1 = bs[1]
    b2 = bs[2]
    b3 = bs[3]
    b4 = bs[4]
    b5 = bs[5]
    b6 = bs[6]
    b7 = bs[7]
    # level1
    l0 = b0 & (not b2)
    l1 = (not b2) & (not b1)
    l2 = b0 & b1
    l3 = b6 ^ b5
    l4 = (not b7) ^ (not b1)
    # level2...
    g0 = l0 & (not b3)
    g1 = l1 & (not b3)
    g2 = l2 & (not b3)
    g3 = (not b5) & b2
    g4 = b2 & l4
    # ..etc
    k0 = g0 & (not b4)
    k1 = g1 & (not b4)
    k2 = g2 & (not b4)
    k3 = g3 & l3
    f0 = k0 | k1
    f1 = k2 | k3
    z0 = f1 | g4
    L  = f0 | z0
    return L

def Not(b):  
    if b == 0: return 1
    else: return 0

if __name__ == "__main__":  
    main()

We get the following

[~/nuit/invest/dump]$ python2 decoder.py          
001101000101010101101011011110100011100100110101010001100011001001011001011100010101000001101001  

which is a 4Ukz95F2YqPi then we try various openssl ciphers to decrypt the file.

[~/nuit/invest/dump]$ for i in `cat m`; do openssl enc -d $i -in encrypt.bin -k "4Ukz95F2YqPi" > out.$i; done 2>/dev/null ; file out.*|egrep -ve "data|empty"
out.-aes256:                   Microsoft Word 2007+  
out.-aes-256-cbc:              Microsoft Word 2007+  
out.-aes256.doc:               Microsoft Word 2007+  
out.-desx:                     8086 relocatable (Microsoft)  
out.-desx-cbc:                 8086 relocatable (Microsoft)  

The file out.-aes256 is the correct one with the flag behind darth vader's image.

}}