文章基于台科大LJP的pwn课程学习
https://www.youtube.com/watch?v=8zO47WDUdIk&t=1087s
与c语言比较
rax和rbx是x86架构下的一种通用寄存器
jmp这里是跳过的意思,这里指直接跳到begin
cmp是比较的意思
jle是conditional jump指条件跳转(即如果前一个比较指令结果表明第一个操作数小于或等于第二个操作数,则执行跳转)
LOOP这里指的是直接跳出去(这里是跳到循环开头)
如果要搜索直接在谷歌或者edge输入==x86 要搜索的指令==
在 x86 架构中,特别是在函数调用过程中,通常会使用基址指针 bp
(也称为帧指针)来访问函数的局部变量和参数
当一个函数被调用时,它的栈帧(stack frame)被压入栈中,bp
指针会指向栈帧的基址,这样就可以通过 bp
指针来访问函数的局部变量和参数
read()
函数会尝试从文件描述符 fd
中读取 count
个字节的数据,并将数据存储到 buf
所指向的缓冲区中
它会返回实际读取的字节数:
read()
函数是一个阻塞函数,如果没有数据可读,它会一直等待
不同的区域存在不同的栈帧,里面存放不同的局部变量,每个function都有头部和尾部
头部:prologue
尾部:epilogue
前面是头部后面是尾部
0x0007fffffffe5c8是rsp的具体的位置
这个地方做一个说明这里的8bit是一个举例,在x86 架构下,这里一般是4个字节(32个bit)
这里不太好理解,
我认为的是这里的pop指令的关键就是上面的铺设和mov的操作的一次返还来保证这里的栈的稳定性,不发生偏移
1.取消掉rsp前面被push进入栈的8个bit也就是rbp原本的值
2.然后取消掉==这里rbp被mov到rsp经过push压栈后的值的操作==
结果如下(若果不理解可以再看看上面栈头部的操作)
这里的执行方法就是说将这里rsp中的0x401234的返回值pop给一个另外的寄存器rip(它的作用是不断更新执行那个下一条将要执行的地址)这样rip就会将rsp的pop回到原来的值(也就是e5a0对应的值,也就是main函数那里leave对应的地址)
这里可以发现stack的值已经返回了原来的函数调用前的值
取消掉rsp前面被push进入栈的8个bit也就是rbp原本的值
然后取消
掉==这里rbp被mov到rsp经过push压栈后的值
的操作==
这里推荐大佬的文章配置pwn的ubuntu的pwn环境
ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建_ubuntu powerpc编译环境-CSDN博客
但是个人觉得kali的好看一点
设定中断点
继续执行
执行一个命令
执行一个命令
显示记忆内容
disassemble
命令用于反汇编指定的代码区域,将其转换为汇编语言的形式并显示出来
可以指定要反汇编的代码区域
使用指令示例:
disassemble
:
在不指定具体代码区域的情况下,会显示当前程序执行点所在的代码的汇编
disassemble function_name
:
指定函数名,显示该函数的汇编代码
disassemble start_address, end_address
:
指定地址范围,显示该范围内的代码的汇编
disassemble *address
:
指定具体的地址,显示该地址处的代码的汇编
这里是gef的显示的界面
可以看见这里再b下断点之后发现,这里是展示了00到08的记忆体的数据
我么想要查看后面8位的数据
就使用指令
这里1个g就表示8个bit
就可以看到这个界面
用于创建本地进程。通过该函数,你可以启动一个本地的程序,并与其进行交互
其中 p
是一个 process
对象
/path/to/program
是要执行的程序的路径
你可以使用 p.send()
方法发送数据给该进程,使用 p.recv()
方法接收进程的输出
示例代码
用于创建网络连接并在远程主机上执行程序。通过该函数,你可以连接到远程主机的指定端口,并与其进行交互
r
是一个 remote
对象
host
是远程主机的 IP 地址或域名
port
是要连接的端口号
可以使用 r.send()
方法发送数据给远程主机,使用 r.recv()
方法接收远程主机的输出
示例代码
用于向进程或远程主机发送数据。无论是本地进程还是远程主机,你都可以使用 send
函数发送数据
其中 p
可以是 process
对象或 remote
对象,data
是要发送的数据
示例代码
发送带有换行符的数据。当需要发送一行数据并在末尾添加换行符时,可以使用 sendline
函数
其中 p
可以是 process
对象或 remote
对象
data
是要发送的数据,在末尾会自动添加换行符 \n
示例代码
在指定字符串出现后发送数据。当需要等待某个特定字符串出现后再发送数据时,可以使用 sendafter
函数
其中 p
可以是 process
对象或 remote
对象,search
是要搜索的字符串,一旦找到该字符串就会发送数据 "data"
示例代码
在指定字符串出现后发送带有换行符的数据。结合了 sendline
和 sendafter
的功能,在指定字符串出现后发送带有换行符的数据
其中 p
可以是 process
对象或 remote
对象,search
是要搜索的字符串,一旦找到该字符串就会发送带有换行符的数据 "data\n"
sendline
用于发送带有换行符的数
sendafter
用于在指定字符串出现后发送数据
sendlineafter
则是在指定字符串出现后发送带有换行符的数据
接收指定长度的数据。该函数用于接收指定长度的数据并返回
其中 p
可以是 process
对象或 remote
对象,n
是要接收的数据的长度。如果不指定 n
,则会接收尽可能多的数据
接收一行数据。该函数用于接收一行数据并返回,一行数据以换行符 \n
结尾
示例代码
接收直到指定字符串出现为止的所有数据。该函数用于接收数据,直到指定的字符串出现为止,然后返回所有接收到的数据(包括指定的字符串)
其中 p
可以是 process
对象或 remote
对象,pattern
是要等待的字符串
后期慢慢说,现在知道概念就行
由在局部变量上面越界输入导致
依旧利用前面的栈帧来进行模拟
假设这个蓝色的框里面存在使用者进行传参的窗口
如果传入的A没有限制
到leave的时候程序还能执行
但是到ret的时候,return回去8个A,就是8个4141的地址(假如它存在),如果不存在,程序就会出错
一种用于防范缓冲区溢出攻击的安全机制
会在程序的栈帧中插入一个称为"Stack Canary"的特殊值。这个值被设计为一个随机生成的数字,会在系统运行时动态地插入到程序栈的关键位置之间,如函数返回地址之前
当程序运行时,栈上的保护值会被监视,如果发生缓冲区溢出攻击,导致该值被修改,程序会检测到这种异常情况并采取相应的安全措施,例如终止程序执行,防止进一步的恶意操作
fs是一个暂存器,这里fs:28h相当于将存储在 FS 段寄存器偏移地址为 0x28 的位置)加载到 RAX 寄存器
将rax的值放在rbp-8的位置
这里cannary的值被改掉了
mov rcx,[rbp-8]就是指尝试获取前面rbp+8存放的cannary的值
一样的输出0 ,如果不一样则结果不是0
jump到ok的位置
正常跳到ok,执行ok的后面正常退出
call __stack_chk_fail
呼叫这个检查函数,程序不会正常退出,程序检查到了栈溢出的问题
基本的想法:
泄露cannary的值
在传入参数的时候将cannnary放到正确的位置,其他还是一样的传入A
就可以绕过检查
不太清晰这个东西以后再分享类似的
一段用于利用计算机系统漏洞或实施攻击的机器码,通常是用来注入恶意代码并获得对系统控制权的一种技术。Shellcode 通常以二进制形式编写,用于利用特定的漏洞或弱点,例如缓冲区溢出漏洞。一旦成功执行,Shellcode 可能会启动一个 shell 或者执行其他恶意操作
==这样本来的func1函数执行结束后应该返回位于main函数的该函数返回地址继续执行后面的操作,但是这里将地址更改后就会执行shellcode的程序==
这里针对的就是刚刚的关键性步骤在栈上的数据,能够被当做指令执行的问题
栈上的记忆体有三重权限(rwx三重权限)
==r(read) w(write) x(eXcute)==
但是在设定nx之后将不会存在rwx区段
在stack这个区段上面fde000-fff000存在rwx的权限
b main
之后run运行
到断点的位置
将会展示详细的记忆体区段信息
这里可以发现这里的的字段存在了很多rwx的三重权限
一般来说这里都要可读r(否则无法读取的话执行和写入都无法进行)
将数据所在内存页(用户栈中)标识为不可执行
当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令
就像这样:
https://www.runoob.com/cprogramming/c-function-setvbuf.html
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流
buffer -- 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲
mode -- 这指定了文件缓冲的模式:
size --这是缓冲的大小,以字节为单位
如果成功,则该函数返回 0,否则返回非零值
发现这里可以实现前面演示的攻击流程
有点看不懂,这里先去学一下pwntools的基本使用方法
附上笔记博客:pwntools的基本使用方法 – giraffe‘blog (giraffexiu.love)
攻击脚本中对齐rbp的栈顶,然后用0x90填充到0x70个字的大小
先传入8个字的0x90覆盖rbp
再将buf的数据传入覆盖return adrsss的值,然后回到攻击脚本的开始的传入的asm指令
要理解这段指令
chorme搜索,查询execve
这里是说linux中 x64 的命令执行的格式模板
==这里的sys_execve表示在rax设置为59,rdi放入要执行的文件的名称,rsi 和rdx不想指定的话,可以赋值为0==
这样的话指令就会将程序指向攻击的脚本位置开始执行
经过动调后发现这里的bin/sh/文件在(buf+0x14)的位置
等待用户输入,先暂停运行进程,方便调试
<img src="https://g1rffe.oss-cn-chengdu.aliyuncs.com/typora/image-20240328163657238.png" alt="image-20240328163657238" style="zoom:80%;" />
或者
作用:
现在执行在read函数当中,然后finish的作用就是执行完这个read函数然后直接返回main函数
==省去传入参数后,在main函数下断点,然后再单步调试到这个函数结束然后跳转到main函数的过程==
基于前面的shellcode分析和ida的逆向分析
我们知道向rbp-0x70的位置传入了数据
框住的部分应该就是shellcode,后面的部分是exp里面的填充到0x70的部分的0x90(nop)
<img src="https://g1rffe.oss-cn-chengdu.aliyuncs.com/typora/image-20240328224011799.png" alt="image-20240328224011799" style="zoom:200%;" />
这里应该是/bin/sh的值(猜测)
查一下
rax返回了一个错误的值
简析部分指令:
endbr64
指令通常与 jmp
(跳转)指令结合使用,以确保跳转目标的安全性。在执行 jmp
指令时,如果跳转目标地址附近存在 endbr64
指令插入的随机化标记,处理器会在跳转前检查该标记的存在性,以防止恶意利用分支预测攻击
用于启用基于分支目标地址的随机化(Branch Target Injection Mitigation)。这个指令主要是为了增强系统的安全性,特别是针对分支预测攻击,例如 Spectre 和 Meltdown。
跳回到这的第一个entry,就会push(link_map)
这里是在解析puts
写入负数的话就会想上面的地址进行传入
实际对应
rax对应value
array对应rcx
idx对应rdx
发现个array的地方的地址距离got表很近
可以利用前面发现的idx的漏洞进行劫持
==(16进制)==
通过获取 atoi
函数在 libc 中的地址,然后从 atoi
函数在内存中的地址中减去这个地址偏移量,得到的结果就是 libc 在内存中的基地址
这样我们的choice实际上就转换为
就拿到shell了
方便调试的进行
atoi就在这个区域
根据这里的地址
用基地址计算出这里的偏移
反推出libc的地址
发现在也在这个区域里面
查看相对于libc的基地址的偏移
将这里link出来的atoi的地址减去相对于libc的偏移0x47730,然后加上system的偏移0x55410就可以得到system的位置
利用泄露的atoi的地址,减去libc里面存储的偏移值
就可以得到libc基地址(就可以将他加上libc上面的存储的各种偏移,从而定位到其他的真实地址)
跟进检查got表,这里printf,stevbuf等没有改,这里的atoi变成了system
传入/bin/sh
这里atoi接收/bin/sh
带着这个参数,跳转到system
comtinue就拿到shell了
Gadget指可以利用的指令片段
只劫持一次流程,libc中有一些地址,跳进去就拿shell
工具最新版的下载地址
https://qiita.com/kusano_k/items/4a6f285cca613fcf9c9e#gl
ibc-231の場合
相关文章:
仮想関数テーブル、one-gadget RCE、glibc 2.31 #CTF - Qiita
跟前面的got hijack基本相同的源码(相同题目不同打法)
这里的基本思路是将这里atoi改写成gadget(这样的话无论buf传入什么都可以拿到shell)
==其实这里可以不用atoi的got表,只要是一个可以呼叫到的一个entary就行,只要能执行就可以得到flag)
这里的execve就相当于这里多个寄存器配置好的一个shell
这里也可以直接==利用工具直接one gadget找到关键点==
==能利用的条件r12和r10为NULL或者指向的NULL==
甚至可以对==binsh对应的地方进行交叉引用,来找到符合条件的one gadget==
==模拟程序基本概况==
返回的是gadget1的地址
跳转后执行,将rdi的值push进去
==gadget1执行返回后,这里的再次返回地址改成gadget2的地址==
执行gadget2
由此rsi=0
rdx=0
rax=59
这里最后将gadget3的值栈中,最后跳转到gadget3中执行
==进入后执行syscall(系统调用)==
在 x86_64 架构上,使用 syscall
指令执行系统调用。执行 syscall
指令时,CPU 会执行以下操作:
ssize_t read(
int
fd,
void
*buf,
size_t
count);
ssize_t read(
int
fd,
void
*buf,
size_t
count);
push rbp
mov rbp,rsp
sub rsp,
20h
```
call function1
leave
ret
push rbp
mov rbp,rsp
sub rsp,
20h
```
call function1
leave
ret
push rbp
mov rbp,rsp
sub rsp,
30h
```
leave
ret
push rbp
mov rbp,rsp
sub rsp,
30h
```
leave
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-7-18 19:18
被gir@ffe编辑
,原因: