Reverse Engineering Challenge: Disassemble it!

Are you trying to master the ELF format and you need some stuff to try your new skills?.. well, this is a simple challenge for you to try.

The Challenge

A colleague that works as Network Engineer for an important company has captured a piece of malware that somebody was trying to use against the company. Your friend tried to take a look at the code using objdump but he couldn’t get anything so he come to you, an expert hacker, master of the Elfs to help him get the assembly code for that malware.

Note: The binary in this challenge does not contain any malware. You can run it if you want but that is not even necesary

Your Goal

Fix the binary file. So you can disassemble it using objdump and figure out the thread for this zero-day malware.

To probe you fix the binary, post the output of readelf -h in the comments. Use the spoil tag so your solution is not seen by other people trying to solve the challenge. Be free to provide a brief write-up explaining how did you solve the challenge

To get the binary you will have to copy and paste the text below in a file name rec01.gz.base64 (actually you can name it as you wish… it is just a name)

Then you have to figure out how to get the binary out of that file.

Hint0: No idea on how to get the binary file

Well, this is hint 0 because we have already gave it to you. Check again the file name we have proposed and make a guess

Hint1: How to get the binary

cat rec01.gz.base64 | base64 -d | gunzip > rec01

Hint2: Not sure what to do next?

Try readlelf on the binary and check the output. There are some complains in there.

Hint3: Use this as last resource in case you are really stuck.

The original program has 9 sections… Now it should be easy!

Your Secondary Goal

Write a program to apply this basic anti-forensic technique to any ELF file :stuck_out_tongue:
Then you can sell this tool to your colleague company so they can analyse future malwares using this technique.

Hack Fun!

The Challenging Binary

H4sIACQUw1cAA+1YXWwUVRS+s7vTHWg7u+En1gB11ZJsoSwMLtKSAjtl297FAVugIH+W1l2gSX+S
dqY2IIKORS/Dig/ogw8EjS+YSBpjkBBCp6xSQINpQ6AJEQIRsmWN/Gn573ju7GxtNzHRxAcf9iR3
zz3fPd853525mezMznKpwsYwKGU2tAjRqJYJmHHAwj9wj6QAVowc5gpn5rJotAXG+EAWGuMR8pi/
lGdCqbruTWN8jgW/NlI3yXNYw2NJ9lg6U77Ayk55h+Wrr8thJ/rnlpJF+ZNg2GFULq9BF55+/lA4
5PNPHprWzxv7L1+pvqBWHXPB6okhRwB1U1mCHq803bpYLSY31uDoqmEOk8kHeQ+KN8IKJr1xmvh7
V+k3yPWtHiJnXSxe+JZhGO2u+wNVcVpvLeGwetqIdTGDF2HhaB5cmRjW2OVQBZNhTBbh3kqOagRv
yu2tzDNDUunttgEy2G5DqAaT6+KaELkorhZrxFUhcn4lJvew+quX7DgeUhejZdGdnDK0jAxL5AHW
3LizX34Gq4+eVbKxFuBwp67MS2RhVXcnrgpD3VlsgO5vBlTGUeWUSL4/Nh56wobyAQqSW3HOXCo/
hTVQwh7M9aCQ5oQy8gSsPnheyQppa7gEF1J73YkbQj+0w706QpsM7Co/CTFhPwOKoIc0HneekQuw
+tCujAtpq6mURC6ELA0rIOyHYuopd+I8rb6YCl8gnMHkAlZPFqvbTjuVF7FW4YaK86EiTD0w9Sen
XpiGYJrgsNGDS3qU05LGFlOt7zygp8n1/mF6JTXWQbES3fXuAYhD0arVWJsXz6F3oAer127jwvNS
VCpwhdSrtyXy070vJMALewT93pevREsZTBb+AMnx8mHD6J4Ily5u0LIgN+9EPoT0IKxNIq6PddcR
PWETdMJuoQ00O2j8ymzFfkKdoe+pMt7rl527Hs1W7iTi2o7jErmGyeX49qeGkWCPttBK0cjAenGD
uFF8XazdGBNrBF1cBWfgD6yeM+DOP8bkVjUmd+4exuQKJmeT0/tw3Qfw/T7MnMPGWbkEl9yFg/Lm
DFzYJ0WLsqWShJy/pxwV25UJZsvfgBz/FNrBTsZhI5b4WdAToHdg3fqNtHms68DgCyCqyz44hTrb
4CTq0GAuOHoMa1ZWS9r0fdketKzzF7kCdxqYPJUXCTpWzxhUUF8w2mCEtKkFuPOS7JXITavpdLOp
wpftKp25UHEujW6YydDT9B2X6BMurQMJlgI4CSV98kTXEYcrCD883OdgoS7fpFc4VtUYqWuLFHk2
N3Q0RZ4fj2aHI+2zlda65nBLE5KCtVUryqVXxSBqr2vc0trQHE4+A6gx21YgpsPNTMlxch8xSXwq
jCePDWMKTRB591qeM3E/PTJPDGPk0Wc9SyQY8wGfOwovghGmzynAh5lknd22JblZZfvsoQ8dUXZv
VlB12m8y0BdZtffDeA5OVgcFynh31Bbk8/baRd6z24F5r8pW8nNs63kvACKfBwllPEd5P8LYDrzN
I7yyEZ5IeSLwanhvcBSP6naArkPAm/YvnqMZy1jGMpaxjGUsYxn7f5n5Gm2MN+eVS5Ys8Hhr6pVm
WfH4fcU+/6y5ihkJbwt+3xy/76XCJI6Qr21rm9wq19UjX3OLHPFtaVZ89UpDY3hWQxj55EiHjHyt
LeE6uQ75IltrN7fWNUWQLxnXt7Uh3xstTU2RZvm/2kc2DPp+b7Piv74LJOOCtHxHWpyPRn2TAAta
/KDF/9o+Np8ZG5r1KZb6T08/BiR9MhbS8pk0X2TNU/q9bMDyyTgvrSGXVu9liz8ic+R7StLlpOWn
77/Uqpniu1PfYSw+TstP7y9a/eek4Sn+zDQ8ff9jtI+y+RZ/6d/wU/YnFJ4o78gSAAA=
11 Likes

I’m about as far as making it into a legit binary.

3 Likes

Here is how I solve the Challenge

First Goal : Fix it
Here is how I did to solve the first part

To Solve the challenge here is what I’ve done

Decode the string to have the binary file

First thing we need to do is decode the string provided. I copied the string in a file then ran :

cat challenge | base64 --decode > TheZeroDayMalware

I now have a new file called TheZeroDayMalware.

The second thing I’ve done is run file on the new file, here is the output of file :

file ZeroDAy
ZeroDAy: gzip compressed data, last modified: Sun Aug 28 12:41:08 2016, from Unix

Now to have the binary, I just ran gzip -d TheZeroDayMalware

Fix Me please :’(

Now that I have a binary file I run the readelf -h to see the header of that file, here is the ouput :

ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x40015f
Start of program headers: 64 (bytes into file)
Start of section headers: 4232 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 4
Size of section headers: 64 (bytes)
Number of section headers: 0 (0)
Section header string table index: 8 <corrupt: out of range>
readelf: Warning: possibly corrupt ELF file header - it has a non-zero section header offset, but no section headers

The command said the File was corrupted.

The “it has a non-zero section header offset, but no section headers” made me realize that I needed to change
“Number of section headers: 0 (0)” to something else

I run xxd on the binary file to have the hexdump of the binary

xxd TheZeroDayMalware > HexZeroDay

Now I open it in vim and search for 8 ( = Section header string table index: 8, the last line of readelf -h output )

0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF…
0000010: 0200 3e00 0100 0000 5f01 4000 0000 0000 …>…_.@…
0000020: 4000 0000 0000 0000 8810 0000 0000 0000 @…
0000030: 0000 0000 4000 3800 0400 4000 0000 0800 …@.8…@…

The 4th line contains 0800 : I supposed this line was “Section header string table index”
The 0000 before 0800 is the “Number of section headers: 0 (0)”

I try to change the file header to this :

0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF…
0000010: 0200 3e00 0100 0000 5f01 4000 0000 0000 …>…_.@…
0000020: 4000 0000 0000 0000 8810 0000 0000 0000 @…
0000030: 0000 0000 4000 3800 0400 4000 0800 0800 …@.8…@…

I have changed the Number if section headers But when I ran readelf -h on the new binary I still have an error

ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x40015f
Start of program headers: 64 (bytes into file)
Start of section headers: 4232 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 4
Size of section headers: 64 (bytes)
Number of section headers: 8
Section header string table index: 8 <corrupt: out of range>

Then I change the Number of section headers to 9 instead of 8

And Voila :

ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x40015f
Start of program headers: 64 (bytes into file)
Start of section headers: 4232 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 4
Size of section headers: 64 (bytes)
Number of section headers: 9
Section header string table index: 8

5 Likes

Congrats @L3akM3-0day :trophy: and thanks for the detailed write-up.

Well done!

4 Likes

What utility are you using to edit raw binary?

EDIT: Nevermind! I used hexedit and solved it!

1 Like

[spoiler]By readelf-ing the binary it’s quite obvious that there is something wrong with the number of the section headers. They just can’t be 0.

So the next step was to patch the binary so that the ELF header field would be correct. The ELF 64-bit header looks like this:

typedef struct
{
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum; <— Number of Section Headers
Elf64_Half e_shstrndx;
} Elf64_Ehdr;

Using the xxd command, we get a pretty lengthy output but we only care about the first 64 bytes. Specifically, we care about the 61th and 62th byte just by looking at the struct above. Thus:

As you can see the 61th and 62th byte are 0000. I played around with the number of section headers by using a hex editor but kept getting errors until 9 was proved to be the correct number of section headers and voila:

No corruption errors!

I’m hoping to come up with the anti-forensic code soon and I’ll edit the post.

@0x00pf I’d like to ask a question. I’m not that experienced with ELF yet so I hope I won’t sound stupid, but how is “Stripping” the number of sections headers considered “malicious”?[/spoiler]

4 Likes

Congrats @_py :trophy:!

Hope you enjoyed… but according to your write up it looks like it was too easy…

Answering your question. Stripping the sections is not really malicious, however, it can be done in a way readelf does not complain and then it is not so-obvious what is going on. Check this example (not gzipped just base64 encoded)

f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAhQBAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAB
AAAAAAAAAAEAAAAHAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAuAAAAAAAAAC4AAAAAAAAAAAQ
AAAAAAAAuAEAAAC/AQAAAA8Fw0i+pQBAAAAAAAC6EgAAAOjf////uDwAAAC/AAAAAA8FSSBhbHNv
IG5lZWQgYSBmaXgKAA==

Many anti-forensic techniques are just annoyances to make more difficult the analysis of the sample.

Note that, this simple measurement can be considered an anti-forensic technique because:

  • The program still works normally, despite of having a corrupted header
  • readelf does not show any section or any symbol in the binary
  • objdump cannot disassemble the program
  • gdb refuses to load the program

I wanted to make the challenge accessible to everybody, and I think this is the simplest ELF modification that can be done.

3 Likes

I don’t think it was meant to be malicious, probably just a method to obfuscate information to deter the reverse engineer’s attempts.

EDIT: Forgot to click “Reply” before pico’s.

Also, here’s a quick reverse engineer of the disassembly of the main function, so it might be a bit inaccurate:

size_t count;                           // length of data
const char *buf;                        // pointer to a buffer
unsigned int fd;                        // file descriptor
unsigned int sys_call;                  // syscall number

// main
func_00400144() {
    count = 0xF;                        // length = 15
    buf = 0x4004f6;                     // "Please, fix me!", 0
    fd = 0x1;                           // STDOUT

    ret = func_004001a2();              // printf-like function
    ret ^= ret;                         // 0

    return ret;                         // return 0
}

// this is basically some implementation of printf
func_004001a2() {
    sys_call = 0x1;
    ret_0 = sys_call(fd, buf, count);   // ssize_t ret0 = write(fd, buf, count);

    if (ret_0 < unsigned(0xffffffffffffff7c)) {
        var_0 = -LOW_32BITS(ret0);      // 0x84
        ret_1 = func_004001a9();        // ret_1 = 0x601018; no idea what this value is
        ret_0 = var_0;
        ret_0 |= -1;                    // or 0xffffffffffffffff; return value is -1
    }

    return ret_0;                       // return number of bytes written or -1 on error
}

// no idea what this is; probably needs to be tested in runtime
func_004001a9() {
    return 0x601018;
}
3 Likes

@0x00pf, indeed I could not load the program or dump the object code and that was actually my first thought of solving it but gdb was refusing to load the program. I actually tried using a breakpoint at the entry point address which was shown in the readelf output but I didn’t have any luck at all. Also thank you @0x00pf for the challenge. It was my first one and no it wasn’t that easy to be solved because I haven’t mastered ELF yet. Though, it made me more familiar with the format itself and this could not be done without your challenge. Props to you mate.

@dtm, I got confused with the “malware” reference in the challenge description that’s why I asked but I can totally see how it can be used for obfuscation reasons since I could not disassemble the code with gdb. Do you mind sharing your thoughts about your code? Did you manage to get the object code because as I explained above to pico it wasn’t possible with the knowledge I had.

2 Likes

I’ve edited my response with more comments and fixed some small inaccuracies.

Since I had no experience with it, I was required to sneak a peek at the last hint. Other than that, it wasn’t bad. All I really had to do was to look up the structures of the ELF64 format and I could work it out from there.

@_py sorry about the confusion. Malware use to do things like that to avoid analysis. That is what I mentioned.

The challenge writing really needs some improvement :stuck_out_tongue:

To get some assembly out of the binary you can try radare2

Alright here are the two programs i used to fix it:

#include <linux/elf.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int get_file_size( int fd )
{
    struct stat _info;
    fstat( fd, &_info );
    return _info.st_size;
}

int main(int argc, char *argv[])
{
    struct elf64_hdr *elf;
    int fd;

    fd = open( argv[1], O_APPEND | O_RDWR, 0 );
    int len = get_file_size( fd );

    elf = mmap(0, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0);
    elf->e_shoff = 0;

    close(fd);
}
#include <linux/elf.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int get_file_size( int fd )
{
    struct stat _info;
    fstat( fd, &_info );
    return _info.st_size;
}

int main(int argc, char *argv[])
{
    struct elf64_hdr *elf;
    int fd;

    fd = open( argv[1], O_APPEND | O_RDWR, 0 );
    int len = get_file_size( fd );

    elf = mmap(0, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0);
    elf->e_shstrndx = 0;

    close(fd);
}

Ok so the spoiler tag is pretty useless here.
Ofcourse this could be put together as one program easily, but I fixed it two steps at a time.

Writing a program that automaticly detects tricks like this will be a bit harder, but I’ll see what I can come up with. ^^

2 Likes

Good job. I did exactly the same

1 Like

@unh0lys0da, the “fix” for this challenge was to change the number of sections from 0 to 9. This code sample doesn’t seem to accomplish that unless I’m too tired.

1 Like

Hm I should do some reading then, but after changing it to 0, it worked for me as well.

1 Like

@_py and @unh0lys0da,

To be honest I haven’t read the text on @unh0lys0da post, just looked at the code and I though it was the reply to the secondary goal:

Actually, this are the two additional things you have to do to remove any warning from the readelf output (in addition to set the number of sections to 0). After that, objdump, will still fail to disassemble the program and readelf will not show any warning.

I really have to be more precise with these writings :blush:

1 Like