0%

pwn系列之一

工具安装

  • objdump
  • readelf
  • IDA Pro
  • GDB-PEDA
  • Pwntools (具体用法见这儿或者下图)

软件保护机制

  • ASLR

    内存地址随机化机制(address space layout randomization),有以下三种情况:

    • 0-表示关闭进程地址空间随机化
    • 1-表示将mmap的基址,stack和vdso页面随机化
    • 2-表示在1的基础上增加堆(heap)的随机化
  • NX(DEP)

    NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

构造ROP
  • PIE

    位置独立的可执行区域(position-independent executables)

    代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术

    在每次加载程序时都变换加载地址

    结果就是找不到要跳转的地址

爆破后几位

泄露地址(相对偏移不变)
https://www.anquanke.com/post/id/177520

  • StackGuard(canary)

    栈溢出保护是一种缓冲区溢出攻击缓解手段,当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证 cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie 信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。

    canary的值存储在tls区段中的tcbhead_t结构中,有一个寄存器一直指向这个结构,在×86中是gs,×64中是fs

配合格式化字符串漏洞

canary本身的弱点,当stack_check_fail时,会打印出正在运行中程序的名称,所以,我们只要将libc_argv[0]覆盖为目标地址

露一个子进程的Canary,再在另一个子进程栈中伪造Canary

  • RELRO

    gcc-Wl,-z,relro,-z,now 开启full,于是GOT只读,函数在程序载入时就全部解析完毕

不可通过修改GOT表 控制dl_resolve函数

GOT&PLT

  • GOT(全局偏移量表):表中存放着函数真正的位置
  • PLT(过程链接表)
  • 流程为:PLT->GOT,如下图所示:

若为第一次调用,则GOT表会进行延迟绑定,此后调用就为真实地址了。

反调试

  • 一般地,反调试是通过比较程序在未被调试和被调试两种运行状况下的不同点来判断程序是否处于被调试的状态,进而选择执行正常流程之外的程序功能,如终止程序、删除自身甚至格式化硬盘
  • 反调试也经常被病毒马所采用,当检测到自己处于调试环境时,则不执行自身的恶意代码,最终躲过安全人员的检测

常见反调试方法:

  • ptrace自身进程
    • 在同一时间,进程最多只能被一个调试器进行调试,可以通过调试进程自身,来判断是否已经有其他进程(调试器)的存在
  • 检查父进程名称
    • 通常在使用gdb调试时,是通过gdb<TARGET>这种方式进行的,这种方式启动gdb后,gdb fork出子进程执行目标二进制文件。因此,二进制文件的父进程即为调试器,我们可通过检查父进程名称来判断是否是由调试器fork
  • 检查进程运行状态
    • 调试器可以通过attach到某个已有进程的方法进行调试这种情况下,被调试进程的父进程便不是调试器了。我们可以通过直接检查进程的运行状态来判断是否被调试,当进程正常运行而未被调试时,/proc/进程ID/status文件中的TracerPid值为0,而当进程被attach后,该值会变为调试器的PID
  • 设置程序运行最大时间
    • 由于程序在调试时的断点、检查修改内存等操作,运行时间往往要远大于正常运行时间。所以,一旦程序运行时间过长,便可能是由于正在被调试。具体实现方法是在程序启动时,通过alarm设置定时,到达时则中止程序
      • 当我们使用gdb在main函数下断点,稍候片刻后继续执行时,则触发了SIGALRM,进而检测到调试器
      • 这种方式可以轻易地被绕过,通过设置gdb对signal的处理方式,将SIGALRM忽略而非传递给程序,则alarmHandler便不会被执行
  • 检查进程打开的文件描述符
    • 如果被调试的进程是通过gdb<TARGET>的方式启动,那么它便是由gdb进程fork得到的。而fork在调用时,父进程所拥有的fd(file descriptor)会被子进程继承。由于gdb在往往会打开多个fd,因此如果进程拥有的fd较多,则可能是继承自gdb的,即进程在被调试。
      • 我们检查/proc/self/fd/中是否包含fd为5。由于fd从0开始编号,所以fd为5则说明已经打开了6个文件。如果程序正常运行则不会打开这么多,所以由此来判断是否被调试
  • 其他方法
    • 在程序中创建一个子进程定期检查父进程是否被调试
    • 插入int3指令扰乱调试器的正常行为

参考:北京理工大学慕课

技巧

  • 无保护机制的尝试:

    先使用以下命令关掉了整个系统的ASLR

    1
    echo 0 > /proc/sys/kernel/randomize_va_space

    再使用以下命令进行编译:

    1
    gcc -m32 -fno-stack-protector -z execstack -o test test.c

    -m32:编译为32位的程序

    -fno-stack-protector:禁用StackGuard

    -z execstack:关掉NX

  • 如何覆盖到返回地址?

    可进行手动计算,但我一般使用gdb的peda插件的pattern进行计算。

    1
    2
    3
    4
    5
    gdb-peda$ pattern create 100
    AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

    gdb-peda$ pattern offset (AAD
    (AAD found at offset: 24

    这样即可轻松得到覆盖大小。