Tut01: GDB/x86

IOLI-crackme

Did you successfully connect to the CTF server? Let's start playing with some binaries.

For this tutorial, we've prepared four "crackme" binaries. Your goal is very simple: find a password that each binary accepts. Before tackling this week's challenges, you'll learn how to use GDB, how to read x86 assembly, and how to have the mindset of a hacker!

We highly recommend tackling the crackme binaries first (at least up to 0x03) before jumping into the bomblab. In the bomblab, if you make a mistake ("exploding the bomb"), some points will be deducted from your score.

In this tutorial, we'll solve two of the crackme binaries together.

crackme0x00

# log into the CTF server
# ** check Canvas for login information! **
[host] $ ssh lab01@<ctf-server-address>

# let's start lab01!
[CTF server] $ cat README
[CTF server] $ cd tut01-crackme

Where should we start? There are many options:

  1. Reading the whole binary first (e.g., objdump -M intel -d crackme0x00)

  2. Starting it in a GDB session (e.g., gdb ./crackme0x00) and setting a breakpoint on some known function (for example, main() is luckily exposed in this binary -- try nm crackme0x00 to see) before running it (r)

  3. Running ./crackme0x00 first (waiting on the "Password" prompt) and then attaching it to GDB (e.g., gdb -p $(pgrep crackme0x00))

  4. Or just running with GDB (gdb ./crackme0x00), starting the binary (r), and then pressing Ctrl-C (i.e., sending a SIGINT signal) to return to GDB while on the "Password" prompt

Let's take 4. as an example.

$ gdb ./crackme0x00
Reading symbols from ./crackme0x00...(no debugging symbols found)...done.
(gdb) r

[r]un is the command to run a program; try help run for more.

Starting program: /home/lab01/tut01-crackme/crackme0x00
IOLI Crackme Level 0x00
Password: ^C

Press Ctrl+C (^C) to send a signal to stop the process, which will bring you back to the GDB prompt.

Program received signal SIGINT, Interrupt.
0xf7fd8d09 in __kernel_vsyscall ()
(gdb) bt
#0  0xf7fd5079 in __kernel_vsyscall ()
#1  0xf7ecbdf7 in __GI___libc_read (fd=0, buf=0x804b570, nbytes=1024) at ../sysdeps/unix/sysv/linux/read.c:27
#2  0xf7e58258 in _IO_new_file_underflow (fp=<optimized out>) at fileops.c:531
#3  0xf7e5937b in __GI__IO_default_uflow (fp=0xf7fbd5c0 <_IO_2_1_stdin_>) at genops.c:380
#4  0xf7e3ccb1 in _IO_vfscanf_internal (s=<optimized out>, format=<optimized out>, argptr=<optimized out>, errp=<optimized out>) at vfscanf.c:630
#5  0xf7e47e25 in __scanf (format=0x80487f1 "%s") at scanf.c:33
#6  0x080486d1 in main (argc=1, argv=0xffffd6c4) at crackme0x00.c:14

[bt]: print backtrace (i.e., stack frames). Again, don't forget to check help bt.

(gdb) tbreak *0x080486d1
Temporary breakpoint 1 at 0x080486d1

The backtrace shows that main() called __scanf() with return address 0x080486d1 (your addresses may be different). That seems like a good place to set a temporary breakpoint, since we'd like to investigate what main() does with the value it gets from that call. We use [tbreak] to set a temporary breakpoint (help b, help tb, help rb) there.

(gdb) c
Continuing.
aaaaaaaaaaaaaa

[c]ontinue to run the process. Type aaaaaaaaaaaaaa or some other random input, so the call to scanf() will end.

Temporary breakpoint 1, 0x080486d1 in main ()

OK, it hit the breakpoint. Let's check the context.

[disas]semble: dump the assembly code in the current scope.

(gdb) set disassembly-flavor intel
(gdb) disas
   0x080486a3 <+0>:     push   ebp
   0x080486a4 <+1>:     mov    ebp,esp
   0x080486a6 <+3>:     sub    esp,0x10
   0x080486a9 <+6>:     push   0x80487f4
   0x080486ae <+11>:    call   0x8048470 <puts@plt>
   0x080486b3 <+16>:    add    esp,0x4
   0x080486b6 <+19>:    push   0x804880c
   0x080486bb <+24>:    call   0x8048430 <printf@plt>
   0x080486c0 <+29>:    add    esp,0x4
   0x080486c3 <+32>:    lea    eax,[ebp-0x10]
   0x080486c6 <+35>:    push   eax
   0x080486c7 <+36>:    push   0x80487f1
   0x080486cc <+41>:    call   0x8048480 <scanf@plt>
=> 0x080486d1 <+46>:    add    esp,0x8
   0x080486d4 <+49>:    push   0x8048817
   0x080486d9 <+54>:    lea    eax,[ebp-0x10]
   0x080486dc <+57>:    push   eax
   0x080486dd <+58>:    call   0x8048420 <strcmp@plt>
   0x080486e2 <+63>:    add    esp,0x8
   0x080486e5 <+66>:    test   eax,eax
   0x080486e7 <+68>:    jne    0x8048705 <main+98>
   0x080486e9 <+70>:    push   0x804881e
   0x080486ee <+75>:    call   0x8048470 <puts@plt>
   0x080486f3 <+80>:    add    esp,0x4
   0x080486f6 <+83>:    push   0x804882d
   0x080486fb <+88>:    call   0x80485f6 <print_key>
   0x08048700 <+93>:    add    esp,0x4
   0x08048703 <+96>:    jmp    0x8048712 <main+111>
   0x08048705 <+98>:    push   0x804883c
   0x0804870a <+103>:   call   0x8048470 <puts@plt>
   0x0804870f <+108>:   add    esp,0x4
   0x08048712 <+111>:   mov    eax,0x0
   0x08048717 <+116>:   leave
   0x08048718 <+117>:   ret
End of assembler dump.

This is the full assembly code for main(). The "=>" shows the instruction the binary is currently paused on.

Please try to read and understand the code.

   0x080486c3 <+32>:    lea    eax,[ebp-0x10]
   0x080486c6 <+35>:    push   eax
   0x080486c7 <+36>:    push   0x80487f1
   0x080486cc <+41>:    call   0x8048480 <scanf@plt>

This is the call to scanf(). The second value pushed is the address of the format string; let's check what it is:

(gdb) x/s 0x80487f1
0x80487f1:  "%s"

So, in C, that call would look like this ("buf" being some buffer on the stack):

scanf("%s", buf);

Your input can be found in the buffer:

(gdb) x/s $ebp-0x10
0xffffcb30:     'a' <repeats 24 times>

Please learn about the e[x]amine command (help x), which is one of the most versatile commands in GDB.

   0x080486d4 <+49>:    push   0x8048817
   0x080486d9 <+54>:    lea    eax,[ebp-0x10]
   0x080486dc <+57>:    push   eax
   0x080486dd <+58>:    call   0x8048420 <strcmp@plt>

In the same way as before (try examining the argument string), you can find that this is equivalent to:

strcmp(buf, "250381");
   0x080486e5 <+66>:    test   eax,eax
   0x080486e7 <+68>:    jne    0x8048705 <main+98>
   0x080486e9 <+70>:    push   0x804881e
   0x080486ee <+75>:    call   0x8048470 <puts@plt>
   0x080486f3 <+80>:    add    esp,0x4
   0x080486f6 <+83>:    push   0x804882d
   0x080486fb <+88>:    call   0x80485f6 <print_key>
   0x08048700 <+93>:    add    esp,0x4
   0x08048703 <+96>:    jmp    0x8048712 <main+111>
   0x08048705 <+98>:    push   0x804883c
   0x0804870a <+103>:   call   0x8048470 <puts@plt>

That corresponds to the following (prove to yourself that it does):

if (!strcmp(buf, "250381")) {
    printf("Password OK :)\n")
    ...
} else {
    printf("Invalid Password!\n");
}

[Task] Try the password we found! Does it work? You can submit the flag to the submission site to get 20 points for the tutorial!

crackme0x01

Let's go more quickly with this binary. Please take similar steps as for crackme0x00, and reach this place.

(gdb) disas
   0x08048486 <+0>:     push   ebp
   0x08048487 <+1>:     mov    ebp,esp
   0x08048489 <+3>:     sub    esp,0x4
   0x0804848c <+6>:     push   0x8048570
   0x08048491 <+11>:    call   0x8048330 <puts@plt>
   0x08048496 <+16>:    add    esp,0x4
   0x08048499 <+19>:    push   0x8048588
   0x0804849e <+24>:    call   0x8048320 <printf@plt>
   0x080484a3 <+29>:    add    esp,0x4
   0x080484a6 <+32>:    lea    eax,[ebp-0x4]
   0x080484a9 <+35>:    push   eax
   0x080484aa <+36>:    push   0x8048593
   0x080484af <+41>:    call   0x8048340 <scanf@plt>

What's scanf() doing (i.e., what's the value of 0x8048593)?

=> 0x080484b4 <+46>:    add    esp,0x8
   0x080484b7 <+49>:    mov    eax,DWORD PTR [ebp-0x4]
   0x080484ba <+52>:    cmp    eax,0xc8e

This is comparing our input with 0xc8e (hex? integer?), which means that's probably the password.

   0x080484b4 <+46>:    add    esp,0x8
   0x080484b7 <+49>:    mov    eax,DWORD PTR [ebp-0x4]
   0x080484ba <+52>:    cmp    eax,0xc8e
   0x080484bf <+57>:    jne    0x80484d0 <main+74>
   0x080484c1 <+59>:    push   0x8048596
   0x080484c6 <+64>:    call   0x8048330 <puts@plt>
   0x080484cb <+69>:    add    esp,0x4
   0x080484ce <+72>:    jmp    0x80484dd <main+87>
   0x080484d0 <+74>:    push   0x80485a5
   0x080484d5 <+79>:    call   0x8048330 <puts@plt>
   0x080484da <+84>:    add    esp,0x4
   0x080484dd <+87>:    mov    eax,0x0
   0x080484e2 <+92>:    leave
   0x080484e3 <+93>:    ret

[Task] Try the password we found! Does it work? Great. Please explore all four crackme binaries, and when you think you're ready, please start the bomblab!

Bomblab

The bomblab challenges are all in a single "bomb" binary, which you can find under the home directory of user lab01 on the CTF server.

[host] ssh lab01@<ctf-server-address>

[CTF server] $ pwd
/home/lab01
[CTF server] $ ls -alh | grep bomb
-rwsr-x---  1 bomb110-raspberry lab01    22K Jan 14  2021 bomb

Execute the bomb binary and provide your API key to get started:

[CTF server] $ ./bomb
Enter your api-key: <your-api-key>

          ,--.!,   ____                  _     _       _
       __/   -*-  | __ )  ___  _ __ ___ | |__ | | __ _| |__
     ,d08b.  '|`  |  _ \ / _ \| '_ ` _ \| '_ \| |/ _` | '_ \
    0088MM        | |_) | (_) | | | | | | |_) | | (_| | |_) |
     `9MMP'       |____/ \___/|_| |_| |_|_.__/|_|\__,_|_.__/
            cs6265

Welcome to my fiendish little bomb. You have N? phases with
which to blow yourself up. See you alive!
(hint: seriously, security question?)
>

[Task] Defuse the bomb by providing the right answer to each phase. Be careful when handling the bomb; if you enter a wrong answer, the bomb will explode, and you'll lose points for that phase. Submit the flags from each phase to the submission site to earn points.

Reference