Tokyo Westerns CTF 3rd 2017 REV 113

4 minute read

Challenge: lets_go

Reversing

A quick file

$ file ./lets_go
lets_go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

When trying to run it, it gives us the usages message.

$ ./lets_go
Usage: ./lets_go <password>

So obviously we have to find the password. I opened it in IDA, there are so many subroutines in the executable; this was my first experience with anything related to go so I did not know which subs were library functions or which subs were user defined. To narrow down our subs list i tried string search for usage or password but they did not lead me anywhere. Nothing fancy, i just searched for main function in IDA and i found these:

runtime_main               
runtime_main_func1         
runtime_main_func2         
main                       
main_usage                 
main_iep4thiequai1athieSe  
main_f1151e71905f3d94b49b0
main_Wuyeiqua4ievohR4ahng  
main_Xerei4oreeshex6zien0  
main_UhoCh5ooSeith0ee7Ien  
main_main                  
main_init                  

main function did not have much just jumping to runtime_rt0_go; so I figured that main must be calling main_* functions at dynamically. So i started reversing main_* functions. I also started analyzing it in gdb at the same time with input “ABCDEFGH” so i can see what was happening with my input. So this is what i found as my first overview of these functions

  • main_main is the real main function
  • If password is not provided main_main calls main_usage and exits
  • Else main_main calls main.UhoCh5ooSeith0ee7Ien to generates a weird string “WPnq7JEM2AskwjoX3VRx9aHbiYfd4#Zp05TUGc1SBCFNgvhz8rQl6@ytIKumDeOL” (this is where i realized that this binary was passing args to function by putting data on stack even though it was 64 bit compiled, it also returned values on stack)
  • Then main_main calls main.Xerei4oreeshex6zien0 (check for password happened here)

Now I started to reverse main.Xerei4oreeshex6zien0

  • Its calling main_Wuyeiqua4ievohR4ahng which is taking (my password, password_len, weird_string, weird_string_len) as input and generating a base64 like string as its output (for input password “ABCDEFGH” output of this function was “39AqV7aEV60==”) but I could not decode it
  • Then a byte by byte checking happens with output of main_Wuyeiqua4ievohR4ahng (“39AqV7aEV60==”) with a fixed string “R6aYb@VboTG==”
  • Still there was a lot of code in this function but I did not bother for this at this time; first I had to validate the check above.

After a little thought and looking at base64 wiki page I realized that in base64 algorithm after splitting the plain text into 6 bits; value of that 6 bits is taken as an index to “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+”
So for example “000101”(5) indexed to “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+” gives “F”
As length of WPnq7JEM2AskwjoX3VRx9aHbiYfd4#Zp05TUGc1SBCFNgvhz8rQl6@ytIKumDeOL string was also 64; I thought maybe its indexing from this string to generate a base64 like string.
So I made a decoder for this base64 with weird_string as index string and fed “R6aYb@VboTG==” into it. And we got the password “KEY_TW:)”.

WAIT BUT THERE IS MORE

As soon as I fed the binary “KEY_TW:)” as password; it asked me to input flag. Such a drag. Time for more reversing.
Now I remembered that I had skipped a lot of code in main.Xerei4oreeshex6zien0

  • So the code after the password check there was a fmt_scan to take flag from us through stdin
  • Then it was checking if flag length was 0x30 or not (so I made a dummy flag of 48 chars and run the binary in gdb to do binary analysis at the same time)
  • Then it splits our flag input into chunks of 8 chars and fed to main_f1151e71905f3d94b49b0;
  • Output of main_f1151e71905f3d94b49b0 (for each 8 bytes of flag) and a fix list of 8 bytes gets fed into reflect_DeepEqual(I guessed that it just checks both 8byte ints are equal or not). I extracted the fix list of 8 bytes from memory into my python script.

Now it was time to reverse main_f1151e71905f3d94b49b0 (here I am writing a python like code for this function)

stack_array = fix string on stack;
key = "KEY_TW:)"
input_flag = flag we want
rol = rotate left

for i in range(0x20):
	for j in range(0x8):
		t = stack_array[key[j] + input_flag[j]] +input_flag[(j+1)%8]
		input_flag[(j+1)%8] = rol(t)

So we can assume that this is a encryption function which takes key and input_flag as input and its output gets checked to a element of a fix list of 8 bytes

At this moment I wrote a decryption function for this encryption which will basically be this

ror = rotate right

for i in range(0x20):
	for j in range(0x7, -1, -1):
		t = ror(input_flag[(j+1)%8])
		input_flag[(j+1)%8] = t - stack_array[key[j] + input_flag[j]]

Now I just wrote a python script to decrypt fix list of 8 bytes to get the whole flag

#!/usr/bin/python
from pwn import *

stack_array = [0xc56f6bf27b777c63, 0x76abd7fe2b670130, 0xf04759fa7dc982ca,
0xc072a49cafa2d4ad, 0xccf73f362693fdb7, 0x1531d871f1e5a534,
0x9a059618c323c704, 0x75b227ebe2801207, 0xa05a6e1b1a2c8309,
0x842fe329b3d63b52, 0x5bb1fc20ed00d153, 0xcf584c4a39becb6a,
0x85334d43fbaaefd0, 0xa89f3c507f02f945, 0xf5389d928f40a351,
0xd2f3ff1021dab6bc, 0x1744975fec130ccd, 0x73195d643d7ea7c4,
0x88902a22dc4f8160, 0xdb0b5ede14b8ee46, 0x5c2406490a3a32e0,
0x79e4959162acd3c2, 0xa94ed58d6d37c8e7, 0x8ae7a65eaf4566c,
0xc6b4a61c2e2578ba, 0x8a8bbd4b1f74dde8, 0xef6034866b53e70,
0x9e1dc186b9573561, 0x948ed9691198f8e1, 0xdf2855cee9871e9b,
0x6842e6bf0d89a18c, 0x16bb54b00f2d9941]

stack = ''
for x in stack_array:
    stack += p64(x)

key = "KEY_TW:)"
enc_array = [0x48fd9fdd395cfe4a, 0x555ed4725bde6cf0, 0xb492d5de09fa160,
0x9c326531f39e320e, 0x5eecc9092cef233d, 0x10b4e73f5fd73945]

def ror(b_char):
    t = bin(ord(b_char))[2:].zfill(8)
    return chr(int((t[-1] + t[:-1]), 2))

def decrypt(enc_int):
    enc_str = list(p64(enc_int))
    for i in range(0x20):
        for j in range(0x7, -1, -1):
            t = ror(enc_str[(j+1)%8])
            enc_str[(j+1)%8] = chr((ord(t) - ord(stack[(ord(key[j]) + ord(enc_str[j]))&0xff]))&0xff)

    return ''.join(enc_str)

dec_array = []
for x in enc_array:
    dec_array.append(decrypt(x))

print ''.join(dec_array)