Hackthebox - Behind the Scenes - Reversing

File Analysis

fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ file behindthescenes 
behindthescenes: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e60ae4c886619b869178148afd12d0a5428bfe18, for GNU/Linux 3.2.0, not stripped

It’s an ELF file, so we can try and run it:

fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ ./behindthescenes 
./challenge <password>
fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ ./behindthescenes meow
fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ 

It’s expecting an argument, but nothing interesting happens. Let’s run strings to see if there is anything interesting:

fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ strings behindthescenes 
/lib64/ld-linux-x86-64.so.2
libc.so.6
strncmp
puts
__stack_chk_fail
printf
strlen
sigemptyset
memset
sigaction
__cxa_finalize
__libc_start_main
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
./challenge <password>
> HTB{\%s}
:*3$"
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.8060
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
main.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
strncmp@@GLIBC_2.2.5
_ITM_deregisterTMCloneTable
puts@@GLIBC_2.2.5
sigaction@@GLIBC_2.2.5
_edata
strlen@@GLIBC_2.2.5
__stack_chk_fail@@GLIBC_2.4
printf@@GLIBC_2.2.5
memset@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
__data_start
segill_sigaction
sigemptyset@@GLIBC_2.2.5
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
__bss_start
main
__TMC_END__
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5
.symtab
.strtab
.shstrtab
.interp
.note.gnu.property
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.plt.sec
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment
fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ 

These 2 lines are interesting:

./challenge <password>
> HTB{%s}

It should print the flag through the variable / format specifier “%s”. There are also various other printf functions that also send formatted output to stdout.

Let’s try ltrace which intercepts and records the dynamic library calls which are called by the executed process and the signals which are received by that process.

fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ ltrace ./behindthescenes meow
--- SIGILL (Illegal instruction) ---
--- SIGILL (Illegal instruction) ---
--- SIGILL (Illegal instruction) ---
+++ exited (status 0) +++

Nothing interesting happens. Let’s take a look at strace now which lets you observe a given process in detail, printing its system calls as they occur.

fedai@ubuntu:~/hackthebox/Behind the Scenes/rev_behindthescenes$ strace ./behindthescenes meow
execve("./behindthescenes", ["./behindthescenes", "meow"], 0x7ffe27cdaba8 /* 51 vars */) = 0
brk(NULL)                               = 0x560176faf000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe8a9ae9e0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=68515, ...}) = 0
mmap(NULL, 68515, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffa988d9000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300A\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029592, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffa988d7000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
mmap(NULL, 2037344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffa986e5000
mmap(0x7ffa98707000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7ffa98707000
mmap(0x7ffa9887f000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19a000) = 0x7ffa9887f000
mmap(0x7ffa988cd000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7ffa988cd000
mmap(0x7ffa988d3000, 13920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ffa988d3000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7ffa988d8540) = 0
mprotect(0x7ffa988cd000, 16384, PROT_READ) = 0
mprotect(0x5601768c6000, 4096, PROT_READ) = 0
mprotect(0x7ffa98917000, 4096, PROT_READ) = 0
munmap(0x7ffa988d9000, 68515)           = 0
rt_sigaction(SIGILL, {sa_handler=0x5601768c4229, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7ffa98728090}, NULL, 8) = 0
--- SIGILL {si_signo=SIGILL, si_code=ILL_ILLOPN, si_addr=0x5601768c42e6} ---
rt_sigreturn({mask=[]})                 = 0
--- SIGILL {si_signo=SIGILL, si_code=ILL_ILLOPN, si_addr=0x5601768c430b} ---
rt_sigreturn({mask=[]})                 = 0
--- SIGILL {si_signo=SIGILL, si_code=ILL_ILLOPN, si_addr=0x5601768c4432} ---
rt_sigreturn({mask=[]})                 = 4
exit_group(0)                           = ?
+++ exited with 0 +++

Can’t see anything interesting.

Decompile - Ghidra

Time for Ghidra! I want to have a look at the decompiled code to understand what it’s doing.

I firstly look at main.

Line 16 on the decompiled code is just an infinite loop:

invalidInstructionException();

Looking at this function takes us to this assembly code:

It just says UD2. No idea what this is but looking it up explains that it’s some kind of error / invalid opcode - “This instruction is provided for software testing to explicitly generate an invalid opcode.

More research I found this article:

https://github.com/NationalSecurityAgency/ghidra/issues/4113

The submitted issue explains that ghidra “doesn’t go further than this instruction”, unlike objdump or gdb (other decompilers). This is true as we can see loads of uncompiled assembly code. Simply select this, right click, and decompile.

We can then see various strncmp operations which is used to compare at most the first n characters of two strings.

There are multiple strncmp operations, each one spells out a different section of the flag.

Itz_0nLy_UD2

Last updated