注意事项
- read函数读取终端的输入,循环读入一直持续到按
Ctrl + D
即EOF为止,此时输入不包含\n
- system参数前面有地址,需要参数截断($0同sh):
&&sh ||sh ;sh;
- 以cin,scanf读入时遇到0x0a,0x20会截断,因此如果地址有0x0a,0x20要设法绕过(+0x8)!(sample:myzoo-安恒月赛-2018-10)
- 连续open文件
1024
次,使文件描述符数组满了后,无法再次打开文件,用于绕过读取random文件验证。 scanf("%d")
输入+
或者-
不会改变原来栈上内容。(sample:easy_rop-NCTF2019)- read函数需要保证整段输入缓冲区buff+nbytes都有写权限,否则即使只读取一个字节也会失败!因此,如果伪造输入缓冲区到栈Alloc_to_stack时,需要额外小心是否已经超出栈底,可以通过加argv参数进行验证。(sample:smallorange-安恒月赛-2018-12)
close(1)
,关闭标准输出后,通过write(0,"123\n",4)
向标准输入打印也是可以获得输出值的,但是本地调试的时候采用p = process('./blind_note')
,pwntools无法得到输出,因此需要通过socat tcp-l:9999,reuseaddr,fork exec:./blind_note &
将程序绑定到9999端口,然后模拟远程连接p = remote('127.0.0.1', 9999)
,pwntools才能得到输出。get shell之后,同样需要将输出重定向到标准输入才可看到输出值:ls >&0
(sample:blind_note-安恒月赛-2018-11)
linux_64与linux_86的区别
- 内存地址的范围由32位变成了64位。但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。
- 函数参数的传递方式发生了改变,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上。
- 泄漏ELF,32位从0x08048000开始,64位从0x400000开始
函数入口与出口汇编指令
push ebp # 将ebp压栈
mov ebp, esp #将esp的值赋给ebp
leave = mov esp, ebp # 将ebp的值赋给esp
pop ebp #弹出ebp
ret = pop eip #弹出栈顶元素作为程序下一个执行地址
格式化字符串
- 利用
%x
来获取对应栈的内存,但建议使用%p
,可以不用考虑位数的区别 - 利用
%s
来获取变量所对应地址的内容,只不过有零截断 - 利用
%n$x
来获取指定参数的值,利用%n$s
来获取指定参数对应地址的内容。获取栈中被视为第n+1
个参数的值 %12$n
不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。%hhn
向某个地址写入单字节,利用%hn
向某个地址写入双字节,%$lln
表示写入的地址空间为8字节%*25$d
从栈中取变量作为N,比如:栈25$
处的值是0x100
,那么这个格式化字符串就相当于%256d
。(sample:pwn4-MidnightsunCTF-2020)
利用pwntools工具:
求偏移fmtarg
1 |
|
利用
- hijack GOT:
fmtstr_payload(7, {puts_got: system_addr})
- hijack retaddr:
p64(ret_addr) + "%2218d%8$hn"
- 堆上格式化字符串漏洞:修改上层函数保存的ebp(即上上层函数的 ebp) 为堆上预先存储
system_addr的地址-4
,当执行leave=mov esp, ebp; pop ebp
时,ebp=system_addr-4。当再执行leave,mov esp, ebp
将esp=ebp=system_addr-4,pop ebp
出栈操作将esp+4,指向system_addr,此时,ret将EIP指向system_addr,从而getshell。
沙箱保护
seccomp
1 |
|
prctl函数
1 |
|
借助seccomp-tools检查禁用的系统调用
1 |
|
通过OpenReadWrite的方式直接读取输出,过滤了open,可以用openat代替。
fd = open('flag',0) --> read(fd,buf,0x100) --> write(1,buf,0x100)
1 |
|
如果溢出空间太小(x64环境下至少溢出0x10个字节),需要借助栈迁移。
1 |
|
栈迁移(stack pivoting)
原理:借助leave=mov esp,ebp; pop ebp;
控制栈顶指针esp
。
适用条件:读入长度太少,无法直接在当前栈完成攻击,需要将栈迁移到可控区域(多为bss段)。(sample:lab 6-HITCON-Training)
1 |
|
先pop ebp使得ebp=bss,再调用read布置bss,当从read返回调用leave_ret时,esp=ebp=bss,实现控制esp到bss的目的,同理,接下来可以多次迁移。
syscall系统调用
运行在用户空间的程序向操作系统内核请求更高权限运行的服务。
32位
传参方式:首先将系统调用号传入eax,然后将参数从左到右依次存入ebx,ecx,edx寄存器中,返回值存在eax寄存器
调用号(/usr/include/asm/unistd_32.h):read的调用号为3,sys_write的调用号为4,execve的调用号为11
调用方式:使用int 80h
中断进行系统调用
64位
传参方式:首先将系统调用号传入rax,然后将参数从左到右依次存入rdi,rsi,rdx寄存器中,返回值存在rax寄存器
调用号(/usr/include/asm/unistd_64.h):read的调用号为0,sys_write的调用号为1,execve的调用号为59
调用方式: 使用syscall
进行系统调用
静态编译恢复libc符号
1 |
|
然后根据将sig-database中对应的sig文件,放到IDA安装目录的sig/pc(切记一定是pc目录)
shift+F5,右键Apply new signature,选择对应的libc。
ARM-ROP
函数调用约定,函数的第1-4个参数分别保存在r0-r3
寄存器中, 剩下的参数从右向左依次入栈,被调用者实现栈平衡,函数的返回值保存在r0
中。r13栈指针,pc寄存器相当于x86 的eip,保存下一条指令的地址,也是我们要控制的目标。
b/bl
等指令实现跳转。