pwn入门(星盟小破站视频学习)

1.入门基础知识

pwn的大部分操作是在linux上运行的,所以需要一定的linux使用基础

1.基本术语

  1. exploit:用于攻击的脚本
  2. payload:攻击载荷
  3. shellcode:调用攻击目标的shell代码(狭义)能得到对方shell的代码(广义)

2.攻击的基本流程

  1. nc ip port连接服务器
  2. 写一个exp攻击脚本
  3. 通过exp生成payload传给服务器
  4. 触发漏洞得到shell
  5. 通过主机与服务区建立的远程I/O双向连接,利用得到的控制权,在本地调用查看flag
image-20240320104127623

一般ctf比赛会提供一个和运行在远程服务端文件一样的文件交给选手本地查看

3.c语言转化为计算机的执行代码的路径

这里引用csacpp的原图(强烈建议年年看)

image-20240808164509079

大部分的漏洞都是基于c语言产生的

image-20240320110105370

4.可执行文件概述

image-20240320110315315

广义可执行文件:能运行的文件

狭义可执行文件:cpu可以把文件中的数据进行解析读取处理并执行 (因为存在指令集可以理解机械码)

5.计算机运行原理

image-20240808164526335

6.ELF的文件结构

image-20240808164545991

引自文章ELF文件详解-CSDN博客

image-20240320114149940

1.概述

elf文件是一种目标文件格式,常见文件格式为:

  1. 可执行文件
  2. 可重定位文件.o
  3. 共享目标文件.so
  4. 核心转储文件

2.结构

1.基本结构包括:
  1. ELF头
  2. Section头
  3. Program头
  4. Section
2.基本结构定义

数个连续的头称之为头表,头表是虚拟出来的定义,文件中不存在头表,只有头

1.由Section头组成的集合称为Section头表

一个Section头指向一个Section,Section头中包括所指向Section的名字、类型、其在ELF文件中的偏移地址、大小等信息

2.由Program头组成的集合称为Program头表

一个Program头指向一个Segment,Program头中包括所指向Segment的类型、其在ELF文件中的偏移地址、大小,映射到内存的虚拟地址等信息

3.一个Segment由一系列连续的Section构成

连续的Section拥有相同的权限,如只读、读写、可读可执行等

4.ELF头结构
  1. Section头表的在ELF文件中的偏移地址
  2. 单个Section头的大小
  3. Section头表中Section头的个数
  4. Program头表的在ELF文件中的偏移地址
  5. 单个Program头的大小
  6. Program头表中Program头的个数

若是可执行文件的话,还包含的有程序的入口地址

image-20240808163307121
3.头的具体实现
1.相关变量及其大小
img
2.ELF头
#define EI_NIDENT 16

struct Elf32_Ehdr           //共52个字节   //Ehdr表示ELF header
{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;       //类型包括:可执行文件、可重定向文件、共享目标文件等
Elf32_Half e_machine;     //有X86、arm之类
Elf32_Word e_version;
Elf32_Addr e_entry;       //可执行程序的入口地址
Elf32_Off e_phoff;       //Program头表的偏移地址
Elf32_Off e_shoff;       //Section头表的偏移地址
Elf32_Word e_flags;
Elf32_Half e_ehsize;     //本结构体的size
Elf32_Half e_phentsize;   //单个Program头的size
Elf32_Half e_phnum;       //Segment头表中Segment头的个数
Elf32_Half e_shentsize;   //单个Section头的szie
Elf32_Half e_shnum;       //Section头表中Section头的个数
Elf32_Half e_shstrndx;   //储存Section名字集合的Section的下标,指".shstrtab"的下标
};
2.section头
struct Elf32_Shdr              //共40个字节    //Shdl表示Section header
{
  Elf32_Word sh_name;       //所指向Section的名字,如".text"、".data"、".bss"等
  Elf32_Word sh_type;       //所指向Section的类型,如:符号表、字符串表等
  Elf32_Word sh_flags;      
  Elf32_Addr sh_addr;
  Elf32_Off sh_offset;       //所指向Section在ELF文件中的偏移量
  Elf32_Word sh_size;       //所指向Section的size
  Elf32_Word sh_link;       //和其关联的Section头的下标索引
  Elf32_Word sh_info;
  Elf32_Word sh_addralign;   //字节对齐
  Elf32_Word sh_entsize;
};
3.program头
struct Elf32_phdr            //32个字节    //phdr表示Program header
{
  Elf32_Word p_type;       //如PT_LOAD表示,对应Segment可被加载到内存中
  Elf32_Off p_offset;     //Segment在ELF文件中的偏移量
  Elf32_Addr p_vaddr;     //Segment映射到内存后的虚拟地址
  Elf32_Addr p_paddr;     //Segment映射到内存后的物理地址,此时与虚拟地址相同
  Elf32_Word p_filesz;     //Segment在ELF文件中占用的size
  Elf32_Word p_memsz;     //Segment映射到内存后占用的size
  Elf32_Word p_flage;     //读、写、执行权限
  Elf32_Word p_align;     //字节对齐,p_vaddr和p_paddr对p_align取模后为0
};
4.可执行文件具体分析
1.可重定向文件分析

1.ELF头详细信息

image-20240808163253736

观察这个头看到其类型为REL, 即可重定向文件

其中Program头的个数为0,Section头的个数为8个,没有程序入口地址

2.section头的详细信息

image-20240808163246047

观察到Addr在此处被填充为了0(目前并不需要被加载到内存中,在链接的时候才会被填充)

根据上述各Section的偏移量及size可推断出其在该可重定向文件中空间布局

偏移量(Off)大小(size)Section备注
0x00x34ELF头0x34表示十进制的52,刚好为ELF头的大小
0x340x2a.text
0x600x38.data
0x980x0.bss
0x980x30.shstrtab
0xc80x140Section头表一个Section头的大小为40个字节,共8个头,大小为0x140
0x2080x80.symtab
0x2880x28.strtab
0x2b00x10.rel.text

详解section类型

  1. .shstrtab:.shstrtab中存放着各个Section的名字
  2. .strtab:.symtab中存放着程序中用到的符号的名字
  3. .bss:程序中未初始化的全局变量都会被归类到bss段,并在程序加载的时候被初始化为0,在加载.bss的时候和.data一样,都属于可读可写的数据,但在ELF文件中.data需要占用一段内存空间来保存变量的初始化值,而.bss却不需要。即.bss只占用一个Section头的大小,而不需要对应的Section,如上表中可以看出.bss所描述Section的size为0
  4. .rel.text:.rel.text用于告诉链接器,哪些地方需要重定向
  5. .symtab:.symtab内存放着程序中用到的符号,包括变量符号、函数符号,如printf、main.symtab的定义struct Elf32_sym               //
    {
      Elf32_Word st_name;         //符号的名字
      Elf32_Addr st_value;       //符号相对于其所在Section偏移的相对地址
      Elf32_Word st_size;         //符号的size
      unsigned char st_info;     //低四位表示符号的作用范围(全局或局部),高四位表示符号的类型(变量、函数等)
      unsigned char st_other;
      Elf32_Half st_shndx;       //该符号的值在哪个Section下存储
    };实例:image-20240808163235328
  6. data_items:其Ndx为3,表示其在第3个Section,即.data。data_items的value值为00000000,表示其相对于.data的偏移地址为0,即data_itms在.data的开头
  7. _start:value为00000000,表示其在.text的开头,也即整个代码的入口是_start
2.可执行文件分析

1.ELF头解析

image-20240808163221767

相对于可重定向文件:

  1. 其类型变为了EXEC
  2. 少了两个Section header
  3. 多了两个Program头
  4. 并且有可执行程序的入口地址

2.section头详细解析

image-20240808163213283

区别:

  1. .text和.data的Addr不再为0,有了实际的值,这便是在链接过程中装载上的
  2. .bss段因为没有使用到,所以被删除
  3. .rel.text在链接之后,便完成了自己的使命,也就被删除

根据上述各Section的偏移量及size可推断出其在该可执行文件中空间布局

偏移量(Off)大小(size)Section备注
0x00x34ELF头0x34表示十进制的52,刚好为ELF头的大小
0x340x40Program头表一个Program头的大小为32字节,共2个头,大小为0x40
0x740x2a.text
0xa00x38.data
0xd80x27.shstrtab
0x1000xf0Section头表一个Section头的大小为40个字节,共6个头,大小为0xf0
0x1f00xa0.symtab
0x2900x40.strtab

3.Program头

image-20240808163205328

两个segment

  1. ELF头、Program头表和Section头表共同组成了第一个SegmentVirtAddr列指出第一个Segment加载到虚拟地址0x0804 8000(注意在x86平台上后面的PhysAddr列是没有意义的)
  2. data单独组成了另一个Segment第二个Segment加载到地址0x0804 90a0,Flg列指出第一个Segment的访问权限是可读可执行,第二个Segment的访问权限是可读可写
  3. 最后一列Align的值0x1000(4K)是x86平台的内存页面大小在加载时要求文件中的一页对应内存中的一页image-20240808163333550
  4. 这个可执行文件很小,总共也不超过一页大小,但是两个Segment必须加载到内存中两个不同的页面:MMU的权限保护机制是以页为单位的,一个页面只能设置一种权限
  5. 规定每个Segment在文件页面内偏移多少加载到内存页面仍然偏移多少:例如第二个Segment在文件中的偏移是0xa0,在内存页面0x0804 9000中的偏移仍然是0xa0,所以是从0x0804 90a0开始(简化链接器和加载器的实现)
  6. .text段的加载地址应该是0x0804 8074(程序的入口地址)原来目标文件符号表中的Value都是相对地址,现在都改成绝对地址了
  7. 此外还多了三个符号_bss_start、edata和_end,这些是在链接过程中添进去的,加载器可以利用这些信息把.bss段初始化为0
5.可执行ELF文件的装载过程
image-20240808163151475
6.相关知识

段错误(Segment Error)

当程序试图访问不允许访问的内存位置,或试图以不允许的方式访问内存位置(例如尝试写入只读位置,或覆盖部分操作系统)时会发生段错误

  1. 使用未经初始化及或已经释放的指针地址
  2. 访问受系统保护的内存地址
  3. 写入只读的内存地址
  4. 数组越界
  5. 堆栈溢出

7.程序装载与虚拟内存

辅存和主存相关概念

概括的说,CPU对所需要的数据进行计算时,要求很高的存储速度,且不需要能永久保存这些数据,高速存储设备的成本很高

但其他设备对存储速度的要求不像CPU这么高,一般要求永久保存数据。一般低速的存储设备就可以满足,且低速的存储成本也低

所以有主存和辅存之分:

内存(主存)直接给CPU提供存储,高速,低容量,价格贵,不能永久保存数据,断电消失,需要从辅存中重新调入数据

外存(辅存)给主存提供数据,低速,大容量,价格低,能永久保存数据

所以更高缓存的CPU和更大的内存能够大大提升系统的性能

常见主存有:CPU的高速缓存,电脑的内存

常见辅存有:硬盘、光盘、U盘、磁盘、移动硬盘等等

1.创建test.c文件通过gcc编译生成a.out文件(存储在辅存)

image-20240320204207310

2.a.out文件转换到内存变成vm文件,cpu访问主存的内容

image-20240320204901603

3.内存的镜像

1.详细版本
image-20240320221114335
2.简略版本
image-20240320205133990
3.linux分析指令

想查看== elf文件的结构==的话出了利用ida静态分析

可以使用linux指令进行文件结构查看

objdump -s elf

终端展示完整的虚拟内存的结构(linux特性不仅可以表示磁盘中的内容,也可以展示内存中的内容)

cat /proc/pid/maps
4.section到segment转换合并

利用linker进行合并

5.实体内存到虚拟内存的转换和映射通过os来完成(不同的os映射出是不同的)

单位均为bit

怕忘了,再记忆一下

image-20240320222039557
1byte=8bits

4.got表和plt表

1.Globle offset table全局偏移量表:

位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址,GOT表存储在数据段,在IDA中是也就是.data段)可以在程序运行中被修改

hijack GOT:修改某一个被调用函数的地址,让其指向另一个函数

例如:

修改printf()函数的地址让其指向system(),这样做的结果就是原本对于printf()的调用就变成了调用system()函数

2.procedure linkage table过程连接表:

位于代码段,是一个每个条目是16字节内容的数组

其中PLT[0]储存的信息能用来跳转到动态链接器中,PLT【1】是系统启动函数(__libc_start_main)

其余每个条目都负责调用一个具体的函数

3.总结got表和plt表调用思路
1.确定函数A在GOT表中的地址,函数B在内存中的地址,将函数B的地址写入函数A在GOT表中

例如:

我们知道了printf函数在GOT表中的位置,以及system函数在内存中的地址,就可以将system写入GOT表替代printf

2.确定printf函数在GOT表中的地址
  1. 程序调用函数时通过PLT表跳转到了GOT表的对应条目
  2. 我们当然可以在函数调用的汇编指令中找到PLT表中该函数的入口地点
  3. 从而定位到该函数在GOT表中的对应条目
3.确定函数B(system函数)在内存中的地址
  1. 如果系统开启了内存布局随机化,程序每次运行动态链接库的加载位置都是随机的,就很难通过调试工具直接确定函数的地址
  2. 加入函数B在栈溢出之前被调用过,我们就可以通过前一个问题的答案(从GOT表中获取已有的地址)
  3. 函数在动态链接库的相对位置是固定的,并且在动态库生成时就已经确定
  4. 假如我们知道了函数A的地址,同时也知道函数A和函数B在动态链接库的相对位置,就可以推算出函数B的地址
4.最终思路

确定函数A在GOT表中的地址,以及函数B在内存中的地址,将函数B的地址写入函数A在GOT中的地址

5.关于虚拟内存的理解

因为cpu只能读取是的内存,而实体的内存是不连续且抽象的,所以os给我们通过偏移和计算处理生成连续的虚拟内存便于理解和处理

如下图所示

image-20240320223322341

8.cpu的执行与进程

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇