Reversing Rust And Go Binaries

Why do we have Rust and Go???

Languages like C/C++ are general purpouse programming languages used widely for competative programming. Whereas Languages like rust and go are development oriented language or a procedural programming language. The language Go was developed by Google and launched in 2009. The language was developed to solve large scale problems faced by google as an open source programming language.

The language is platform independant like Java and hence can be compiled on any platform and is incredibly easy to learn as it is a lot like python in terms of syntax. These languages are Scripting languages are extremely great for writing services.

Speaking of GoLang

  1. A high performance language , providing great efficiency like C/C++.
  2. Provides high concurrency as in Java.
    • Concurrency is the efficiency of the programme to utilize the untapped capabilities of the OS and the machine hardware. Eg. threading to enable faster execution of the programme.
  3. Easy to learn and fun to code like python : ) !!

Summing it up if you’re looking for scalability and efficiency in a programming language you should probably consider Go!

Go is a win win for the user as well as the processor in terms of efficiency and understandability for the developers !!

GoLang C++
It does not use header files instead of header file go use packages.It contain header file and does not contain package.
It does not support implicit type conversion. It support implicit type conversion.
Go use panic and recover for resolving error. C++ use try, catch, and throw for resolving error.
It does not have while or do-while statements. It have while or do-while statements.
It is more strong typed as comparison to C++ language. It is less strong typed as compare to Go language.
Go contains goroutines and channel. C++ does not contain goroutines and channel.
It does not support inheritance.  It supports Inheritance

Basics of GoLang

Go is pretty much a combo of C/C++ and python in terms of syntax.

Example 1:

“Hello, World” in Go!!

To compile:

$ go build <file name> -o <outfile>

$ ./outfile

Example 2:

Just to Familiarise with the datatypes I tried this example and you can see its quite similar to python.

The output is as follows:

Data types in Go:

As far as reversing a Go binary is concerned as you may have noticed in example 2, the initialisation of variables is not done as it is done in python where the data type need not be mentioned and therefore everytime a variable is initialised it is checked with all the available types in this structure below to determine the type.

  • Bool 
  • String
  • Int 8 ,16 ,32 ,64
  • Uint8 , uint16, uint32 , uint64
  • Complex32, complex64
  • To name a few…

So that’s all you need to know about go language for now ! 

Let’s get started with reversing

Here for analysing the binary , I am using IDA pro . 

Our simple hello World programme has been compiled to ~1800 functions which is not very ideal for reversing. And hence , figuring out our main function is Important.z

Structure of a Go binary

The main of a Go binary is called the “ main.main ”

On further digging we figure out the actual main function that we have written resides in the main.main.

And in the main we can see the call being made to the function Println().

Now lets move on to solving a simple custom challenge.

Challenge Description: make the binary print 1 . : )

Now, since we know what to do first, lets get straight to the main.main function and see the decompilation on IDA!

So our required input is : thebadcat

Now moving onto Rust.

Rust is a more innovative system-level language. Creators produced this language with safety in mind. Notably, they aimed to beat C++ by offering safer memory management while keeping their speed advantage. Rust will lead to the production of fast software.The language frequently supports projects aimed at high-security and high-concurrency .

Rust is also largely known as a memory safe Language

Advantages and Disadvantages :

ProsCons
Highly safe[memory safe language]
Not very friendly in terms of syntax
High speed of execution, almost as much as C/C++
You can’t develop code as ‘fast’ as scripting languages like Python or Ruby
High Concurrency
The binaries produced by rust compiler are larger compared to C/C++
High level of abstraction

Now let’s analyse the binary:

When  you run the binary on IDA, first you see the main and on looking at the graph it is sort of like the libc_start function on C binaries which call the main. And similarly here you can see the highlighted function being called is our actual main function that we wrote.This is what we’ll be analysing next.

So this is the decompilation of our main which clearly shows what the entire programme does , ie , initialises the count , add 1, and prints the value of count till the value of count hits 20,

Now let’s talk about the stack frames in Rust and Go.

The normal C stack frame:

The normal stack frame -Segmented Stack Frame

The stack frame implemented by C binaries where the stack grows from the top, as each new function gets called a new stack frame is created where the local variables are allocated space on the top of the stack -Segmented Stack frames

Stack Frames in Rust and GoLang:

Go provides a light and smart goroutines management. Light because the goroutine stack starts at 2Kb only, and smart since goroutines can grow / shrink automatically according to our needs.

This default stack size is sometimes not enough to run our program. This is when Go automatically adjusts the size of the stack

Function calls in Go binaries

In Go binaries , when a function gets called ,it actually accepts any amount of data and leaves it to the compiler to check the type and length etc of the arguments passed. 

And on analysing , the return value wasn’t on any of the registers , instead the stack held the return value

So here ;

Split Stack and Stack Copying:

So this programme simply calls the functions a,b,c  and prints a line…

Stack copying is a lot like , multiple segmented stacks. The Go routine is running along using the stack space and when it runs out, it hits the stack overflow check and gets copied to a bigger stack space.

However, instead of having a link back to the previous segment, it creates the new segment with double the size and copies the old segment into it. This means that when the stack shrinks back to its old size, the runtime doesn’t have to do anything. Shrinking is now a free operation. Additionally, when the stack grows again, the runtime doesn’t have to do anything. We just reuse the space that we allocated earlier.

Thank You 🙂

HSCTF2020 Writeups

Dis – Python Bytecode Challenge

The challenge was a python bytecode challenge where the approach I chose was to maually replicate the bytecode from the documentation of the dis module in python which took up a while. Im not sure if there’s a less time consuming approach but once that was done the reversing part was automated using Z3.

bytestring=b'\xae\xc0\xa1\xab\xef\x15\xd8\xca\x18\xc6\xab\x17\x93\xa8\x11\xd7\x18\x15\xd7\x17\xbd\x9a\xc0\xe9\x93\x11\xa7\x04\xa1\x1c\x1c\xed'
from z3 import *


def a(s):
    o = [0] * 32
    for i in range(32):
        o[i] = ((s[i]+s[i])-60)
    return o
def b(s,t):
    for x,y in zip(s,t):
        yield(x+y)-50
def c(s):
    return [x+5 for x in s]

def e(s):
    s = [ i for i in s ]
    o = [ (o^5)-30 for o in b(a(s),c(s)) ]
    return o

def main():
    sol=Solver()
    o=b'\xae\xc0\xa1\xab\xef\x15\xd8\xca\x18\xc6\xab\x17\x93\xa8\x11\xd7\x18\x15\xd7\x17\xbd\x9a\xc0\xe9\x93\x11\xa7\x04\xa1\x1c\x1c\xed'
    l=list(o)
    s = [ BitVec('s[%s]' % i, 8) for i in range(32) ]
    s = e(s)
    for i in range(len(l)):
        sol.add(s[i]==l[i])
    print(sol.check())
    print(sol.model())
main()

And the flag we get is :flag{5tr4ng3_d1s45s3mbly_1c0a88}

Emojis – Misc

This challenge was made using emojigram language which is an esolang made using Emojis. All that we need to do is repicate the code in python and generate the flag from it by reversing the code.

The replicated code looked like this.
But since there are conditional jumps and all that we are given is the output when the flag is given, we have no choice but to guess whether the jumps are taken or not inorder to obttain the desired input.
And to achieve the same we played around with a few of the instructions and put together the possibilities to get the Flag.


flag=[120, 66, 94, 114, 95, 69, 110, 125, 73, 78, 99, 52, 118]
flag[9]=flag[9]+flag[1]
flag[7]=flag[7]+flag[11]
flag[2]=47
flag[4]=flag[4]+flag[11]
flag[0]=flag[0]+flag[2]
flag[8]=flag[8]-8
flag[6]=flag[6]+flag[8]
flag[6]=flag[6]-flag[8]
flag[6]=flag[6]-flag[8]
flag[10]=flag[10]+8
flag[11]=flag[11]-1
flag[4]=flag[4]-flag[9]
flag[3]=flag[3]-2
flag[2]=flag[2]-4
flag[0]=flag[0]-flag[11]
flag[1]=flag[1]-flag[3]
flag[1]=flag[1]+flag[3]
flag[1]=flag[1]+flag[3]
flag[1]=flag[1]-flag[7]
flag[3]=flag[12]
flag[2]=flag[2]+8
flag[9]=flag[9]-flag[6]
flag[2]=flag[2]+flag[4]
#flag[1]==flag[5]
#False
flag[2]=flag[5]
#flag[1]==flag[12]
#False
#flag[11]=flag[11]+flag[0]
flag[12]=118

Solved!

Flag: flag{tr3v0r-pAck3p}

APLab:English

Another java Reversing Challenge. We are given a java file which is to be reversed.

Solution:

s="1dd3|y_3tttb5g\`q]^dhn3j"
def unxor(string):
    ret=''
    xorstr=[4,1,3,1,2,1,3,0,1,4,3,1,2,0,1,4,1,2,3,2,1,0,3]
    for i in range(len(string)):
        ret+=chr(ord(string[i])^xorstr[i])
    return ret




def untranspose(string):
    ret=[None]\*23
    transpose=[11,18,15,19,8,17,5,2,12,6,21,0,22,7,13,14,4,16,20,1,3,10,9]
    for i in range(len(transpose)):
        ret[transpose[i]]=string[i]
    return ''.join(ret)


for i in range(3):
    s=unxor(s)
    print(s)
    s=untranspose(s)
    print(s)

The Output looks like this:

5eg2~x\3upwc7gau\\gjo3i
cj3o\\pg~i35uaug\xe2gw7
gk0n^]sgm04watc]zf0fw4
40gf]sma^4wgtc0z]knf0w
01dg_rna_0tf}tb4{_hlg0t
flag{n0t_t00_b4d_r1ght}

And we finally Obtain the flag!

pcme0 (Writeup)

pancrackme: v0

This crackme from crackmes.one was a relatively easy one where the basic idea is the implementaion of multiprocessing using the fork system call and how such challenges can be debugged. My approach was good old gdb and IDAPro where I used IDA for static analysis and decompilation and gdb for the dynamic analysis.

On running the binary!

On running the binary using gdb!

Here we can see that including the parent process there are a total of three processes switching between themselves to finally give the “yeah” output. From the image we can figure out that the first parent process prints the line “pancrackme: v1.0” . And then switches to the new process which writes “Password: ” before it makes the next switch which reads and checks the user input.

Next , on going through the disassembly , we know the second fork call actually produces the process which reads and checks the input. Here the child process . First you follow the default child process on gdb using

$ set follow-fork-mode child

Before the second fork is called , we reset the fork settings on gdb and follow the parent process

$ set follow-fork-mode parent

now in the disassembly we can see that the winwin function as marked and renamed in the following image prints out the yeah statement. In turn it checks our input.

Here we can see a debugger check implementation using ptrace which is bypassed by changing the reaturn value of ptrace.

The lines 49 to 52 can be precisely called the input check where it reads input of maximum 128 characters . If there is a valid input we are taken to the winwin function where the elaborate input check happens in the ‘real_funct’ which on passing allows the filedescriptor to write yeah into the write end of the pipe which is printed if the input is correct.

The check is basically :

input[i]^0x3a==BYTE PTR[arr] // where arr is the hardcoded string in the binary at the location [ 0x804a240+0x7c ] , which can be spotted only on dynamic analysis of the binary. This part of the binary is not decompiled by IDA due to dynamic allocation and unpacking of the binary!

Thus to get the input values, all we have to do is reverse xor the hard coded values and there you have the input to the crack me!

AND FINALLY YEAH! 🙂

HackIm’16 zorro_pub(Writeup)

  • The binary zorro_bin is a stripped file.

In this challenge basically the binary asks you for a number initially, (that is the number of drinks ). And further asks for the drinks’ id’s.If the first input is x, then a loop runs for x times xoring the values of drink id’s , which becomes the seed to a rand function in the code. The drink ids have to have their value in the range (16,0xffff). The seed value is checked against a function which checks whether the bin(seed) has 10 1’s if the condition is satisfied , the seed for the rand function is set and the rand values are updated to a string which is MD5 hashed and eventually checked against a hardcoded hash in the binary. Thats pretty much much it for the description for the challenge.

Solution:

My approach to this was to write a python script to find the input that would give the hash.

import random
import hashlib
from ctypes import *
libc=CDLL('libc.so.6')
arr=[0x3c8,0x32,0x2ce,0x302,0x7f,0x1b8,0x37e,0x188,0x349,0x27f,0x5e,0x234,0x354,0x1a3,0x96,0x340,0x128,0x2fc,0x300,0x28e,0x126,0x1b,0x32a,0x2f5,0x15f,0x368,0x1eb,0x79,0x11d,0x24e]
for j in range(16,0xffff):
    n=j
    c=0
    while(n!=0 and n>0):
        c=c+1
        n=n&(n-1)
    if(c==10):
        n=j
        libc.srand(n)
        c=0
        st=''
        final=''
        for i in range(30):
            a = libc.rand()%1000
            st = st + str(a)
            final = final + chr((a^arr[i])&0xff)
        final =final+'0'
        result = hashlib.md5(st.encode())
        if(result.hexdigest()=='5eba99aff105c9ff6a1a913e343fec67'):
            print(j)
            print(a)
            break

This script will give two outputs the first one being the seed value.