HTB Reversing Writeup: BehindTheScenes, Exatlon

A quick writeup of two very easy reversing challenges. It’s been a rather long time since I’ve done one of these and wanted to dip my toes back in. I’m putting two into the same writeup here, because they’re both pretty straightforward.


BehindTheScenes

After struggling to secure our secret strings for a long time, we finally figured out the solution to our problem: Make decompilation harder. It should now be impossible to figure out how our programs work!

This binary is very trivial, but we’ll explain the steps anyway. As always, run it to see what it does first:

» ./behindthescenes                                                                 
./challenge <password>
» ./behindthescenes xyz
»

Both times, we get a 0 exit code. The assumption is we’re supposed to pass it a specific value, then. Moving onwards, pop it open in a disassembler, jump to main, and dump a disassembly summary:

» r2 -e bin.cache=true -Aq -c 's main' -c 'pds 1000' behindthescenes
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Use -AA or aaaa to perform additional experimental analysis
0x00001270 argc
0x00001276 argv
0x00001293 size_t n
0x00001298 int c
0x0000129d void *s
0x000012a0 call sym.imp.memset
0x000012b3 call sym.imp.sigemptyset
0x000012b8 sym.segill_sigaction]
0x000012d4 struct sigaction *oldact
0x000012d9 const struct sigaction *act
0x000012dc int signum
0x000012e1 call sym.imp.sigaction
0x000012f3 str.._challenge__password_
0x000012fa call sym.imp.puts
0x0000131e call sym.imp.strlen
0x00001342 "Itz"
0x0000134c call sym.imp.strncmp
0x01010246 "", "Itz"
0x00001369 "@8\r@\x1f\x1e\x06"
0x00001372 "_0n"
0x00001379 "@8\r@\x1f\x1e\x06"
0x0000137c call sym.imp.strncmp
0x01010246 "@", "_0n"
0x000013a2 "Ly_"
0x000013ac call sym.imp.strncmp
0x01010246 "", "Ly_"
0x000013c5 "@\x1f\x1e\x06"
0x000013ce "UD2"
0x000013d5 "@\x1f\x1e\x06"
0x000013d8 call sym.imp.strncmp
0x01010246 "@", "UD2"
0x000013f4 str.__HTB_s_n
0x00001400 call sym.imp.printf
0x00001448 call sym.imp.__stack_chk_fail
0x00001456 obj.__frame_dummy_init_array_entry
0x00003d80 [21] -rw- section size 8 named .init_array
0x0000145f arg3
0x00001464 arg2
0x00001469 arg1
0x00001469 str.__HTB_s_n
0x0000146d obj.__do_global_dtors_aux_fini_array_entry
0x00003d88 [22] -rw- section size 8 named .fini_array
0x0000147c call sym._init
0x00001496 str.__HTB_s_n
0x00001499 call qword [r15 + rbx*8]
;-- section..fini:
;-- .fini:
0x000014c8 [17] -r-x section size 13 named .fini

Very funny looking partial strings going on here; looks like we can make out Itz, _0n, Ly_, and UD2. Too coincidental not to try; we plug it in:

» ./behindthescenes Itz_0nLy_UD2
> HTB{Itz_0nLy_UD2}

Well that was easy :)


Exatlon

Can you find the password?

Marginally harder, this one, but nothing nasty in it. Again, try it out first:

» ./exatlon_v1 

███████╗██╗  ██╗ █████╗ ████████╗██╗      ██████╗ ███╗   ██╗       ██╗   ██╗ ██╗
██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║     ██╔═══██╗████╗  ██║       ██║   ██║███║
█████╗   ╚███╔╝ ███████║   ██║   ██║     ██║   ██║██╔██╗ ██║       ██║   ██║╚██║
██╔══╝   ██╔██╗ ██╔══██║   ██║   ██║     ██║   ██║██║╚██╗██║       ╚██╗ ██╔╝ ██║
███████╗██╔╝ ██╗██║  ██║   ██║   ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝  ██║
╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝ ╚═══╝   ╚═╝


[+] Enter Exatlon Password  : WhyWereTheBakersHandsBrown?
[-] ;(


███████╗██╗  ██╗ █████╗ ████████╗██╗      ██████╗ ███╗   ██╗       ██╗   ██╗ ██╗
██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║     ██╔═══██╗████╗  ██║       ██║   ██║███║
█████╗   ╚███╔╝ ███████║   ██║   ██║     ██║   ██║██╔██╗ ██║       ██║   ██║╚██║
██╔══╝   ██╔██╗ ██╔══██║   ██║   ██║     ██║   ██║██║╚██╗██║       ╚██╗ ██╔╝ ██║
███████╗██╔╝ ██╗██║  ██║   ██║   ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝  ██║
╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝ ╚═══╝   ╚═╝


[+] Enter Exatlon Password  :

The banner prints out very slowly. Fine. Kick things off by dumping strings:

» rabin2 -z exatlon_v1 
[Strings]
nth paddr vaddr len size section type string
――――――――――――――――――――――――――――――――――――――――――――
»

Huh. Try again with a more aggressive search:

» rabin2 -zzz exatlon_v1 
000 0x000000ec 0x000000ec   7   8 () ascii UPX!<\t\r
001 0x0000012b 0x0000012b   4   5 () ascii !E&8
002 0x000001a5 0x000001a5   5   6 () ascii \rC}Rn
...
»

The string at the top tells us it’s UPX packed (there are a bunch more “UPX” strings throughout). We cross our fingers that it’s a nice easy one to unpack, and then try again.

» upx -d exatlon_v1            
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX git-33cdcb  Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   2210504 <-    709524   32.10%   linux/amd64   exatlon_v1

Unpacked 1 file.

WARNING: this is an unstable beta version - use for testing only! Really.
» rabin2 -z exatlon_v1           
[Strings]
nth  paddr      vaddr      len size section type    string
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
0    0x0014b018 0x0054b018 81  192  .rodata utf8    ███████╗██╗  ██╗ █████╗ ████████╗██╗      ██████╗ ███╗   ██╗       ██╗   ██╗ ██╗\n blocks=Block Elements,Box Drawing,Basic Latin
1    0x0014b0d8 0x0054b0d8 81  208  .rodata utf8    ██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║     ██╔═══██╗████╗  ██║       ██║   ██║███║\n blocks=Block Elements,Box Drawing,Basic Latin
2    0x0014b1a8 0x0054b1a8 81  184  .rodata utf8    █████╗   ╚███╔╝ ███████║   ██║   ██║     ██║   ██║██╔██╗ ██║       ██║   ██║╚██║\n blocks=Block Elements,Box Drawing,Basic Latin
3    0x0014b260 0x0054b260 81  188  .rodata utf8    ██╔══╝   ██╔██╗ ██╔══██║   ██║   ██║     ██║   ██║██║╚██╗██║       ╚██╗ ██╔╝ ██║\n blocks=Block Elements,Box Drawing,Basic Latin
4    0x0014b320 0x0054b320 81  218  .rodata utf8    ███████╗██╔╝ ██╗██║  ██║   ██║   ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝  ██║\n blocks=Block Elements,Box Drawing,Basic Latin
5    0x0014b400 0x0054b400 83  208  .rodata utf8    ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝ ╚═══╝   ╚═╝\n\n\n blocks=Box Drawing,Basic Latin
6    0x0014b4d0 0x0054b4d0 30  31   .rodata ascii   [+] Enter Exatlon Password  : 
7    0x0014b4f0 0x0054b4f0 170 171  .rodata ascii   1152 1344 1056 1968 1728 816 1648 784 1584 816 1728 1520 1840 1664 784 1632 1856 1520 1728 816 1632 1856 1520 784 1760 1840 1824 816 1584 1856 784 1776 1760 528 528 2000 
8    0x0014b59b 0x0054b59b 22  23   .rodata ascii   [+] Looks Good ^_^ \n\n\n
9    0x0014b5b4 0x0054b5b4 7   8    .rodata ascii   [-] ;(\n
10   0x0014b5c0 0x0054b5c0 41  42   .rodata ascii   basic_string::_M_construct null not valid
11   0x0014b620 0x0054b620 29  30   .rodata ascii   terminate called recursively\n
12   0x0014b641 0x0054b641 11  12   .rodata ascii     what():  
13   0x0014b650 0x0054b650 48  49   .rodata ascii   terminate called after throwing an instance of '
...

Much more like it. Let’s fire up r2 and work out where this weird looking string at 0x0054b4d0 (7th on the list) is being used.

[0x00404990]> axl 0x0054b4f0
Do you want to print 124459 lines? (y/N)

Okay, maybe not like that. Fire up a debugger, enter a string we can search for, and see where it gets used.

» gdb exatlon_v1                       
GNU gdb (GDB) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
92 commands loaded for GDB 12.1 using Python engine 3.10
Reading symbols from exatlon_v1...
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in exatlon_v1)
gef➤  r
Starting program: /home/░░░/src/hackthebox/re/exatlon/exatlon_v1 

███████╗██╗  ██╗ █████╗ ████████╗██╗      ██████╗ ███╗   ██╗       ██╗   ██╗ ██╗
██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║     ██╔═══██╗████╗  ██║       ██║   ██║███║
█████╗   ╚███╔╝ ███████║   ██║   ██║     ██║   ██║██╔██╗ ██║       ██║   ██║╚██║
██╔══╝   ██╔██╗ ██╔══██║   ██║   ██║     ██║   ██║██║╚██╗██║       ╚██╗ ██╔╝ ██║
███████╗██╔╝ ██╗██║  ██║   ██║   ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝  ██║
╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝ ╚═══╝   ╚═╝


[+] Enter Exatlon Password  : WhyWereTheBakersHandsBrown?
[-] ;(


███████╗██╗  ██╗ █████╗ ████████╗██╗      ██████╗ ███╗   ██╗       ██╗   ██╗ ██╗
██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║     ██╔═══██╗████╗  ██║       ██║   ██║███║
█████╗   ╚███╔╝ ███████║   ██║   ██║     ██║   ██║██╔██╗ ██║       ██║   ██║╚██║
██╔══╝   ██╔██╗ ██╔══██║   ██║   ██║     ██║   ██║██║╚██╗██║       ╚██╗ ██╔╝ ██║
███████╗██╔╝ ██╗██║  ██║   ██║   ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝  ██║
╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝ ╚═══╝   ╚═╝


[+] Enter Exatlon Password  :

Interrupt the program, and take a look around in GDB. Looks like our string is loaded into $rsp. It also appeared in the heap at 0x5c2ba0, so we set a watch point there:

gef➤  search-pattern WhyWereThe
[+] Searching 'WhyWereThe' in memory
[+] In '[heap]'(0x5af000-0x5d2000), permission=rw-
  0x5c2ba0 - 0x5c2bbd  →   "WhyWereTheBakersHandsBrown?\n" 
gef➤  awatch *(int *) 0x5c2ba0
Hardware access (read/write) watchpoint 1: *(int *) 0x5c2ba0
gef➤  c
Continuing.
BecauseHeKneadedAShit

Our breakpoint fires, and the stack trace tells us we’re at 0x404d16. Back to our r2 instance, we check what’s going on there.

[0x00404990]> s 0x404d16
[0x00404d16]> pds 100
0x00404d1e int64_t arg2
0x00404d21 int64_t arg1
0x00404d24 call sym exatlon(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
0x00404d2d int64_t arg2
0x00404d2d str.1152_1344_1056_1968_1728_816_1648_784_1584_816_1728_1520_1840_1664_784_1632_1856_1520_1728_816_1632_1856_1520_784_1760_1840_1824_816_1584_1856_784_1776_1760_528_528_2000_
0x00404d34 int64_t arg1
0x00404d37 call method bool std::operator==<char, std::char_traits<char>, std::allocator<char> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const*)
0x00404d42 uint32_t arg1
0x00404d45 call method std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
0x00404d4e int64_t arg2
0x00404d4e str.___Looks_Good____n_n_n
0x00404d55 int64_t arg1
0x00404d55 obj.std::cout
0x00404d5c call method std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
0x00404d64 "ATUH\x83\xec\bH\x8b\aH\x8b@\xe8L\x8b\xa4\a\xf0"
0x00404d6e int64_t arg2
0x00404d71 call method std::ostream::operator<<(std::ostream& (*)(std::ostream&))

It’s being read, then this exatlon function is being called, there’s a reference to that weird string we saw in our rabin2 output, and then it does a comparison. The smart money would be on either the weird string being decrypted, or our string being encrypted, before this comparison. Let’s put a breakpoint just at the equality check, and see what happens.

gef➤  delete breakpoint 1
gef➤  b *0x00404d37
Breakpoint 2 at 0x404d37
gef➤  c

We immediately hit the new breakpoint; our registers look like this:

$rax   : 0x00007fffffffd9a0  →  0x00000000005c3060  →  "1056 1616 1584 1552 1872 1840 1616 1152 1616 1200 [...]"
$rbx   : 0x1               
$rcx   : 0x20363538        
$rdx   : 0x00007fffffffd8f8  →  0x00000000005c2fc5  →  0x00003f6e776f7200
$rsp   : 0x00007fffffffd980  →  0x00000000005c2fb0  →  "BecauseHeKneadedAShit"
$rbp   : 0x00007fffffffd9d0  →  0x000000000049eb50  →  <__libc_csu_init+0> push r15
$rsi   : 0x000000000054b4f0  →  "1152 1344 1056 1968 1728 816 1648 784 1584 816 172[...]"
$rdi   : 0x00007fffffffd9a0  →  0x00000000005c3060  →  "1056 1616 1584 1552 1872 1840 1616 1152 1616 1200 [...]"
$rip   : 0x0000000000404d37  →  <main+267> call 0x4050fa <_ZSteqIcSt11char_traitsIcESaIcEEbRKNSt7__cxx1112basic_stringIT_T0_T1_EEPKS5_>
$r8    : 0x00007fffffffd940  →  0x0000002036353800
$r9    : 0x64              
$r10   : 0x00007fffffffd5a4  →  0x3f38480036353831 ("1856"?)
$r11   : 0x0               
$r12   : 0x000000000049ebe0  →  <__libc_csu_fini+0> push rbp
$r13   : 0x0               
$r14   : 0x00000000005a8018  →  0x00000000004d6f10  →  <__rawmemchr_avx2+0> mov ecx, edi
$r15   : 0x0               
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000

So $rsi is holding the weird string. The other argument to the comparison call is in $rax; note that the 1st, 6th, and 8th numbers are repeated (0 indexing). This indicates it might line up with our input:

1056 1616 1584 1552 1872 1840 1616 1152 1616 1200 [...]
B    e    c    a    u    s    e    H    e    N    [...]

So our string has been encrypted, apparently in a position-invariant way. We presumably want to decrypt the string in the binary. This position-invariance means we can build a backmap of the values by studying the effect on a string of all possible ASCII characters, e.g.:

python3 -c 'import string; print(string.printable)'
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ 	



We copy this, minus the newlines and carriage returns, and then feed it to the program in GDB. The breakpoint triggers again, and we extract the sequence of numbers with

gef➤  p -elements unlimited -- *(char**) $rax
$10 = 0x5c3280 "768 784 800 816 832 848 864 880 896 912 1552 1568 1584 1600 1616 1632 1648 1664 1680 1696 1712 1728 1744 1760 1776 1792 1808 1824 1840 1856 1872 1888 1904 1920 1936 1952 1040 1056 1072 1088 1104 1120 1136 1152 1168 1184 1200 1216 1232 1248 1264 1280 1296 1312 1328 1344 1360 1376 1392 1408 1424 1440 528 544 560 576 592 608 624 640 656 672 688 704 720 736 752 928 944 960 976 992 1008 1024 1456 1472 1488 1504 1520 1536 1968 1984 2000 2016 "

Over to Python. We copy this and the encrypted string in (helpful shortcut: after copying in the space-separated string, :.s/ /,/g will do a search and replace on a single line, in vim), and use our new information to back substitute the numbers in the encrypted string.

import string

s=string.printable
i=[768,784,800,816,832,848,864,880,896,912,1552,1568,1584,1600,1616,1632,1648,1664,1680,1696,1712,1728,1744,1760,1776,1792,1808,1824,1840,1856,1872,1888,1904,1920,1936,1952,1040,1056,1072,1088,1104,1120,1136,1152,1168,1184,1200,1216,1232,1248,1264,1280,1296,1312,1328,1344,1360,1376,1392,1408,1424,1440,528,544,560,576,592,608,624,640,656,672,688,704,720,736,752,928,944,960,976,992,1008,1024,1456,1472,1488,1504,1520,1536,1968,1984,2000,2016]

backmap = { i[it]: s[it] for it in range(len(i)) }
enc=[1152,1344,1056,1968,1728,816,1648,784,1584,816,1728,1520,1840,1664,784,1632,1856,1520,1728,816,1632,1856,1520,784,1760,1840,1824,816,1584,1856,784,1776,1760,528,528,2000]
dec = [ backmap[it] for it in enc ]

print("".join(dec))

And that’s a flag :)