Pages

Categories

DFIR – Hack The Box – FlagCasino

Hack the box provides a collection of Capture The Flag (CTF) style challenges focused on cyber security, digital forensics, and incident response. The objective is to obtain the flag, typically a small message/password, as proof you were able to complete the challenge.

Challenge: FlagCasino

FlagCasino is a reverse engineering challenge on a binary executable. The description conveys it’s a game you are to play and gain funds. Let’s dive in.

Downloads

The CTF provides the following scenario files:

FilenameMD5
rev_flagcasino.zip24F23D0194B0CA3A3550884F286DE53B
rev_flagcasino.zip/Casino0478C3A259655AFCD1CC67E808C20861

Our interest is in the Casino file.

Static Analysis

There’s no extension, so the first thing we can do is check the header of the file.

PowerShell:
((Get-Content .\casino -Encoding Byte -TotalCount 50) | ForEach-Object { [char] $_ }) -join ""
ELF> @(;

An Executable Linkable Format (ELF) is a Linux based binary executable/library. Let’s further analyse the file in Detect It Easy:

Detect It Easy v3.10 – .\casino
.\casino 78 msec
ELF64
Operation system: Debian Linux(ABI: 3.2.0)[AMD64, 64-bit, DYN]
Library: GLIBC(2.7)[DYN AMD64-64]
Language: C
Compiler: GCC((Debian 10.2.1-6) 10.2.1 20210110)
Overlay: Binary[Offset=0x4000,Size=0x02a8]
Unknown: Unknown

This gave us some more useful information, it’s a x64 architecture binary, written in C, and compiled with GCC.

An area of initial interest will be the strings stored within the file. There are many methods to extract the strings, many applications simply run searches for consecutive printable characters within a specific threshold; larger than 3 characters, less than 50.

Upon first look, we can see the following strings which were extracted with Detect It Easy:

Strings:
Address            Size    Type    String
02a8 PT_LOAD(0)    1b      A       /lib64/ld-linux-x86-64.so.2
0456 PT_LOAD(0)    05      A       srand
045c PT_LOAD(0)    0e      A       __isoc99_scanf
0470 PT_LOAD(0)    06      A       printf
0477 PT_LOAD(0)    0e      A       __cxa_finalize
0486 PT_LOAD(0)    11      A       __libc_start_main
0498 PT_LOAD(0)    09      A       libc.so.6
04a2 PT_LOAD(0)    09      A       GLIBC_2.7
04ac PT_LOAD(0)    0b      A       GLIBC_2.2.5
04b8 PT_LOAD(0)    1b      A       _ITM_deregisterTMCloneTable
04d4 PT_LOAD(0)    0e      A       __gmon_start__
04e3 PT_LOAD(0)    19      A       _ITM_registerTMCloneTable
12d2 PT_LOAD(1)    0a      A       []A\A]A^A_
3020 PT_LOAD(2)    0c      A            ,     ,
302d PT_LOAD(2)    0c      A           (\____/)
303a PT_LOAD(2)    0b      A            (_oo_)
3046 PT_LOAD(2)    0a      A              (O)
3051 PT_LOAD(2)    11      A            __||__    \)
3063 PT_LOAD(2)    10      A         []/______\[] /
3074 PT_LOAD(2)    0f      A         / \______/ \/
3084 PT_LOAD(2)    0a      A        /    /__\
308f PT_LOAD(2)    0b      A       (\   /____\
309b PT_LOAD(2)    15      A       ---------------------
30b8 PT_LOAD(2)    1f      A       [ ** WELCOME TO ROBO CASINO **]
30d8 PT_LOAD(2)    20      A       [*** PLEASE PLACE YOUR BETS ***]
3100 PT_LOAD(2)    0e      A       [ * CORRECT *]
310f PT_LOAD(2)    11      A       [ * INCORRECT * ]
3128 PT_LOAD(2)    36      A       [ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]
3160 PT_LOAD(2)    33      A       [ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]

We can see the same data running the `readelf` command on Linux to obtain the read only data section (.rodata) of the ELF file:

Command: readelf -x .rodata ./casino
0x00002000 01000200 00000000 00000000 00000000 ................
0x00002010 00000000 00000000 00000000 00000000 ................
0x00002020 20202020 202c2020 2020202c 0a202020      ,     ,.  
0x00002030 20285c5f 5f5f5f2f 290a2020 20202028  (\____/).     (
0x00002040 5f6f6f5f 290a2020 20202020 20284f29 _oo_).       (O)
0x00002050 0a202020 20205f5f 7c7c5f5f 20202020 .     __||__   
0x00002060 5c290a20 205b5d2f 5f5f5f5f 5f5f5c5b \).  []/______\[
0x00002070 5d202f0a 20202f20 5c5f5f5f 5f5f5f2f ] /.  / \______/
0x00002080 205c2f0a 202f2020 20202f5f 5f5c0a28  \/. /    /__\.(
0x00002090 5c202020 2f5f5f5f 5f5c0a2d 2d2d2d2d \   /____\.-----
0x000020a0 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d ----------------
0x000020b0 00000000 00000000 5b202a2a 2057454c ........[ ** WEL
0x000020c0 434f4d45 20544f20 524f424f 20434153 COME TO ROBO CAS
0x000020d0 494e4f20 2a2a5d00 5b2a2a2a 20504c45 INO **].[*** PLE
0x000020e0 41534520 504c4143 4520594f 55522042 ASE PLACE YOUR B
0x000020f0 45545320 2a2a2a5d 003e2000 20256300 ETS ***].> . %c.
0x00002100 5b202a20 434f5252 45435420 2a5d005b [ * CORRECT *].[
0x00002110 202a2049 4e434f52 52454354 202a205d  * INCORRECT * ]
0x00002120 00000000 00000000 5b202a2a 2a204143 ........[ *** AC
0x00002130 54495641 54494e47 20534543 55524954 TIVATING SECURIT
0x00002140 59205359 5354454d 202d2050 4c454153 Y SYSTEM - PLEAS
0x00002150 45205641 43415445 202a2a2a 205d0000 E VACATE *** ]..
0x00002160 5b202a2a 20484f55 53452042 414c414e [ ** HOUSE BALAN
0x00002170 43452024 30202d20 504c4541 53452043 CE $0 - PLEASE C
0x00002180 4f4d4520 4241434b 204c4154 4552202a OME BACK LATER *
0x00002190 2a205d00                            * ].

Of course it would be too easy if the flag was present in plaint text here.

We have a Linux executable, and based on the strings we can see the basic premise of the game: a bet is placed, it’s correct or incorrect and there may be a condition which ends the game prematurely, or we’ve emptied the house funds from winning.

Dynamic Analysis

Let’s perform some dynamic analysis. A Linux Ubuntu 24.04.2 LTS virtual machine is setup, the casino file transferred across, and executed within a terminal window:

Output:
jamie@VM-Linux-Ubuntu:~/Documents$ ./casino
[ ** WELCOME TO ROBO CASINO **]
     ,     ,
    (\____/)
     (_oo_)
       (O)
     __||__    \)
  []/______\[] /
  / \______/ \/
 /    /__\
(\   /____\
---------------------
[*** PLEASE PLACE YOUR BETS ***]
> 0
[ * INCORRECT * ]
[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]

As presumed, the game asks for a bet, and a correct/incorrect response is given. Upon the incorrect bet, the game ended immediately. A few more attempts were made with numbers (small, large, negative, positive), letters, punctuation, all of various lengths but to no correct response being given. This Blackbox style of experimentation is not getting any further in obtaining the flag. We could go deeper with `gdb` and `ltrace`:

gdb Output:
jamie@VM-Linux-Ubuntu:~/Documents$ gdb casino
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Debuginfod has been disabled.
(No debugging symbols found in casino)
(gdb) d main
(gdb) r
Starting program: /home/jamie/Documents/casino
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[ ** WELCOME TO ROBO CASINO **]
     ,     ,
    (\____/)
     (_oo_)
       (O)
     __||__    \)
  []/______\[] /
  / \______/ \/
 /    /__\
(\   /____\
---------------------
[*** PLEASE PLACE YOUR BETS ***]
> 0
[ * INCORRECT * ]
[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]
[Inferior 1 (process 27979) exited with code 0376]
ltrace Output:
jamie@VM-Linux-Ubuntu:~$ ltrace ./casino
puts("[ ** WELCOME TO ROBO CASINO **]"[ ** WELCOME TO ROBO CASINO **]
)          = 32
puts("     ,     ,\n    (\\____/)\n     ("...     ,     ,
    (\____/)
     (_oo_)
       (O)
     __||__    \)
  []/______\[] /
  / \______/ \/
 /    /__\
(\   /____\
---------------------
)   = 145
puts("[*** PLEASE PLACE YOUR BETS ***]"...[*** PLEASE PLACE YOUR BETS ***]
)      = 33
printf("> ")                                     = 2
__isoc99_scanf(0x5b10c12660fc, 0x7ffd949f542b, 0, 0> 0
) = 1
srand(48, 0, 0, 0)                               = 1
rand(0x7db938a030a0, 0xffffffff, 0x7db938a03024, 0x7db938a036a0) = 0x5788e9e7
puts("[ * INCORRECT * ]"[ * INCORRECT * ]
)                        = 18
puts("[ *** ACTIVATING SECURITY SYSTEM"...[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]
)      = 55
exit(-2 <no return ...>
+++ exited (status 254) +++

All we gained from this is the application uses the functions `puts`, `printf`, `scanf`, `srand`, `rand`, and `exit`.

We need to figure out the game condition of what is considered a correct or incorrect response.

Decompilation & Reversing

The application is processed within Ghidra 11.3.2 using the standard analysers.

Through Ghidra, we navigate to the main entry point of the application. The tool has done a good job at decompiling the raw byte code to its C equivalent, and walking through the code we can rename some of the variables to give more meaning:

int main(void)
{
    int int_ValidationCheck;
    char char_UserInput;
    uint index;
    puts("[ ** WELCOME TO ROBO CASINO **]");
    puts(
       "     ,     ,\n" +
       "    (\\____/)\n" +
       "     (_oo_)\n" +
       "       (O)\n" +
       "     __||__    \\)\n" +
       "  []/______\\[] /\n" +
       "  / \\______/ \\/\n" +
       " /    /__\\\n" +
       "(\\   /____\\\n" +
    "---------------------");
    puts("[*** PLEASE PLACE YOUR BETS ***]");
    index = 0;
    while(true)
    {
        // `0x1c` = 28
        if (0x1c < index)
        {
            puts("[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]");
            return 0;
        }
        printf("> ");
        int_ValidationCheck = __isoc99_scanf(&DAT_001020fc,&char_UserInput);
        if (int_ValidationCheck != 1)
        {
            break;
        }
        srand((int)char_UserInput);
        int_ValidationCheck = rand();
        if (int_ValidationCheck != *(int *)(check + (long)(int)index * 4))
        {
            puts("[ * INCORRECT * ]");
            puts("[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]");
            /* WARNING: Subroutine does not return */
            exit(-2);
        }
        puts("[ * CORRECT *]");
        index = index + 1;
    }
    /* WARNING: Subroutine does not return */
    exit(-1);
}

Reviewing the decompiled code we can see the following actions are taking place:

Line Number(s)ActionSource Code
3-5Variable setup: int_ValidationCheck char_UserInput indexint, char, and uint declaration.
6-18A message banner is displayed, and the user is requested to enter their bet.Collection of puts commands with some of the strings we saw earlier.
19Initialise an index to 0. index = 0;
20Enter an infinite loop.while(true)
23Conditional game end. Check if the index is more than 28, and if so, exit the application, otherwise continue.if (0x1c < index)
28-33Wait for input from the user and store the first character in char_UserInput. There is a validation check for the input string to safely exit out of the application – this is ignored for the CTF purposes, but is considered good practice.int_ValidationCheck = __isoc99_scanf(&DAT_001020fc,&char_UserInput);
34The application then seeds the standard library’s random number generator with the user’s input character (cast to an integer).srand((int)char_UserInput);
35A random number is then generated, and stored in a variable.int_ValidationCheck = rand();
36-42Game winning condition check is made against the random number and a calculation. An incorrect guess instantly informs the user and ends the application.   A correct guess matches the random number with values stored in the .data segment of the binary, given the name `check`.if (int_ValidationCheck != *(int *)(check + (long)(int)index * 4))
43-44On a correct guess, the user is informed, and the index increments by 1.puts("[ * CORRECT *]"); index = index + 1;
45 -> 20The game continues, iterating again from the infinite loop. 

This is a basic number guessing game. There’s no sign of a flag being outputted from correct guesses, therefore the characters we enter in a specific order must be the flag, with the positive feedback of a ‘CORRECT’ message.

Reversing the condition, would also mean reversing all possible seeds to the random number generator, and comparing them with the `check` table. The alternative, is to brute force the solution, repeatedly attempt to guess the answer, and adjust the next iteration with the feedback. Given we are working on checking a single character at a time, we can limit each guess to a range of 256 options. We could infer as it’s a console application written in C, it is using the base ASCII table of 128 options. Even further, just printable characters within the ASCII range, resulting in 94 characters to test:

ASCII Sub-SetCharacters
Digits:0123456789
Uppercase Letters:ABCDEFGHIJKLMNOPQRSTUVWXYZ
Lowercase Letters:abcdefghijklmnopqrstuvwxyz
Punctuation and Symbols:!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

Solution

The brute force script is rather simple, run the application, provide an attempt, and depending on the feedback, mark the attempt as correct and reuse it for the next iteration, or try the next character.

Here it is in Python:

import subprocess

printable_chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

flag = ''

for i in range(29):

    for j in range(len(printable_chars)):

        attempt = f'{flag}{printable_chars[j]}'
        print(attempt)

        result = subprocess.run(['./casino'], stdout = subprocess.PIPE, text = True, input = attempt)
        output_lines = result.stdout.strip().split('\n')
        last_line = output_lines[-1]

        if 'VACATE' not in last_line:
            flag = attempt
            break

print('Flag:', flag)

Running the script provides the following flag output:

Flag:HTB{r4nd_1s_v3ry_pr3d1ct4bl3}

We can now submit the flag!

Tags:, ,