intro to gdb
Last meeting we learned about how to use GDB, lecture here.
There were two challenges, which were compiled differently (gdb_demo
and gdb_demo_harder
).
gdb demo
You can start gdb by running the command gdb gdb_demo
in the /cybersec/rev/gdb_demo
directory.
Let’s take a look at the main function:
gef➤ disass main
Dump of assembler code for function main:
0x000000000040136d <+0>: push rbp
0x000000000040136e <+1>: mov rbp,rsp
0x0000000000401371 <+4>: sub rsp,0x140
0x0000000000401378 <+11>: lea rax,[rip+0xc85] # 0x402004
0x000000000040137f <+18>: mov rdi,rax
0x0000000000401382 <+21>: call 0x401030 <puts@plt>
0x0000000000401387 <+26>: mov rdx,QWORD PTR [rip+0x2cb2] # 0x404040 <stdin@GLIBC_2.2.5>
0x000000000040138e <+33>: lea rax,[rbp-0x110]
0x0000000000401395 <+40>: mov esi,0x100
0x000000000040139a <+45>: mov rdi,rax
0x000000000040139d <+48>: call 0x401050 <fgets@plt>
0x00000000004013a2 <+53>: lea rax,[rbp-0x110]
0x00000000004013a9 <+60>: lea rdx,[rip+0xc60] # 0x402010
0x00000000004013b0 <+67>: mov rsi,rdx
0x00000000004013b3 <+70>: mov rdi,rax
0x00000000004013b6 <+73>: call 0x401040 <strcspn@plt>
0x00000000004013bb <+78>: mov BYTE PTR [rbp+rax*1-0x110],0x0
0x00000000004013c3 <+86>: movabs rax,0xd0ab00da1433ce15
0x00000000004013cd <+96>: movabs rdx,0x1ce6c3b971c298e5
0x00000000004013d7 <+106>: mov QWORD PTR [rbp-0x140],rax
0x00000000004013de <+113>: mov QWORD PTR [rbp-0x138],rdx
0x00000000004013e5 <+120>: movabs rax,0x7de75c7e8fa44d19
0x00000000004013ef <+130>: movabs rdx,0x7812867cdce6c5c
0x00000000004013f9 <+140>: mov QWORD PTR [rbp-0x130],rax
0x0000000000401400 <+147>: mov QWORD PTR [rbp-0x128],rdx
0x0000000000401407 <+154>: mov BYTE PTR [rbp-0x120],0x0
0x000000000040140e <+161>: lea rax,[rbp-0x140]
0x0000000000401415 <+168>: mov rsi,rax
0x0000000000401418 <+171>: mov edi,0x20
0x000000000040141d <+176>: call 0x4012e9 <decrypt>
0x0000000000401422 <+181>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000401429 <+188>: jmp 0x401469 <main+252>
0x000000000040142b <+190>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040142e <+193>: cdqe
0x0000000000401430 <+195>: movzx eax,BYTE PTR [rbp+rax*1-0x110]
0x0000000000401438 <+203>: movsx edx,al
0x000000000040143b <+206>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040143e <+209>: cdqe
0x0000000000401440 <+211>: movzx eax,BYTE PTR [rbp+rax*1-0x140]
0x0000000000401448 <+219>: movzx eax,al
0x000000000040144b <+222>: cmp edx,eax
0x000000000040144d <+224>: je 0x401465 <main+248>
0x000000000040144f <+226>: lea rax,[rip+0xbbc] # 0x402012
0x0000000000401456 <+233>: mov rdi,rax
0x0000000000401459 <+236>: call 0x401030 <puts@plt>
0x000000000040145e <+241>: mov eax,0x1
0x0000000000401463 <+246>: jmp 0x401483 <main+278>
0x0000000000401465 <+248>: add DWORD PTR [rbp-0x4],0x1
0x0000000000401469 <+252>: cmp DWORD PTR [rbp-0x4],0x20
0x000000000040146d <+256>: jle 0x40142b <main+190>
0x000000000040146f <+258>: lea rax,[rip+0xbb1] # 0x402027
0x0000000000401476 <+265>: mov rdi,rax
0x0000000000401479 <+268>: call 0x401030 <puts@plt>
0x000000000040147e <+273>: mov eax,0x0
0x0000000000401483 <+278>: leave
0x0000000000401484 <+279>: ret
End of assembler dump.
At main+176
, there is a call to a “decrypt” function, but our input is not being used in the call to it. So the decrypt function is probably going to be somewhat related to the flag. We can break at main+181
by typing in break * 0x0000000000401422
or with break * main+181
. Note that this challenge was compiled without PIE enabled, so that means we can break directly at the address (0x0000000000401422
) and not worry about it changing between runs.
When we run the program with r
and we get to the breakpoint we set, we can see the flag in the registers! Due to how Jason wrote this code it was conveniently located at the end of the stack frame.
Flag: flag{reading_your_mind_with_gdb}
gdb demo harder
Now, our executable has a couple of changes. First, there are no debug symbols. This means we can’t break *main+<whatever>
or even disass main
because when compiling, those names were thrown out. Now they are all just memory addresses.
Second, PIE is enabled. This means that memory addresses for instructions are relative only to each other, and are loaded at a random base every run. GDB can partially solve this by always loading the binary at some fixed address, typically with a bunch of 0x55555555
s at the beginning, like 0x0000555555554000
. This can be toggled with the command set disable-randomization [on/off]
with GEF.
Not gonna lie I don’t really know if this is the best way to do it but this is how I did it.
I first ran the program with r
. The program waits for input so I decided to hit ctrl-c
to interrupt the program. Because the program is reading input, I know that at some point the actual code has a call to a function that reads input. So if I interrupt and get a backtrace with bt
, I can probably figure out where the calling function is:
In this case, 0x00005555555550b4
looks pretty promising. It was entered into by __libc_start_main
, which happens to be the function that calls the main
function in executables that use libc, which is pretty much everything.
Taking a look at the raw instructions starting from 0x00005555555550b4
, it looks kind of like the gdb demo
program we did earlier. Playing around with ni
and si
leads to the conclusion that call 0x555555555360
calls the decrypt
function and right after the flag is on the stack. So we want to break at 0x5555555550f3
.
Sure enough, our flag is there:
Flag: flag{reading_your_mind_with_gdb}
Note that the flags are the same because the code compiled for the program is literally the exact same, but the two were compiled differently.
See you at the next meeting!