DEV Community

Cover image for [Hack] PicoCTF: Low Level Binary Intro - Intro to Debuggers
Falme Streamless
Falme Streamless

Posted on

[Hack] PicoCTF: Low Level Binary Intro - Intro to Debuggers

This PicoCTF Playlist section is called Intro to Debuggers, the challenges will go deeper into how to use the Debugger, specifically GDB, a common debugger and disassembler. After some explanations of how to use the GDB, we can do the challenges.

I am also using a Virtual Machine with Ubuntu to solve these challenges.


GDB baby step 1

Can you figure out what is in the eax register at the end of the main function? Put your answer in the picoCTF flag format: picoCTF{n} where n is the contents of the eax register in the decimal number base. If the answer was 0x11 your flag would be picoCTF{17}. Disassemble this (file).

So what we need to do is:

  1. Get the file (debugger0_a) to disassemble
  2. Disassemble the main function with GDB
  3. Gather the value of eax register at the end of main function

In summary, we need to gather a assembly dump from the requested file, just like in the previous challenges.

So after downloaded the file, we can disassemble the main function with the following command in shell:

chmod +x ./debugger0_a
gdb ./debugger0_a
Enter fullscreen mode Exit fullscreen mode

and inside gdb:

(gdb) disassemble main
Enter fullscreen mode Exit fullscreen mode

Making these actions, gdb returns with the following dump:

0x0000000000001129 <+0>:    endbr64
0x000000000000112d <+4>:    push   %rbp
0x000000000000112e <+5>:    mov    %rsp,%rbp
0x0000000000001131 <+8>:    mov    %edi,-0x4(%rbp)
0x0000000000001134 <+11>:   mov    %rsi,-0x10(%rbp)
0x0000000000001138 <+15>:   mov    $0x86342,%eax
0x000000000000113d <+20>:   pop    %rbp
0x000000000000113e <+21>:   ret
Enter fullscreen mode Exit fullscreen mode

It's kinda hard for me to read this one, maybe if we change to we are used to, the intel syntax, for that we will:

(gdb) set disassembly-flavor intel
(gdb) disassemble main
Enter fullscreen mode Exit fullscreen mode

and that returns the following dump:

0x0000000000001129 <+0>:    endbr64
0x000000000000112d <+4>:    push   rbp
0x000000000000112e <+5>:    mov    rbp,rsp
0x0000000000001131 <+8>:    mov    DWORD PTR [rbp-0x4],edi
0x0000000000001134 <+11>:   mov    QWORD PTR [rbp-0x10],rsi
0x0000000000001138 <+15>:   mov    eax,0x86342
0x000000000000113d <+20>:   pop    rbp
0x000000000000113e <+21>:   ret
Enter fullscreen mode Exit fullscreen mode

Really better, now, we need to find the final result to EAX register. The funny part is that there's only one command that affects EAX:

0x0000000000001138 <+15>:   mov    eax,0x86342
Enter fullscreen mode Exit fullscreen mode

So, the value of EAX is 0x86342.

But we actually need the decimal value, so we put it in our python script HexToDec.py, then we can have the answer:

python3 HexToDec.py 0x86342
Enter fullscreen mode Exit fullscreen mode

We take the result with picoCTF{value} and we have the flag:

Answer:GDB baby step 1
picoCTF{549698}


GDB baby step 2

Can you figure out what is in the eax register at the end of the main function? Put your answer in the picoCTF flag format: picoCTF{n} where n is the contents of the eax register in the decimal number base. If the answer was 0x11 your flag would be picoCTF{17}. Debug this (file).

The description is the same as the last one, but this time, we are playing with breakpoints and loops in the program. The exercise is to start looking into dynamic analysis.

Static analysis is what we have done before, looking through code to understand what it does. Dynamic analysis study the code through execution and debugging (breakpoints).

Doing the same assembly dump as the previous exercise, we gather this instruction:

0x0000000000401106 <+0>:    endbr64
0x000000000040110a <+4>:    push   rbp
0x000000000040110b <+5>:    mov    rbp,rsp
0x000000000040110e <+8>:    mov    DWORD PTR [rbp-0x14],edi
0x0000000000401111 <+11>:   mov    QWORD PTR [rbp-0x20],rsi
0x0000000000401115 <+15>:   mov    DWORD PTR [rbp-0x4],0x1e0da
0x000000000040111c <+22>:   mov    DWORD PTR [rbp-0xc],0x25f
0x0000000000401123 <+29>:   mov    DWORD PTR [rbp-0x8],0x0
0x000000000040112a <+36>:   jmp    0x401136 <main+48>
0x000000000040112c <+38>:   mov    eax,DWORD PTR [rbp-0x8]
0x000000000040112f <+41>:   add    DWORD PTR [rbp-0x4],eax
0x0000000000401132 <+44>:   add    DWORD PTR [rbp-0x8],0x1
0x0000000000401136 <+48>:   mov    eax,DWORD PTR [rbp-0x8]
0x0000000000401139 <+51>:   cmp    eax,DWORD PTR [rbp-0xc]
0x000000000040113c <+54>:   jl     0x40112c <main+38>
0x000000000040113e <+56>:   mov    eax,DWORD PTR [rbp-0x4]
0x0000000000401141 <+59>:   pop    rbp
0x0000000000401142 <+60>:   ret
Enter fullscreen mode Exit fullscreen mode

We can see 2 problems that we not faced before.

  1. EAX register is being called in multiple places
  2. we have JUMPs in the instruction, indicating a possible loop

The loop can be found as the following pattern:

At main+51 we have a comparison:

0x0000000000401139 <+51>:   cmp    eax,DWORD PTR [rbp-0xc]
Enter fullscreen mode Exit fullscreen mode

If this comparison is less than a value, jump to another line; JL (Jump if Less)
So in this case, we are checking if [rpb-0xc] is less than EAX

If so, we jump to the line main+38, so we go backwards and do it again. That way we find a loop

0x000000000040113c <+54>:   jl     0x40112c <main+38>
Enter fullscreen mode Exit fullscreen mode

So, with that information, we can assure that we will not count every single time that it loops and add a value to EAX.

In fact, we will go to the end of the program, and just read what EAX is, like a good dynamic analysis want us to do.

First, we add a breakpoint to the end of the main function before the return. the main+59 line is a good place to do this. So we go to gdb and do the following:

chmod +x ./debugger0_b
gdb ./debugger0_b
Enter fullscreen mode Exit fullscreen mode

and inside gdb we set up a breakpoint, where it will pause the execution:

(gdb) break *main+59
Enter fullscreen mode Exit fullscreen mode

And run the program normally through the GDB debugger:

(gdb) run
Enter fullscreen mode Exit fullscreen mode

After a little moment, it will stop with the following message:

Breakpoint 1, 0x0000000000401141 in main ()
Enter fullscreen mode Exit fullscreen mode

and now the program is paused as we asked on line main+59 , you can check the address, is the same number. Now we can ask for the value of EAX, that will show the result for the flag in hexadecimal and decimal.

(gdb) info registers eax
Enter fullscreen mode Exit fullscreen mode

We take the result with picoCTF{value} and we have the flag:

Answer:GDB baby step 2
picoCTF{307019}


GDB baby step 3

Now for something a little different. 0x2262c96b is loaded into memory in the main function. Examine byte-wise the memory that the constant is loaded in by using the GDB command x/4xb addr. The flag is the four bytes as they are stored in memory. If you find the bytes 0x11 0x22 0x33 0x44 in the memory location, your flag would be: picoCTF{0x11223344}. Debug this (file).

After a brief explanation about how to read Memory in GDB, the exercise is presented.

We need to find the bytes in the memory address of the file debugger0_c . The tip is the value 0x2262c96b in the main function.

So first of all, we need to disassemble the main function, same as before, resulting in the following assembly code:

0x0000000000401106 <+0>:    endbr64
0x000000000040110a <+4>:    push   rbp
0x000000000040110b <+5>:    mov    rbp,rsp
0x000000000040110e <+8>:    mov    DWORD PTR [rbp-0x14],edi
0x0000000000401111 <+11>:   mov    QWORD PTR [rbp-0x20],rsi
0x0000000000401115 <+15>:   mov    DWORD PTR [rbp-0x4],0x2262c96b
0x000000000040111c <+22>:   mov    eax,DWORD PTR [rbp-0x4]
0x000000000040111f <+25>:   pop    rbp
0x0000000000401120 <+26>:   ret
Enter fullscreen mode Exit fullscreen mode

following this, the value 0x2262c96b is moved (MOV at +15) to the memory address $RBP-0x4.

First some explanations. RBP register is the Register Base Pointer, it point to the base of the Stack Frame, and the addition of the values is through the negative numbering.

So usually we go for something like RBP-0x4 or RBP-0x8, this is adding values from the RBP (Register Base Pointer) to the RSP (Register Stack Pointer).

So we are moving the value 0x2262c96b to the Register Base Pointer position Minus 4.

Add a breakpoint at main+25 and run the program:

(gdb) break *main+25
(gdb) run
Enter fullscreen mode Exit fullscreen mode

And call the values inside RBP-0x4. where x/ is the command to call the memory reading, we want 4 bytes (4) in hexadecimal (x) each with byte-sized (b) resulting the command 4xb .

(gdb) x/4xb $rbp-0x4
Enter fullscreen mode Exit fullscreen mode

Resulting in:

0x7fffffffddbc: 0x6b    0xc9    0x62    0x22
Enter fullscreen mode Exit fullscreen mode

Well, actually the value returned is inverted from the original data 0x2262c96b . It's because we are dealing with Little endian.

We have Big Endian and Small Endian. Will add two images from a video (no longer listed) from C3rb3ru5d3d53c explaining this visually:

Big Endian

Normally "correct" the way we read the hexadecimal values.

Where

  • M : is the Most Significant Byte
  • L : is the Least Significant Byte

Big Endian

Little Endian

Normally "inverted" the way we read the hexadecimal values.

Little Endian

So the exercise asked for us to write down the flag as we read them, so, as Little endian is on the screen.

simply an exercise to just read a value in memory and understand Little endian.

We take the result with picoCTF{value} and we have the flag:

Answer:GDB baby step 3
picoCTF{0x6bc96222}


GDB baby step 4

main calls a function that multiplies eax by a constant. The flag for this challenge is that constant in decimal base. If the constant you find is 0x1000, the flag will be picoCTF{4096}. Debug this (file).

This exercise is very simple, is to show that we can call and disassemble many functions, not only main. we need to find a constant number that multiplies with EAX.

So me make the same as the other exercises to disassemble the main function returning the following:

0x000000000040111c <+0>:    endbr64
0x0000000000401120 <+4>:    push   rbp
0x0000000000401121 <+5>:    mov    rbp,rsp
0x0000000000401124 <+8>:    sub    rsp,0x20
0x0000000000401128 <+12>:   mov    DWORD PTR [rbp-0x14],edi
0x000000000040112b <+15>:   mov    QWORD PTR [rbp-0x20],rsi
0x000000000040112f <+19>:   mov    DWORD PTR [rbp-0x4],0x28e
0x0000000000401136 <+26>:   mov    DWORD PTR [rbp-0x8],0x0
0x000000000040113d <+33>:   mov    eax,DWORD PTR [rbp-0x4]
0x0000000000401140 <+36>:   mov    edi,eax
0x0000000000401142 <+38>:   call   0x401106 <func1>
0x0000000000401147 <+43>:   mov    DWORD PTR [rbp-0x8],eax
0x000000000040114a <+46>:   mov    eax,DWORD PTR [rbp-0x4]
0x000000000040114d <+49>:   leave
0x000000000040114e <+50>:   ret
Enter fullscreen mode Exit fullscreen mode

The line main+38 calls another function called "func1". Maybe the multiplication is there. Lets check it out

(gdb) disassemble func1
Enter fullscreen mode Exit fullscreen mode

Returns us the following:

0x0000000000401106 <+0>:    endbr64
0x000000000040110a <+4>:    push   rbp
0x000000000040110b <+5>:    mov    rbp,rsp
0x000000000040110e <+8>:    mov    DWORD PTR [rbp-0x4],edi
0x0000000000401111 <+11>:   mov    eax,DWORD PTR [rbp-0x4]
0x0000000000401114 <+14>:   imul   eax,eax,0x3269
0x000000000040111a <+20>:   pop    rbp
0x000000000040111b <+21>:   ret
Enter fullscreen mode Exit fullscreen mode

At the line func+14 we have a multiplication with EAX, maybe the value 0x3269 is what we need.

But we actually need the decimal value, so we put it in our python script HexToDec.py, then we can have the answer:

python3 HexToDec.py 0x3269
Enter fullscreen mode Exit fullscreen mode

We take the result with picoCTF{value} and we have the flag:

Answer:GDB baby step 4
picoCTF{12905}


ASCII FTW

This program has constructed the flag using hex ascii values. Identify the flag text by disassembling the program. You can download the file from here.

This is the challenge of the module. The mission is to find the flag with GDB in bytes, and then convert to ASCII/String.

First, let's look into the main function:

0x0000555555555169 <+0>:    endbr64
0x000055555555516d <+4>:    push   rbp
0x000055555555516e <+5>:    mov    rbp,rsp
0x0000555555555171 <+8>:    sub    rsp,0x30
0x0000555555555175 <+12>:   mov    rax,QWORD PTR fs:0x28
0x000055555555517e <+21>:   mov    QWORD PTR [rbp-0x8],rax
0x0000555555555182 <+25>:   xor    eax,eax
0x0000555555555184 <+27>:   mov    BYTE PTR [rbp-0x30],0x70
0x0000555555555188 <+31>:   mov    BYTE PTR [rbp-0x2f],0x69
0x000055555555518c <+35>:   mov    BYTE PTR [rbp-0x2e],0x63
0x0000555555555190 <+39>:   mov    BYTE PTR [rbp-0x2d],0x6f
0x0000555555555194 <+43>:   mov    BYTE PTR [rbp-0x2c],0x43
0x0000555555555198 <+47>:   mov    BYTE PTR [rbp-0x2b],0x54
0x000055555555519c <+51>:   mov    BYTE PTR [rbp-0x2a],0x46
0x00005555555551a0 <+55>:   mov    BYTE PTR [rbp-0x29],0x7b
0x00005555555551a4 <+59>:   mov    BYTE PTR [rbp-0x28],0x41
0x00005555555551a8 <+63>:   mov    BYTE PTR [rbp-0x27],0x53
0x00005555555551ac <+67>:   mov    BYTE PTR [rbp-0x26],0x43
0x00005555555551b0 <+71>:   mov    BYTE PTR [rbp-0x25],0x49
0x00005555555551b4 <+75>:   mov    BYTE PTR [rbp-0x24],0x49
0x00005555555551b8 <+79>:   mov    BYTE PTR [rbp-0x23],0x5f
0x00005555555551bc <+83>:   mov    BYTE PTR [rbp-0x22],0x49
0x00005555555551c0 <+87>:   mov    BYTE PTR [rbp-0x21],0x53
0x00005555555551c4 <+91>:   mov    BYTE PTR [rbp-0x20],0x5f
0x00005555555551c8 <+95>:   mov    BYTE PTR [rbp-0x1f],0x45
0x00005555555551cc <+99>:   mov    BYTE PTR [rbp-0x1e],0x41
0x00005555555551d0 <+103>:  mov    BYTE PTR [rbp-0x1d],0x53
0x00005555555551d4 <+107>:  mov    BYTE PTR [rbp-0x1c],0x59
0x00005555555551d8 <+111>:  mov    BYTE PTR [rbp-0x1b],0x5f
0x00005555555551dc <+115>:  mov    BYTE PTR [rbp-0x1a],0x38
0x00005555555551e0 <+119>:  mov    BYTE PTR [rbp-0x19],0x39
0x00005555555551e4 <+123>:  mov    BYTE PTR [rbp-0x18],0x36
0x00005555555551e8 <+127>:  mov    BYTE PTR [rbp-0x17],0x30
0x00005555555551ec <+131>:  mov    BYTE PTR [rbp-0x16],0x46
0x00005555555551f0 <+135>:  mov    BYTE PTR [rbp-0x15],0x30
0x00005555555551f4 <+139>:  mov    BYTE PTR [rbp-0x14],0x41
0x00005555555551f8 <+143>:  mov    BYTE PTR [rbp-0x13],0x46
0x00005555555551fc <+147>:  mov    BYTE PTR [rbp-0x12],0x7d
0x0000555555555200 <+151>:  movzx  eax,BYTE PTR [rbp-0x30]
0x0000555555555204 <+155>:  movsx  eax,al
0x0000555555555207 <+158>:  mov    esi,eax
0x0000555555555209 <+160>:  lea    rdi,[rip+0xdf4]        # 0x555555556004
0x0000555555555210 <+167>:  mov    eax,0x0
0x0000555555555215 <+172>:  call   0x555555555070 <printf@plt>
0x000055555555521a <+177>:  nop
0x000055555555521b <+178>:  mov    rax,QWORD PTR [rbp-0x8]
0x000055555555521f <+182>:  xor    rax,QWORD PTR fs:0x28
0x0000555555555228 <+191>:  je     0x55555555522f <main+198>
0x000055555555522a <+193>:  call   0x555555555060 <__stack_chk_fail@plt>
0x000055555555522f <+198>:  leave
0x0000555555555230 <+199>:  ret
Enter fullscreen mode Exit fullscreen mode

We have many addition of bytes into the memory from main+27 (RBP-0x30) to main+147 (RBP-0x12).

So we can add a breakpoint at somewhere like main+155, just after the values was added to memory.

(gdb) break *main+155
(gdb) run
Enter fullscreen mode Exit fullscreen mode

And then read the memory from the RBP-0x30.

So the question is, how we read and why start from -0x30.

Because this negative position, we are incrementing the values to read, so making the reading start from RBP-0x30 it will goes after to -0x2f, then -0x2e, etc...

and to read the string we will change the type from byte (x) to string (s). Same as the last one we've done, but changing this we got

(gdb) x/1sb $rbp-0x30
Enter fullscreen mode Exit fullscreen mode

Another this is, we usually read a lot of bytes, but in this case we do 1sb, we are reading 1 string. This is kinda weird in a length sense, but this will output each "String" and not "Character" from the memory.

Reading this value from memory we receive directly the flag to put into the answer.

Answer:ASCII FTW
picoCTF{ASCII_IS_EASY_8960F0AF}


Top comments (0)