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
callsmain_usage
and exits - Else
main_main
callsmain.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
callsmain.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 intoreflect_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)