Basic Binary Disassembly
Completing a simple reversing challenge
Introduction
In this blog post, I shall be trying to complete the room `0x41haz` on TryHackMe, listed here. It is listed as a 'simple reversing challenge'; so we shall we be trying to understand what the supplied binary does by looking at its disassembled code.
First steps
The first steps would be to run the file
command on the binary, which will provide us some information as to the file type and architecture. For example:
$ file 0x41haz.0x41haz
0x41haz.0x41haz: ELF 64-bit MSB *unknown arch 0x3e00* (SYSV)
We can also run checksec
to check if there are any binary security controls in place like PIE or NX.
$ checksec 0x41haz.0x41haz
Checksec Results: ELF
βββββββββββββββ³ββββββ³ββββββ³βββββββββ³ββββββββββ³ββββββββ³ββββββββββ³ββββββββββ³ββββββββββ³ββββββββββββ³ββββββββββββββ³βββββββββββββ
β β β β β β β β β β β β Fortify β
β File β NX β PIE β Canary β Relro β RPATH β RUNPATH β Symbols β FORTIFY β Fortified β Fortifiable β Score β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β 0x41haz.0xβ¦ β Yes β Yes β No β Partial β No β No β No β No β No β No β 0 β
βββββββββββββββ΄ββββββ΄ββββββ΄βββββββββ΄ββββββββββ΄ββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββββ΄ββββββββββββββ΄βββββββββββββ
Note: This is using the pip version of checksec which is available here.
Both NX and PIE are enabled. NX enabled means that executable bit isn't set, that is, certain areas of the memory address space are non-executable. Thus meaning we cannot execute data (if we were to supply it, by overwriting the RIP, for example). PIE stands for Position Independent Executable. This is also a binary protection measure which ensures that the binary is loaded into random memory addresses every time the binary is executed (although certain parts of the binary may reside in a static parts of memory).
We can also conduct basic static analysis of the binary and look at the strings within it. For example:
$ strings -n 6 0x41haz.0x41haz
/lib64/ld-linux-x86-64.so.2
strlen
__cxa_finalize
__libc_start_main
libc.so.6
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
2@@25$gfH
[]A\A]A^A_
=======================
Hey , Can You Crackme ?
=======================
It's jus a simple binary
Tell Me the Password :
Is it correct , I don't think so.
Well Done !!
GCC: (Debian 10.3.0-9) 10.3.0
.shstrtab
.interp
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.plt.got
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.got.plt
.comment
We can see some pretty typical strings in the binary such as calls to the libc shared object etc. However, there are strings which ask for a password and provides different output based on the input. This tells us, most likely, that the binary will feature an if/else statement and will jump to different parts of memory depending on the input provided by the user.
The first protection
Recall from our file
command the output:
0x41haz.0x41haz: ELF 64-bit MSB *unknown arch 0x3e00* (SYSV)
This is not typical of a normal binary especially this segment of the output MSB *unknown arch 0x3e00* (SYSV)
. The file here is treated directly as an executable. We need to edit the ELF header in the binary (specifically changing the sixth bit which defines endianness); in this case it is set to the Most Significant Bit, MSB (01 in hex) 1. We must change this from 01
to 02
to switch it to the Least Significant Bit (where the least-significant byte is put first). This is so that the file can be read properly. We can use a hex editor to do this, such as hexedit
or hexeditor
.
After changing the sixth bit, we can run the file
command again on the modified binary.
$ file 0x41haz.0x41haz
0x41haz.0x41haz: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6c9f2e85b64d4f12b91136ffb8e4c038f1dc6dcd, for GNU/Linux 3.2.0, stripped
We now can see a lot more information about the binary. We are running an ELF 64-bit executable which has PIE enabled (which we verified using checksec
). The binary is made for Linux systems and has been stripped. This means the symbol names (such as names of functions, like main
) have been removed. This makes reverse engineering the binary harder, but still possible.
Detonation
We can now denote or run the binary to see how it works before we take a look at it in a program like ghidra
or r2
. This can be done like so:
$ ./0x41haz.0x41haz
=======================
Hey , Can You Crackme ?
=======================
It's jus a simple binary
Tell Me the Password :
We get the output above, which we found when analysing the binary using strings
. We can now type in a password, and see what the program returns. Of course, we don't know the password as of yet, so we should get an incorrect response.
=======================
Hey , Can You Crackme ?
=======================
It's jus a simple binary
Tell Me the Password :
hello world
Is it correct , I don't think so.
Typing hello world
gives us Is it correct, I don't think so
. Clearly we guessed incorrectly. Now to take a closer look at it.
Using Radare2
We can use Radare2 r2
to disassemble the file. We do this with:
$ r2 -d 0x41haz
[0x7fa4c619a360]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Skipping type matching analysis in debugger mode (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
We can typeaaa
to analyse all the code. The output tells us what that command does. Once we have analysed the file, we can run afl
which analyses function lists (it lists all the functions within the program).
[0x00001080]> afl
0x00001080 1 43 entry0
0x00001030 1 6 sym.imp.puts
0x00001040 1 6 sym.imp.strlen
0x00001050 1 6 sym.imp.gets
0x00001060 1 6 sym.imp.exit
0x00001070 1 6 sym.imp.__cxa_finalize
0x00001165 8 219 main
0x00001160 5 133 -> 56 entry.init0
0x00001120 5 57 -> 50 entry.fini0
0x000010b0 4 41 -> 34 fcn.000010b0
0x00001000 3 23 fcn.00001000
[0x00001080]>
Here, we get a list of all the functions and their corresponding memory addresses. Typically, if it is a C program, we will first want to look at themain()
function. We can do this in r2
via s main
. That will change the prompt to the memory address of the main function.
[0x00001080]> s main
[0x00001165]> # prompt has been changed
We can now run pdb
which will print the basic block assembly of the function.
[0x00001165]> pdb
; DATA XREF from entry0 @ 0x109d
β 219: int main (int argc, char **argv, char **envp);
β ; var char *s @ rbp-0x40
β ; var int64_t var_16h @ rbp-0x16
β ; var int64_t var_eh @ rbp-0xe
β ; var int64_t var_ah @ rbp-0xa
β ; var size_t var_8h @ rbp-0x8
β ; var int64_t var_4h @ rbp-0x4
β 0x00001165 55 push rbp
β 0x00001166 4889e5 mov rbp, rsp
β 0x00001169 4883ec40 sub rsp, 0x40
β 0x0000116d 48b832404032. movabs rax, 0x6667243532404032 ; '2@@25$gf'
β 0x00001177 488945ea mov qword [var_16h], rax
β 0x0000117b c745f2735426. mov dword [var_eh], 0x40265473 ; 'sT&@'
β 0x00001182 66c745f64c00 mov word [var_ah], 0x4c ; 'L'
β 0x00001188 488d3d790e00. lea rdi, str._nHey___Can_You_Crackme___n ; 0x2008 ; "=======================\nHey , Can You Crackme ?\n=======================" ; const char *s
β 0x0000118f e89cfeffff call sym.imp.puts ; int puts(const char *s)
β 0x00001194 488d3db50e00. lea rdi, str.Its_jus_a_simple_binary__n ; 0x2050 ; "It's jus a simple binary \n" ; const char *s
β 0x0000119b e890feffff call sym.imp.puts ; int puts(const char *s)
β 0x000011a0 488d3dc40e00. lea rdi, str.Tell_Me_the_Password_: ; 0x206b ; "Tell Me the Password :" ; const char *s
β 0x000011a7 e884feffff call sym.imp.puts ; int puts(const char *s)
β 0x000011ac 488d45c0 lea rax, [s]
β 0x000011b0 4889c7 mov rdi, rax ; char *s
β 0x000011b3 b800000000 mov eax, 0
β 0x000011b8 e893feffff call sym.imp.gets ; char *gets(char *s)
β 0x000011bd 488d45c0 lea rax, [s]
β 0x000011c1 4889c7 mov rdi, rax ; const char *s
β 0x000011c4 e877feffff call sym.imp.strlen ; size_t strlen(const char *s)
β 0x000011c9 8945f8 mov dword [var_8h], eax
β 0x000011cc 837df80d cmp dword [var_8h], 0xd
β ββ< 0x000011d0 7416 je 0x11e8
Going further
We can see there are a few lines which spark some curiosity.
0x0000116d 48b832404032. movabs rax, 0x6667243532404032 ; '2@@25$gf'
0x0000117b c745f2735426. mov dword [var_eh], 0x40265473 ; 'sT&@'
0x00001182 66c745f64c00 mov word [var_ah], 0x4c ; 'L'
These lines contain hex values which resemble a password. We can put them together and enter them into the binary.
=======================
Hey , Can You Crackme ?
=======================
It's jus a simple binary
Tell Me the Password :
2@@25$gfsT&@L
Well Done !!
We get the correct answer and can submit the password as the flag, wrapped in THM{}. We can do that manually, or for fun use sed
like so:
echo '2@@25$gfsT&@L' | sed "s/.*/THM{&}/"
Summary
This challenge was very easy, and did not require any binary exploitation. However it provided us with a step into using a disassembler like r2
and showcasing some of the commands used to analyse an executable.
Last updated