Reversing/ GoBlind
Description:
This binary hides its secrets deep within. Can you untangle the layers and reveal the secret?
File
: file
Writeup
- This challenge was written in
GoLang
. - Executing this binary (without debugger), requires a password to reveal some
super secret
as it says. - Giving some random input ( less than 16 chars) says:
Invalid Length
. - Open the binary in the debugger and there we see a lot of
main_*
functions. Start analysing from the
main_main
and first see the valid length of the input is16
- After length check, we can see the
input[11]
after some operations is being passed tosync__ptr_WaitGroup_Add()
. - Let’s reverse the operations being performed on
input[11]
res= ((ord(input[11]) >> 2)%10)+2
- this res value is being passed to the function
sync__ptr_WaitGroup_Add()
, it basically keeps the track of how many go_routines are active or need to be completed. - so,
res
should be equal to no. of go_routines are need to be executed. - to determine the number of executing goroutines, observe calls to runtime_newproc() and track the respective wrapper functions whose pointers are being passed.
- In the wrapper functions, functions which is going to be executed within that goroutine.
- To clear the check let’s find the possible chars for
input[11]
Possible chars -
'0' '1' '2' '3' 'X' 'Y' 'Z' '['
-> these 8 possibilities- Let’s look into the first go routine:
PxIuE72uAaCXX3HmXchd
this function checks the system year and month.- Year should be
2004
and hash of the month name should be equal to0xad018838
- let’s look at the hash function
t5gftiAC
:
func t5gftiAC(input string) uint64 {
var hash uint64
for _, c := range input {
hash = (hash*0x1003F + uint64(c)) & 0xffffffff
}
return hash
}
- Month name found is
July
. - Now, since
July
is the 7th month, sokey[7]='N'
key[9]='$'
key[10]='G'
- In this function itself, there is another function
CWlj47zuCalSy1YWM
that returns bool value. - This seems to be another check. From here, we can see 3 chars of input involved.
- input[5]==’G’ is directly observed.
- let’s look for 13th and 14th character.
- there’s another function called
nOsylsKWBryE2UkXS
, in which we can seeruntime.Callers()
-> that calculates the stack depth. result =((input[13] ^ input[14])>>1)%14
- Understand how runtime.Callers work, get the value equal to result.
- runtime.Callers()
- nOsylsKWBryE2UkXS()
- CWlj47zuCalSy1YWM()
- PxIuE72uAaCXX3HmXchd()
- .go_exit()
- The value here will be
5
Again there can be few possibilities, but to pass the check we can proceed further with any possible combination.
- second goroutine involves checking chars of file name with chars of input.
exeName[8]==input[8] && exeName[5]==input[12] =>
key[8]='e'
andkey[12]='.'
- 3rd goroutine involves the no. of files in the directory in which
chall.exe
is present and being executed. No. of files in the directory should be 5 and thus
key[6]=='o'
- In 4th goroutine, first 5 chars of the key are passed to the function
HrjZ1eDoZCypd2kqH
and its result is compared toNGZI1mz
. - upon taking look at this function, it seems to be some kind of custom base64.
- its encoding goes like this:-
- it converts each char to 8-bit representation
- Xor each 8-bit chunk with 0xAA
- padding to 6-bit chunks
- bits are reversed in each 6-bit chunk.
- order of 6-bit chunks is reversed.
- then mapping each 6-bit chunk to the character in the 64 length buffers (like base64).
- Decoding it, we can get
g0n3!
-> these are first five characters of the key. The results from these goroutines are collected via channels. If all are
true
, it proceeds; otherwise, it signs out of Windows.Till now we have the key:
g0n3!GoNe$GX.XXX
where X are still unknown or unconfirmed.- If all results collected are
true
then another functionR7iQNypRBtbrpyaNq1z
is encountered. It looks to have some encrypted buffer. This may be the decryption routine. Let’s dive into it and see what does it unfold? - In function
OnkQsAFZuhclKr2sBDT
encrypted buffer is being passed and this decryption routine looks likerc6
. But, I changed the hardcoded constants used in the standard algo.pw=0xB7E12163
qw= 0x9E3778D9
keeping rest same. - There is no need to reverse this function, it will decrypt the buffer with the right key.
- For right key, we still the need rest 4 characters.
- Since, all
goroutines
checks are passed [setting the required system time and year, keeping the required no of files in the same directory],data.txt
file will be created, but it won’t be readable since we don’t have right key yet. - There is a function that should be reversed in depth is
Z2JEqRKSdxHoRaSCjUshuqZUVD
. - This function loads some buffer and then goes through some routine (that should be some sort of decoding routine) and we can see very useful stuff here like:
kernel32.dll
VirtualAlloc
CreateThread
this is enough for us to conclude that it must be involving shellcode. - 3 chars from the input,
input[5]
input[13]
input[15]
are involved in the shellcode. - that decryption routine invoves first 2 chars of the key.
g
and0
.
for i in range(len(sc)):
sc[i]= sc[i]-2
sc[i]= sc[i]^ord('g')
sc[i]= sc[i]-1
sc[i]= sc[i]^ord('0')
- we already know input[5]=
G
, we need to find two chars such that our shellcode is fixed. Find the index where these chars are involved in shellcode.
- For key[15]->
68 42 65 65 70 push 0x70656542
is the correct instruction. Here0x68
is corresponding to input[15] - For key[13]->
8b 40 78 mov eax,DWORD PTR [rax+0x78]
Here0x78
is corresponding to key[13].
Looking at the shellcode carefully, we can find that there is peb_traversal is involved and it finds function with it. Referring to peb_traversal, we can fix key[13].
For key[15], we can see before this instruction, stack is being set up and
0x70656542
->peeB
:-Beep
It is pushing this string on the stack. Before this instruction there isxor rsi rsi; push rsi
that suggests it is pushing a null-byte to the stack, followed by thispeeB
->Beep\x00
Resources that can be referred to:
- https://www.ired.team/offensive-security/code-injection-process-injection/finding-kernel32-base-and-function-addresses-in-shellcode
- https://defuse.ca/online-x86-assembler.htm#disassembly
- Capstone
i=0x78
i= i^ord('0')
i= i+1
i= i^ord('g')
i= i+2
print(chr(i)) -> It gives '0'
i=0x68
i= i^ord('0')
i= i+1
i= i^ord('g')
i= i+2
print(chr(i)) -> It gives '@'
- Our key is now:-
g0n3!GoNe$GX.0X@
str="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/!@#$%^&*."
for i in str:
if 5==((48 ^ord(i))>>1)%14:
print(i)
this is possible options for key[14]:= [J
K
n
o
r
s
]
- With some permutations:
g0n3!GoNe$G0.0n@
[Correct key] Now with the correct key and other challenge requirements, we can get our
data.txt
which contains:1 2 3
Good Job, Mate! Thank you decrypting this file-- means a lot to me. Your secret is here: "ZmxhZ3tnMF9yMHV0MW4zc180cjNfYzAwbCEkIX0=" Enjoy, happy reversing!
A
Beep
sound is heard at the end, and binary exits.- Flag is:
flag{g0_r0ut1n3s_4r3_c00l!$!}