queenp's blog

Posted Mon 01 May 2017

crackme1 / Magic - Defcon Qualifier CTF 2017 Twofer Edition

I've been taking a substantial break from CTF stuff on account of being pretty busy and hacking enough things for the day job to give me my fill to an extent. I've gotten somewhat better than last year, although a few of the challenges I thought I had a pretty decent understanding of, I just couldn't get the exploit to work for (and one of them required a technique I could recognise but hadn't actually done in practice before).

Got to skill up better for next year on stuff I'm missing I guess.

crackme1 and magic were essentially the same challenge. The first one you solve a crackme, and the second one you automate solving an arbitrary number of similarly formatted crackmes, so my writeup is going to just cover both together.

Figuring out what's what

The crackme1 binary didn't actually work without installing libc-musl. running it you get a prompt like so:

$ ./4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
enter code:
Entering some garbage right here
[0] $

So with that being unenlightening I chucked it in radare2

[0x000007cc]> aaa
[0x000007cc]> pdf
(fcn) entry0 62
  entry0 ();
             ; CALL XREF from 0x000007dd (entry0)
          0x000007cc      4831ed         xor rbp, rbp
          0x000007cf      4889e7         mov rdi, rsp
          0x000007d2      488d354f1620.  lea rsi, 0x00201e28         ; 0x201e28 ; section.DYNAMIC ; section.DYNAMIC
          0x000007d9      4883e4f0       and rsp, 0xfffffffffffffff0
          0x000007dd      e800000000     call 0x7e2
             ; CALL XREF from 0x000007dd (entry0)
          0x000007e2      4883ec08       sub rsp, 8
          0x000007e6      488d5708       lea rdx, [rdi + 8]          ; 0x8 ; char ** ubp_av
          0x000007ea      488b37         mov rsi, qword [rdi]        ; int argc
          0x000007ed      4c8d05cd0600.  lea r8, sym._fini           ; 0xec1 ; "P.....X.enter code:" @ 0xec1 ; func fini
          0x000007f4      488b0dbd1720.  mov rcx, qword sym._init    ; [0x201fb8:8]=0x6f8 sym._init ; func init
          0x000007fb      4531c9         xor r9d, r9d                ; func rtld_fini
          ; Looky here
          0x000007fe      488d3d6bffff.  lea rdi, 0x00000770         ; 0x770 ; section..text ; "S.Q" @ 0x770 ; func main
          0x00000805      e856ffffff     call sym.imp.__libc_start_main; int __libc_start_main(func main, int argc, char **ubp_av, func init, func fini, func rtld_fini, void *stack_end)

There is no designated main function, but there's a literal pointer 0x770 passed to libc_start_main, so let's follow that yellow brick road.

;-- section_end..plt.got:
;-- section..text:
; DATA XREF from 0x000007fe (entry0)
  0x00000770      53             push rbx                    ; section 9 va=0x00000770 pa=0x00000770 sz=1873 vsz=1873 rwx=--r-x .text
  0x00000771      be51000000     mov esi, 0x51               ; 'Q'
  0x00000776      bf01000000     mov edi, 1
  0x0000077b      e8c8ffffff     call sym.imp.calloc         ;[1]; void *calloc(size_t nmeb, size_t size)
  0x00000780      488d3d420700.  lea rdi, str.enter_code:    ; 0xec9 ; section..rodata ; "enter code:" @ 0xec9
  0x00000787      4889c3         mov rbx, rax
  0x0000078a      e8a1ffffff     call sym.imp.puts           ;[2]; int puts(const char *s)
  0x0000078f      488b3d8a1820.  mov rdi, qword obj.stdout   ; [0x202020:8]=0x3830363130322031 ; "1 20160822" @ 0x202020
  0x00000796      e8a5ffffff     call sym.imp.fflush         ;[3]; int fflush(FILE *stream)
  0x0000079b      488b15861820.  mov rdx, qword obj.stdin    ; [0x202028:8]=0x7368732e00003232 ; "22" @ 0x202028
  0x000007a2      be50000000     mov esi, 0x50               ; 'P'
  0x000007a7      4889df         mov rdi, rbx
  0x000007aa      e879ffffff     call sym.imp.fgets          ;[4]; char *fgets(char *s, int size, FILE *stream)
  0x000007af      4889df         mov rdi, rbx
  0x000007b2      e8b5040000     call fcn.00000c6c           ;[5]
  0x000007b7      488d3d170700.  lea rdi, str.sum_is__ld_n   ; 0xed5 ; "sum is %ld." @ 0xed5
  0x000007be      4889c6         mov rsi, rax
  0x000007c1      31c0           xor eax, eax
  0x000007c3      e858ffffff     call sym.imp.printf         ;[6]; int printf(const char *format)
  0x000007c8      31c0           xor eax, eax
  0x000007ca      5b             pop rbx
  0x000007cb      c3             ret

This is pretty much self explanatory thanks to being mostly calls to libc, and we can see that the interesting stuff is probably happening in fcn.00000c6c

As you can see below, this function does a running checksum of the results of a series of functions which each operate on one character of input text, and are all conveniently called in address order. If the checksum fails, the program ejects prematurely.

Otherwise, the function returns the expected final value of the checksum.

(fcn) fcn.00000c6c()
  0x00000c6c      55             push rbp
  0x00000c6d      53             push rbx
  0x00000c6e      4889fd         mov rbp, rdi
  0x00000c71      4883ec08       sub rsp, 8
  0x00000c75      480fbe3f       movsx rdi, byte [rdi]
  0x00000c79      e8bdfcffff     call fcn.0000093b
  0x00000c7e      480fbe7d01     movsx rdi, byte [arg_1h]    ; [0x1:1]=69
  0x00000c83      48c1f803       sar rax, 3
  0x00000c87      4889c3         mov rbx, rax
  0x00000c8a      e8c6fcffff     call fcn.00000955
  0x00000c8f      480fbe7d02     movsx rdi, byte [arg_2h]    ; [0x2:1]=76 ; LEA rcx ; "LF..." @ 0x2
  0x00000c94      4801c3         add rbx, rax                ; '#'
  0x00000c97      48c1fb03       sar rbx, 3
  0x00000c9b      e8d1fcffff     call fcn.00000971
  0x00000ca0      480fbe7d03     movsx rdi, byte [arg_3h]    ; [0x3:1]=70
  0x00000ca5      4801c3         add rbx, rax                ; '#'
  0x00000ca8      48c1fb03       sar rbx, 3
  0x00000cac      e8dafcffff     call fcn.0000098b
  ; ... snipped
  0x00000e6b      480fbe7d1e     movsx rdi, byte [arg_1eh]   ; [0x1e:1]=0
  0x00000e70      4801c3         add rbx, rax                ; '#'
  0x00000e73      48c1fb03       sar rbx, 3
  0x00000e77      e8d6fdffff     call fcn.00000c52
  0x00000e7c      4801c3         add rbx, rax                ; '#'
  0x00000e7f      48c1fb03       sar rbx, 3
  0x00000e83      4883fb13       cmp rbx, 0x13
  0x00000e87      740a           je 0xe93      ;<--- Checksum success condition
  0x00000e89      bffa000000     mov edi, 0xfa               ; " " @ 0xfa ; int status
  0x00000e8e      e8c5f8ffff     call sym.imp.exit          ; void exit(int status)
  0x00000e93      5a             pop rdx
  0x00000e94      b813000000     mov eax, 0x13
  0x00000e99      5b             pop rbx
  0x00000e9a      5d             pop rbp
  0x00000e9b      c3             ret

The functions each do simple byte comparisons and it's pretty obvious they're spelling out words character by character.

(fcn) fcn.0000093b 26
  fcn.0000093b ();
  ; CALL XREF from 0x00000c79 (fcn.00000c6c)
  0x0000093b      4883ff79       cmp rdi, 0x79               ; 'y' ; 'y'
  0x0000093f      740e           je 0x94f                    ;[1]
  0x00000941      4883ec08       sub rsp, 8
  0x00000945      bf01000000     mov edi, 1                  ; int status
  0x0000094a      e809feffff     call sym.imp.exit           ;[2]; void exit(int status)
  0x0000094f      b8a7000000     mov eax, 0xa7               ; section_end..shstrtab
  0x00000954      c3             ret
(fcn) fcn.00000955 28
  fcn.00000955 ();
    ; CALL XREF from 0x00000c8a (fcn.00000c6c)
  0x00000955      4883ff65       cmp rdi, 0x65               ; 'e' ; 'e'
  0x00000959      740e           je 0x969                    ;[3]
  0x0000095b      4883ec08       sub rsp, 8
  0x0000095f      bf02000000     mov edi, 2                  ; int status
  0x00000964      e8effdffff     call sym.imp.exit           ;[2]; void exit(int status)
  0x00000969      48c7c09bffff.  mov rax, -0x65
  0x00000970      c3             ret
(fcn) fcn.00000971 26
  fcn.00000971 ();
    ; CALL XREF from 0x00000c9b (fcn.00000c6c)
  0x00000971      4883ff73       cmp rdi, 0x73               ; 's' ; 's'
  0x00000975      740e           je 0x985                    ;[4]
  0x00000977      4883ec08       sub rsp, 8
  0x0000097b      bf03000000     mov edi, 3                  ; int status
  0x00000980      e8d3fdffff     call sym.imp.exit           ;[2]; void exit(int status)
  0x00000985      b8a0000000     mov eax, 0xa0
  0x0000098a      c3             ret

So the solution is to read these, enter them in to the end point and grab the flag.

For the actual endpoint you needed to submit the results in base64, i.e.

$ echo "yes and his hands shook with ex" | base64 | nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001
send your solution as base64, followed by a newline
The flag is: important videos best playlist Wigeekuk8

Now this time at scale

Having started the challenge at 1am my time I was a little bit worse for wear. Imagine my surprise when I come back the next evening and the magic challenge had been unlocked and was essentially identical.

The one hitch with it was that there were 200 binaries in the bz2 file. No problem! I appreciated this because on some level it was a gift (crackme1 is super easy just reading the binary but knowing the shape of the obfuscated strcmp makes it quicker to automate), and also a valuable lesson for next time of scripting solutions to things as I go so I can tweak and repeat as necessary if I see similar again.

Time to break out the r2pipe script and repeat everything I did for the original.

import r2pipe
import glob
import json
from pwnlib.tubes import remote

conn = remote.remote('cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me',12001)

def get_solution(fn):
    print fn
    r2 = r2pipe.open(fn)
    # Analyse everything for good measure
    # It turned out looking at a couple of the files the location of the main checker function
    # was held at 0x7b2, so I was lazy and just grabbed the instruction at that location
    checker = json.loads(r2.cmd('pdj 1@0x7b2'))[0]['opcode']
    if 'call' in checker:
        checker = checker.split(' ')[1]
        print "checker value was wrong"
        print json.dumps(checker)
    solution = ""

       # Iterate over instructions in the main checker function finding calls
       for op in json.loads(r2.cmd('pdfj @'+checker))['ops']:
           lastjump = None
           if(op['type'] == 'call' and 'sym.imp.exit' not in r2.cmd('pd 1@'+hex(op['offset']))):

               # for each called function follow the jump and grab the character literal from the comparison
               # (in r2pipe json this is the 'ptr' field of the operation)
               jump = op['jump']
               char_chk_fn = chr(json.loads(r2.cmd('pdfj@'+hex(jump)))['ops'][0]['ptr'])
               solution += char_chk_fn
       return solution

   # skip first line from the network service
   print conn.recvline()
   while True:
       binary_file = conn.readline().rstrip('\n')
       print binary_file
       sol = get_solution(binary_file)
       print sol


$ python magic.py
send your solution as base64, followed by a newline

, was cocking an eyebrow at her an
es of the work -- not derivati
d, thinking I'd outrun Lil and Debra and all
t. I have a backup in 9 9984
I'm a sucker for musicians. We brought
y, reason trickled back into m
 "I'm goin > 0x00000129 9984
l -- I had no way of answerin
he _best_ work ove00129 9984
 a baby. # Of course, I got caught. I do
The flag is: a color map of the sun sokemsUbif
The flag is: a color map of the sun sokemsUbif

Credit to Tobal Jackson for the r2 screen highlighter/lexer I adapted for this post (my fork here):

Category: writeups
Tags: crackme defcon