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
unzipthe binary, are are presented with a singularELFexecutable. -
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
stringsfor 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 familiarmain(). -
We also spot
srand()andrand(), which we will keep in mind. -
For now, we will checkout the
main()function. Weseek tomain()andprint-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 withVV, 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.

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

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()andrand()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
&checkpart 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.

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_dataarray - 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_dataarray
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 🙂