Pwn学习日记-1

学Pwn第一天,其实之前学过,不过今天开始认真系统地学一遍,很多基础的东西看exp都不太明白,确实菜,而且Pwn很帅啊,顺便说一下Zikey Vi老师太强了,讲的很详细,实力也很强!

PWN?

概述
  • 破解、利用成功(程序的二进制漏洞)
  • 攻破(设备、服务器)
  • 控制(设备、服务器)
一次简单的hack
  • exploit 用于攻击的脚本与方案
  • payload 攻击载荷,是对目标进程劫持控制流的数据
  • shellcode 调用攻击目标shell的代码

二进制基础

程序的编译与链接
从C源代码到可执行文件的生成过程
  • 编译
    • 由C语言代码生成汇编代码
  • 汇编
    • 由汇编代码生成机器码
  • 链接
    • 将多个机器码的目标文件链接成一个可执行文件

Linux下的可执行文件格式ELF
什么是可执行文件?
  • 广义:文件中的数据是可执行代码的文件
    • .out、.exe、.sh、.py
  • 狭义:文件中的数据是机器码的文件
    • .out、.exe、.dll、.so
可执行文件的分类
  • Windows:PE(Portable Executable)
    • 可执行程序
      • .exe
    • 动态链接库
      • .dll
    • 静态链接库
      • .lib
  • Linux:ELF(Executable and Linkable Format)
    • 可执行程序
      • .out
    • 动态链接库
      • .so
    • 静态链接库
      • .a
ELF文件解析

  • ELF文件头表(ELF header)
    • 记录了ELF文件的组织结构
  • 程序头表/段表(Program header table)
    • 告诉系统如何创建进程
    • 生成进程的可执行文件必须拥有此结构
    • 重定位文件不一定需要
  • 节头表(Section header table)
    • 记录了ELF文件的节区信息
    • 用于链接的目标文件必须拥有此结构
    • 其它类型目标文件不一定需要

磁盘中的ELF(可执行文件)与内存中的ELF(进程内存映像)

进程虚拟地址空间
虚拟地址表述

地址以字节编码 1Byte = 8bits,常以16进制表示 0x3c = 0011 1100

  • 虚拟内存用户空间每个进程一份
  • 虚拟内存内核空间所有进程共享一份
  • 虚拟内存 mmap 段中的动态链接库仅在物理内存中装载一份

因为当程序运行时,会在内存中创建进程,许多个程序也是如此,但是他们都在操作系统上运行的。程序并不会更改系统内核,当它们调用系统代码时,其实是同一份系统内核,所以许多进程共享一份内核,每个进程拥有独立内存空间。

段(segment)与节(section)
  • 代码段(Text segment)包含了代码与只读数据
    • .text 节
    • .rodata 节
    • .hash 节
    • .dynsym 节
    • .dynstr 节
    • .plt 节
    • .rel.got 节
    • ……
  • 数据段(Data segment)包含了可读可写数据
    • .data 节
    • .dynamic 节
    • .got 节
    • .got.plt 节
    • .bss 节
    • ……
  • 栈段(Stack segment)
    • 一个 段 包含多个 节
    • 段视图用于进程的内存区域的 rwx权限划分
    • 节视图用于ELF文件 编译链接时 与 在磁盘上存储时 的文件结构的组织
程序数据是如何在内存中组织的

程序的装载与进程的执行
大端序与小端序
小端序大端序
低地址存放数据低位、高地址存放数据高位低地址存放数据高位、高地址存放数据低位
进程的执行过程

AMD64寄存器简述

AMD64寄存器结构

  • rax: 8Bytes
  • eax:4Bytes
  • ax: 2Bytes
  • ah: 1Bytes
  • al: 1Bytes

部分寄存器功能

  • RIP
    存放当前执行的指令的地址
  • RSP
    存放当前栈帧的栈顶地址
  • RBP
    存放当前栈帧的栈底地址
  • RAX
    通用寄存器。存放函数返回值

r开头一般都是64位操作系统的寄存器,大小为8,32位的为4

静/动链接程序的执行过程
x86&AMD64汇编简述
常用汇编指令

MOV LEA ADD/SUB PUSH POP CMP JMP J[Condition] CALL LEAVE RET ……
这里用例子简单介绍几个常用的

MOV

MOV DEST, SRC ; 把源操作数传送给目标

  • MOV EAX,1234H ;执行结果(EAX) = 1234H
  • MOV EBX, EAX
  • MOV EAX, [00404011H] ;[ ] 表示取地址内的值
  • MOV EAX, [ESI]

LEA

LEA REG, SRC ; 把源操作数的有效地址送给指定的寄存器

  • LEA EBX, ASC ; 取 ASC 的地址存放至 EBX 寄存器中
  • LEA EAX, 6[ESI] ; 把 ESI+6 单元的32位地址送给 EAX

PUSH

PUSH VALUE ; 把目标值压栈,同时SP指针-1字长

  • PUSH 1234H
  • PUSH EAX

POP

POP DEST ; 将栈顶的值弹出至目的存储位置,同时SP指针+1字长

  • POP EAX

LEAVE

  • 在函数返回时,恢复父函数栈帧的指令
  • 等效于:
    • MOV ESP, EBP
    • POP EBP

RET

  • 在函数返回时,控制程序执行流返回父函数的指令
  • 等效于:
    • POP RIP(这条指令实际是不存在的,不能直接向RIP寄存器传送数据)
两种汇编格式
intelAT&T
mov eax, 8movl $8, %eax
mov ebx, 0ffffhmovl $0xffff, %ebx
int 80hint $80
mov eax, [ecx]movl (%ecx), %eax

栈溢出基础

C语言函数调用栈
  • 函数调用栈是指程序运行时内存一段连续的区域
  • 用来保存函数运行时的状态信息,包括函数参数与局部变量等
  • 称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶
  • 在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态
  • 函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大

栈帧结构概览

  • 函数状态主要涉及三个寄存器 —— esp,ebp,eip。esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。
  • 下面让我们来看看发生函数调用时,栈顶函数状态以及上述寄存器的变化。变化的核心任务是将调用函数(caller)的状态保存起来,同时创建被调用函数(callee)的状态。
  • 首先将被调用函数(callee)的参数按照逆序依次压入栈内。如果被调用函数(callee)不需要参数,则没有这一步骤。这些参数仍会保存在调用函数(caller)的函数状态内,之后压入栈内的数据都会作为被调用函数(callee)的函数状态来保存。

将被调用函数的参数压入栈内

  • 然后将调用函数(caller)进行调用之后的下一条指令地址作为返回地址压入栈内。这样调用函数(caller)的 eip(指令)信息得以保存。

将被调用函数的返回地址压入栈内

  • 再将当前的ebp 寄存器的值(也就是调用函数的基地址)压入栈内,并将 ebp 寄存器的值更新为当前栈顶的地址。这样调用函数(caller)的 ebp(基地址)信息得以保存。同时,ebp 被更新为被调用函数(callee)的基地址。

将调用函数的基地址(ebp)压入栈内,并将当前栈顶地址传到 ebp 寄存器内

  • 再之后是将被调用函数(callee)的局部变量等数据压入栈内。

将被调用函数的局部变量压入栈内

  • 在压栈的过程中,esp 寄存器的值不断减小(对应于栈从内存高地址向低地址生长)。压入栈内的数据包括调用参数、返回地址、调用函数的基地址,以及局部变量,其中调用参数以外的数据共同构成了被调用函数(callee)的状态。在发生调用时,程序还会将被调用函数(callee)的指令地址存到 eip 寄存器内,这样程序就可以依次执行被调用函数的指令了。
  • 看过了函数调用发生时的情况,就不难理解函数调用结束时的变化。变化的核心任务是丢弃被调用函数(callee)的状态,并将栈顶恢复为调用函数(caller)的状态。
  • 首先被调用函数的局部变量会从栈内直接弹出,栈顶会指向被调用函数(callee)的基地址。

将被调用函数的局部变量弹出栈外

  • 然后将基地址内存储的调用函数(caller)的基地址从栈内弹出,并存到 ebp 寄存器内。这样调用函数(caller)的 ebp(基地址)信息得以恢复。此时栈顶会指向返回地址。

将调用函数(caller)的基地址(ebp)弹出栈外,并存到 ebp 寄存器内

  • 再将返回地址从栈内弹出,并存到 eip 寄存器内。这样调用函数(caller)的 eip(指令)信息得以恢复。
  • 至此调用函数(caller)的函数状态就全部恢复了,之后就是继续执行调用函数的指令了。

将被调用函数的返回地址弹出栈外,并存到 eip 寄存器内

函数调用栈的工作方式(cdecl)
  • x86
    • 使用栈来传递参数
    • 使用 eax 存放返回值
  • amd64
    • 前6个参数依次存放于 rdi、rsi、rdx、rcx、r8、r9 寄存器中
    • 第7个以后的参数存放于栈中

函数调用前,EBP的值入栈,ESP存储的是栈顶地址。然后ESP的值传给EBP,函数被调用,此时ESP一直指向栈顶。函数调用结束后,EBP将值传回ESP,EBP跳转到父函数的栈底,ESP又指向了栈顶地址。EIP指向下一条指令,每执行一次指令,EIP的指向都会改变。

ret2text
  • 介绍完背景知识,就可以继续回归栈溢出攻击的主题了。当函数正在执行内部指令的过程中我们无法拿到程序的控制权,只有在发生函数调用或者结束函数调用时,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数状态来实现攻击。而控制程序执行指令最关键的寄存器就是 eip,所以我们的目标就是让 eip 载入攻击指令的地址。
  • 先来看看函数调用结束时,如果要让 eip 指向攻击指令,需要哪些准备?首先,在退栈过程中,返回地址会被传给 eip,所以我们只需要让溢出数据用攻击指令的地址来覆盖返回地址就可以了。其次,我们可以在溢出数据内包含一段攻击指令,也可以在内存其他位置寻找可用的攻击指令。

缓冲区溢出(Buffer overflow)

本质是向定长的缓冲区中写入了超长的数据,造成超出的数据覆写了合法内存区域

  • 栈溢出(Stack overflow)
    • 最常见、漏洞比例最高、危害最大的二进制漏洞
    • 在 CTF PWN 中往往是漏洞利用的基础
  • 堆溢出(Heap overflow)
    • 堆管理器复杂,利用花样繁多
    • CTF PWN 中的常见题型
  • Data段溢出
    • 攻击效果依赖于 Data段 上存放了何种控制数据

  • 篡改栈帧上的返回地址为程序中已有的后门函数

例子:
checksec
checksec
main
main
vulnerable
vulnerable
get_shell
get_shell
栈内存
gdb内输入8个A查看栈

这道题,那么多截图,也就看的很清晰了,main函数里俩puts没啥漏洞,但是留了一个vulnerable函数,进入查看,定义了大小为8的数组,但是gets可以输入任何长度的数据,这里我们将数组到ebp之间都填满,这里其实就包含了数组空间,再把ebp寄存器填满,后满紧跟的就是返回地址空间,将返回地址空间里填入后门函数的地址就好。

上图是我的个人理解

ret2shellcode
  • 篡改栈帧上的返回地址为攻击者手动传入的 shellcode 所在缓冲区地址
  • 初期往往将 shellcode 直接写入栈缓冲区
  • 目前由于 the NX bits 保护措施的开启,栈缓冲区不可执行,故当下的常用手段变为向 bss 缓冲区写入 shellcode 或向堆缓冲区写入 shellcode 并使用 mprotect 赋予其可执行权限

例子:
checksec
checksec
main
main
buf2地址
buf2地址
段权限
段权限
栈内存
栈内存

由此可以看出,保护全关,目标机器可能开了ASLR,题目也是让我们调用shellcode,main里将s复制到buf2,显然可以造成溢出,buf2所在段为.bss,查看段权限,此段拥有rwx权限,那我们就可以生成shellcode,以及足够溢出的字符,再加上buf2的地址即可。为什么要这样构造payload?因为在s中输入数据后,即使复制到了buf2,程序也只是将数据复制一份过去而已,并没有运行buf2代码,程序仍然在main里运行着,所以在s里溢出,将跳转位置覆写为buf2地址,这样程序就跳转到了buf2,并且往下执行shellcode。

有没有发现我换了终端?没错,同一道题,Kali和Ubuntu在查看段权限那里显示不一致。Kali显示没有rwx权限,exp也运行错误,但是Ubuntu显示.bss段有rwx权限,而且可以正确运行exp,那我只能抛弃Kali做题,Ubuntu兼容确实好。

今天也算是对之前野路子学Pwn的归纳总结,以前一直是直接做题,不会就看wp,百度,看博客,各种渠道去解疑,然后遇到啥学啥,导致很多底层原理很混乱,这篇文章是一个很好的梳理,后面会继续更新的!

还有一些保护措施,编译,数据结构上的细节没有写到,后面涉及到会继续补充!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tajang

感谢投喂

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值