Intro Link to heading

Recently I stumbled upon HTB Challenges and figured I wanted to try some. In the past I only did full Machines and never really knew there were more “CTF-type” challenges available. I didn’t have too much time on my hands, so I randomly picked an easy RE challenge, FlagCasino. If you have some background in Reverse-Engineering, you will be familiar with all the topics covered here, so this is more a “reversing 101” kind of writeup.

Recon Link to heading

  • We download and unzip the binary, are are presented with a singular ELF executable.

  • Interestingly, it’s not stripped, but that doesn’t matter too much in this case.

1$ file casino
2casino: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7618b017ef4299337610a90a0a6ccb7f9efc44a4, for GNU/Linux 3.2.0, not stripped
  • Especially when a binary is not stripped, always check with strings for interesting content. In this case we can already see some printable characters and lines that indicate some sort of menu for us to navigate.
  1$ strings casino   
  2/lib64/ld-linux-x86-64.so.2
  3exit
  4srand
  5__isoc99_scanf
  6puts
  7printf
  8__cxa_finalize
  9__libc_start_main
 10libc.so.6
 11GLIBC_2.7
 12GLIBC_2.2.5
 13_ITM_deregisterTMCloneTable
 14__gmon_start__
 15_ITM_registerTMCloneTable
 16u/UH
 17[]A\A]A^A_
 18     ,     ,
 19    (\____/)
 20     (_oo_)
 21       (O)
 22     __||__    \)
 23  []/______\[] /
 24  / \______/ \/
 25 /    /__\
 26(\   /____\
 27---------------------
 28[ ** WELCOME TO ROBO CASINO **]
 29[*** PLEASE PLACE YOUR BETS ***]
 30[ * CORRECT *]
 31[ * INCORRECT * ]
 32[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]
 33[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]
 34;*3$"
 35e( 78C
 36 78C
 37j 78C7
 38"GCC: (Debian 10.2.1-6) 10.2.1 20210110
 39crtstuff.c
 40deregister_tm_clones
 41__do_global_dtors_aux
 42completed.0
 43__do_global_dtors_aux_fini_array_entry
 44frame_dummy
 45__frame_dummy_init_array_entry
 46main.c
 47__FRAME_END__
 48__init_array_end
 49_DYNAMIC
 50__init_array_start
 51__GNU_EH_FRAME_HDR
 52_GLOBAL_OFFSET_TABLE_
 53__libc_csu_fini
 54_ITM_deregisterTMCloneTable
 55puts@GLIBC_2.2.5
 56_edata
 57printf@GLIBC_2.2.5
 58banner
 59__libc_start_main@GLIBC_2.2.5
 60srand@GLIBC_2.2.5
 61__data_start
 62__gmon_start__
 63__dso_handle
 64_IO_stdin_used
 65__libc_csu_init
 66__bss_start
 67main
 68check
 69__isoc99_scanf@GLIBC_2.7
 70exit@GLIBC_2.2.5
 71__TMC_END__
 72_ITM_registerTMCloneTable
 73__cxa_finalize@GLIBC_2.2.5
 74.symtab
 75.strtab
 76.shstrtab
 77.interp
 78.note.gnu.build-id
 79.note.ABI-tag
 80.gnu.hash
 81.dynsym
 82.dynstr
 83.gnu.version
 84.gnu.version_r
 85.rela.dyn
 86.rela.plt
 87.init
 88.plt.got
 89.text
 90.fini
 91.rodata
 92.eh_frame_hdr
 93.eh_frame
 94.init_array
 95.fini_array
 96.dynamic
 97.got.plt
 98.data
 99.bss
100.comment
  • When we execute the binary, we are greeted by a cute little robot 🙂
 1$ ./casino 
 2[ ** WELCOME TO ROBO CASINO **]
 3     ,     ,
 4    (\____/)
 5     (_oo_)
 6       (O)
 7     __||__    \)
 8  []/______\[] /
 9  / \______/ \/
10 /    /__\
11(\   /____\
12---------------------
13[*** PLEASE PLACE YOUR BETS ***]
14> 
  • As always, we try with some bogus input to see how we fail.
1[*** PLEASE PLACE YOUR BETS ***]
2> A
3[ * INCORRECT * ]
4[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]

Analysis Link to heading

Now that we have a feel for the challenge, we need to gain a deeper understanding of its inner workings. To do that, we will at first take a cursory look with radare2, then use BinaryNinja to decompile the code.

radare2 Link to heading

I like to start challenges like this by breaking out radare2 and do some simple inspection.

We start by having r2 analyze the binary with aaaa, then inspect the functions with afl.

 1$ r2 casino
 2WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
 3 -- Enable asm.trace to see the tracing information inside the disassembly
 4[0x000010a0]> aaaa
 5INFO: Analyze all flags starting with sym. and entry0 (aa)
 6INFO: Analyze imports (af@@@i)
 7INFO: Analyze entrypoint (af@ entry0)
 8INFO: Analyze symbols (af@@@s)
 9INFO: Analyze all functions arguments/locals (afva@@@F)
10INFO: Analyze function calls (aac)
11INFO: Analyze len bytes of instructions for references (aar)
12INFO: Finding and parsing C++ vtables (avrr)
13INFO: Analyzing methods (af @@ method.*)
14INFO: Recovering local variables (afva@@@F)
15INFO: Type matching analysis for all functions (aaft)
16INFO: Propagate noreturn information (aanr)
17INFO: Scanning for strings constructed in code (/azs)
18INFO: Finding function preludes (aap)
19INFO: Enable anal.types.constraint for experimental type propagation
20[0x000010a0]> afl
210x00001030    1      6 sym.imp.puts
220x00001040    1      6 sym.imp.printf
230x00001050    1      6 sym.imp.srand
240x00001060    1      6 sym.imp.__isoc99_scanf
250x00001070    1      6 sym.imp.exit
260x00001080    1      6 sym.imp.rand
270x00001090    1      6 sym.imp.__cxa_finalize
280x000010a0    1     42 entry0
290x000010d0    4     34 sym.deregister_tm_clones
300x00001100    4     51 sym.register_tm_clones
310x00001140    5     50 entry.fini0
320x00001180    1      5 entry.init0
330x00001000    3     23 sym._init
340x000012e0    1      1 sym.__libc_csu_fini
350x000012e4    1      9 sym._fini
360x00001280    4     93 sym.__libc_csu_init
370x00001185    9    242 main
38[0x000010a0]> 
  • As expected, we see some imported libc functions like puts(), exit() and our familiar main().

  • We also spot srand() and rand(), which we will keep in mind.

  • For now, we will checkout the main() function. We seek to main() and print-disassemble-function.

 1[0x000010a0]> s main
 2[0x00001185]> pdf
 3            ; ICOD XREF from entry0 @ 0x10bd(r)
 4┌ 242: int main (int argc, char **argv, char **envp);
 5│ afv: vars(2:sp[0xc..0xd])
 6│           0x00001185      55             push rbp
 7│           0x00001186      4889e5         mov rbp, rsp
 8│           0x00001189      4883ec10       sub rsp, 0x10
 9│           0x0000118d      488d3d240f..   lea rdi, str.___WELCOME_TO_ROBO_CASINO__ ; 0x20b8 ; "[ ** WELCOME TO ROBO CASINO **]" ; const char *s
10│           0x00001194      e897feffff     call sym.imp.puts           ; int puts(const char *s)
11│           0x00001199      488d3d800e..   lea rdi, obj.banner         ; 0x2020 ; "     ,     ,\n    (\____/)\n     (_oo_)\n       (O)\n     __||__    \)\n  []/______\[] /\n  / \______/ \/\n /    /__\\n(\   /____\\n---------------------" ; const char *s
12│           0x000011a0      e88bfeffff     call sym.imp.puts           ; int puts(const char *s)
13│           0x000011a5      488d3d2c0f..   lea rdi, str.__PLEASE_PLACE_YOUR_BETS__ ; 0x20d8 ; "[*** PLEASE PLACE YOUR BETS ***]" ; const char *s
14│           0x000011ac      e87ffeffff     call sym.imp.puts           ; int puts(const char *s)
15│           0x000011b1      c745fc0000..   mov dword [var_4h], 0
16│       ┌─< 0x000011b8      e99b000000     jmp 0x1258
17│       │   ; CODE XREF from main @ 0x125e(x)
18│      ┌──> 0x000011bd      488d3d350f..   lea rdi, [0x000020f9]       ; "> " ; const char *format
19│      ╎│   0x000011c4      b800000000     mov eax, 0
20│      ╎│   0x000011c9      e872feffff     call sym.imp.printf         ; int printf(const char *format)
21│      ╎│   0x000011ce      488d45fb       lea rax, [var_5h]
22│      ╎│   0x000011d2      4889c6         mov rsi, rax
23│      ╎│   0x000011d5      488d3d200f..   lea rdi, [0x000020fc]       ; " %c" ; const char *format
24│      ╎│   0x000011dc      b800000000     mov eax, 0
25│      ╎│   0x000011e1      e87afeffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
26│      ╎│   0x000011e6      83f801         cmp eax, 1
27│     ┌───< 0x000011e9      740a           je 0x11f5
28│     │╎│   0x000011eb      bfffffffff     mov edi, 0xffffffff         ; -1 ; int status
29│     │╎│   0x000011f0      e87bfeffff     call sym.imp.exit           ; void exit(int status)
30│     │╎│   ; CODE XREF from main @ 0x11e9(x)
31│     └───> 0x000011f5      0fb645fb       movzx eax, byte [var_5h]
32│      ╎│   0x000011f9      0fbec0         movsx eax, al
33│      ╎│   0x000011fc      89c7           mov edi, eax                ; int seed
34│      ╎│   0x000011fe      e84dfeffff     call sym.imp.srand          ; void srand(int seed)
35│      ╎│   0x00001203      e878feffff     call sym.imp.rand           ; int rand(void)
36│      ╎│   0x00001208      8b55fc         mov edx, dword [var_4h]
37│      ╎│   0x0000120b      4863d2         movsxd rdx, edx
38│      ╎│   0x0000120e      488d0c9500..   lea rcx, [rdx*4]
39│      ╎│   0x00001216      488d15632e..   lea rdx, obj.check          ; 0x4080
40│      ╎│   0x0000121d      8b1411         mov edx, dword [rcx + rdx]
41│      ╎│   0x00001220      39d0           cmp eax, edx
42│     ┌───< 0x00001222      750e           jne 0x1232
43│     │╎│   0x00001224      488d3dd50e..   lea rdi, str.___CORRECT__   ; 0x2100 ; "[ * CORRECT *]" ; const char *s
44│     │╎│   0x0000122b      e800feffff     call sym.imp.puts           ; int puts(const char *s)
45│    ┌────< 0x00001230      eb22           jmp 0x1254
46│    ││╎│   ; CODE XREF from main @ 0x1222(x)
47│    │└───> 0x00001232      488d3dd60e..   lea rdi, str.___INCORRECT___ ; 0x210f ; "[ * INCORRECT * ]" ; const char *s
48│    │ ╎│   0x00001239      e8f2fdffff     call sym.imp.puts           ; int puts(const char *s)
49│    │ ╎│   0x0000123e      488d3de30e..   lea rdi, str.___ACTIVATING_SECURITY_SYSTEM___PLEASE_VACATE___ ; 0x2128 ; "[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]" ; const char *s
50│    │ ╎│   0x00001245      e8e6fdffff     call sym.imp.puts           ; int puts(const char *s)
51│    │ ╎│   0x0000124a      bffeffffff     mov edi, 0xfffffffe         ; 4294967294 ; int status
52│    │ ╎│   0x0000124f      e81cfeffff     call sym.imp.exit           ; void exit(int status)
53│    │ ╎│   ; CODE XREF from main @ 0x1230(x)
54│    └────> 0x00001254      8345fc01       add dword [var_4h], 1
55│      ╎│   ; CODE XREF from main @ 0x11b8(x)
56│      ╎└─> 0x00001258      8b45fc         mov eax, dword [var_4h]
57│      ╎    0x0000125b      83f81d         cmp eax, 0x1d
58│      └──< 0x0000125e      0f8659ffffff   jbe 0x11bd
59│           0x00001264      488d3df50e..   lea rdi, str.___HOUSE_BALANCE__0___PLEASE_COME_BACK_LATER___ ; 0x2160 ; "[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]" ; const char *s
60│           0x0000126b      e8c0fdffff     call sym.imp.puts           ; int puts(const char *s)
61│           0x00001270      b800000000     mov eax, 0
62│           0x00001275      c9             leave
63└           0x00001276      c3             ret
64[0x00001185]> 
  • radare2 is an awesome tool and we can quickly see a clear and annotated representation of our main() function. You can also print an ASCII-style graph view with VV, but we will do this later with BinaryNinja so we skip this for now.

If you have never done assembly before, it can look intimidating at first, but it’s always a good idea to take it slow and try to spot patterns and interesting function calls. Browsing the code a bit, we notice some interesting things like the jmp 0x1258 at addr 0x000011b8. We follow to this location and see the jump target at address 0x00001258, mov eax, dword [var_4h].

After that, we see a cmp eax, 0x1d followed by a conditional jbe 0x11bd which takes us right back to the top of the function. There we can see that it loads the address of a string into rdi, clears eax by setting it to zero and then does a printf with the format string > .

We obviously remember the output of our challenge binary, and specifically we note that it also uses > as the “shell prompt”.

 1$ ./casino 
 2[ ** WELCOME TO ROBO CASINO **]
 3     ,     ,
 4    (\____/)
 5     (_oo_)
 6       (O)
 7     __||__    \)
 8  []/______\[] /
 9  / \______/ \/
10 /    /__\
11(\   /____\
12---------------------
13[*** PLEASE PLACE YOUR BETS ***]
14>                                           <- see "> " string

We have obviously found some sort of input processing loop. As we want to speed up our analysis process, we will switch gears and continue our journey with BinaryNinja.

BinaryNinja Link to heading

Recently, I got to play with the commercial version of BinaryNinja and I must say I really like it. I don’t nearly do enough RE to justify an IDA Pro subscription and I’m too much of a noob to master radare2 :)

Anyhow, we open our binary, check out the list of functions and find our main(). We set the display mode to Pseudo-C and enjoy BinaryNinjas decompilation.

bc

To get a better overview, we checkout the graphing too.

bg

I won’t go over this in the same detail again, but I think the structure is becoming pretty clear by now. In short, what does this function do ?

1prints ASCII header
2
3for each loop iteration (loops from 0 - 0x1d)
4    read byte (char) from input
5    seed the RNG with it
6    get a random number
7    compare that with a hardcoded value
8    if that fails (i.e. they are not the same) it activates the security system

Two things stand out here I want to highlight seed the RNG with it and compare that with a hardcoded value.

  • At this point its good to remember our note about srand() and rand() and break out the manpage.

Looking at it, we skip over the definitions and check the description. In there we find this remarkable sentence.

1The srand() function sets its argument as the seed for a new
2sequence of pseudo-random integers to be returned by rand().
3These sequences are repeatable by calling srand() with the same
4seed value.

In case you don’t speak manpage, lets simplify this:

1same seed == same random values

While this might look small and innocent, this is the true “vulnerability” this challenge is built upon. It even has a scary sounding MITRE definition, CWE-335: Incorrect Usage of Seeds in Pseudo-Random Number Generator (PRNG), yikes !

  • The second thing of note is the &check part in
1if (rand() != *(uint32_t*)(((int64_t)var_c << 2) + &check))

With BinaryNinja its easy enough to double click the symbol and be taken straight to where it’s defined.

bb

We see a bunch of raw bytes !

As we know from earlier (even though it might be hard to decipher) these are ultimately unsigned 32-bit integers in Little-Endian, aka uint32_t.

Using BinaryNinja comes in handy here because we can just dump these bytes to the right format.

 1const uint32_t flag_data[30] = 
 2{
 3	0x244b28be, 0x0af77805, 0x110dfc17, 0x07afc3a1,
 4	0x6afec533, 0x4ed659a2, 0x33c5d4b0, 0x286582b8,
 5	0x43383720, 0x055a14fc, 0x19195f9f, 0x43383720,
 6	0x19195f9f, 0x747c9c5e, 0x0f3da237, 0x615ab299,
 7	0x6afec533, 0x43383720, 0x0f3da237, 0x6afec533,
 8	0x615ab299, 0x286582b8, 0x055a14fc, 0x3ae44994,
 9	0x06d7dfe9, 0x4ed659a2, 0x0ccd4acd, 0x57d8ed64,
10	0x615ab299, 0x22e9bc2a
11};

Solver Link to heading

To quickly recap, remember the “algorithm” we constructed ?

1for each loop iteration (loops from 0 - 0x1d)
2    read byte (char) from input
3    seed the RNG with it
4    get a random number
5    compare that with a hardcoded value
6    if that fails (i.e. they are not the same) it activates the security system

We now have everything together to build our solver:

  • loop over the flag_data array
  • for each value there, start a loop in the ASCII (1-byte) range (0-255)
  • seed the RNG with srand() each time and get the rand() value
  • check that value against the data in the flag_data array

Its simple enough to do in C, you can grab the code here.

 1#include <stdio.h>
 2#include <stdint.h>
 3#include <stdlib.h>
 4#include <inttypes.h>
 5
 6// this encodes the flag
 7// 
 8// - after the srand() and rand() calls bp at 0x5..208
 9//   0x5555555551fe <main+0079>      call   0x555555555050 <srand@plt>
10//   0x555555555203 <main+007e>      call   0x555555555080 <rand@plt>
11//  *0x555555555208 <main+0083>      mov    edx, DWORD PTR [rbp-0x4]
12//   0x55555555520b <main+0086>      movsxd rdx, edx
13// 
14// - $rax contains the value returned by rand() call
15// 
16// $rax   : 0x23a8da61        
17// $rbx   : 0x00007fffffffde18  →  0x00007fffffffe166  →  "/home/[...]"
18// $rcx   : 0x00007ffff7f9d1e8  →  0x51e20d9140004e20 (" N"?)
19// 
20// - in the example above, 0x23a8da61 corresponds to an 'A' entered
21const uint32_t flag_data[30] = 
22{
23    // beginning of the HTB{...} string
24    //  H           T           B           {
25	0x244b28be, 0x0af77805, 0x110dfc17, 0x07afc3a1,
26	0x6afec533, 0x4ed659a2, 0x33c5d4b0, 0x286582b8,
27	0x43383720, 0x055a14fc, 0x19195f9f, 0x43383720,
28	0x19195f9f, 0x747c9c5e, 0x0f3da237, 0x615ab299,
29	0x6afec533, 0x43383720, 0x0f3da237, 0x6afec533,
30	0x615ab299, 0x286582b8, 0x055a14fc, 0x3ae44994,
31	0x06d7dfe9, 0x4ed659a2, 0x0ccd4acd, 0x57d8ed64,
32	0x615ab299, 0x22e9bc2a
33};
34
35int main(void) {
36    size_t flag_data_len = sizeof(flag_data) / sizeof(flag_data[0]);
37    char flag[32] = {0};
38
39    // loop over the data
40    for (int i = 0; i < flag_data_len; i++) {
41
42        // grab flag data
43        uint32_t val = flag_data[i];
44        printf("[+] checking value  : 0x%02x\n", val);
45
46        // loop all possible ascii chars
47        for (uint32_t c = 0; c <= 255; c++) {
48
49            // seed the RNG and call rand()
50            srand(c);
51            uint32_t rnd = (uint32_t)rand();
52
53            // compare until we match
54            if (rnd == val) {
55                printf("[+] found flag char : %c\n", c);
56                flag[i] = (char)c;
57                break;
58            }
59        }
60    }
61
62    printf("[*] flag : %s\n", flag);
63
64    return 0;
65}
  • we build an run the solver
 1$ ./solve 
 2[+] checking value  : 0x244b28be
 3[+] found flag char : H
 4[+] checking value  : 0xaf77805
 5[+] found flag char : T
 6[+] checking value  : 0x110dfc17
 7[+] found flag char : B
 8[+] checking value  : 0x7afc3a1
 9[+] found flag char : {
10[+] checking value  : 0x6afec533
11[+] found flag char : r
12[+] checking value  : 0x4ed659a2
13[+] found flag char : 4
14[+] checking value  : 0x33c5d4b0
15[+] found flag char : n
16[+] checking value  : 0x286582b8
17[+] found flag char : d
18[+] checking value  : 0x43383720
19[+] found flag char : _
20[+] checking value  : 0x55a14fc
21[+] found flag char : 1
22[+] checking value  : 0x19195f9f
23[+] found flag char : s
24[+] checking value  : 0x43383720
25[+] found flag char : _
26[+] checking value  : 0x19195f9f
27[+] found flag char : s
28[+] checking value  : 0x747c9c5e
29[+] found flag char : u
30[+] checking value  : 0xf3da237
31[+] found flag char : p
32[+] checking value  : 0x615ab299
33[+] found flag char : 3
34[+] checking value  : 0x6afec533
35[+] found flag char : r
36[+] checking value  : 0x43383720
37[+] found flag char : _
38[+] checking value  : 0xf3da237
39[+] found flag char : p
40[+] checking value  : 0x6afec533
41[+] found flag char : r
42[+] checking value  : 0x615ab299
43[+] found flag char : 3
44[+] checking value  : 0x286582b8
45[+] found flag char : d
46[+] checking value  : 0x55a14fc
47[+] found flag char : 1
48[+] checking value  : 0x3ae44994
49[+] found flag char : c
50[+] checking value  : 0x6d7dfe9
51[+] found flag char : t
52[+] checking value  : 0x4ed659a2
53[+] found flag char : 4
54[+] checking value  : 0xccd4acd
55[+] found flag char : b
56[+] checking value  : 0x57d8ed64
57[+] found flag char : l
58[+] checking value  : 0x615ab299
59[+] found flag char : 3
60[+] checking value  : 0x22e9bc2a
61[+] found flag char : }
62[*] flag : HTB{r4nd_1s_sup3r_pr3d1ct4bl3}
63$ 

We get the flag and agree with the sentiment 🙂