CTF_WIKI_PWN!

LINUX PWN

  • 记录ctf_wiki的学习

Linux Pwn

安全防护机制

canary

泄漏栈中的Canary


Canary 设计为以字节 \x00 结尾,本意是为了保证 Canary 可以截断字符串。
泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。

要点: 读出数据 栈溢出

比如:覆盖\x00,然后用输出函数输出并截取Canary部分 可用来栈溢出

one-by-one 爆破 Canary

要点:通过 fork 函数开启子进程交互Canary不变

劫持__stack_chk_fail 函数

要点:修改 GOT 表劫持这个函数

例子:参见 ZCTF2017 Login,利用方式是通过 fsb 漏洞篡改 __stack_chk_fail 的 GOT 表,再进行 ROP 利用

覆盖 TLS 中储存的 Canary 值

要点:当溢出尺寸较大时,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过。

例子:参见 StarCTF2018 babystack

PIE 和 ASLR

PIE 是无限手套,ASLR是大番薯

PIE 在编译的过程中赋予了 ELF加载到内存时其加载基址随机化的功能

  • PIE编译出来的ELF如果ASLR=0,ELF的加载基址也是不会变。

ASLR 的三个级别变成了 :

  • 0, 不开启任何随机化;
  • 1, 开启stacklibraries [、executable base(special libraries -^-) if PIE is enabled while compiling] 的随机化;
  • 2,开启heap随机化(相对于executable base)。

RELRO

在没有开启 RELRO 保护的前提下,每个 libc 的函数对应的 GOT 表项是可以被修改的。

栈溢出


stack pivoting

劫持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行 ROP

一般来说,我们可能在以下情况需要使用 stack pivoting

  • 可以控制的 栈溢出的字节数较少,难以构造较长的 ROP 链
  • 开启了 PIE 保护,栈地址未知,我们可以将栈劫持到已知的区域。
  • 其它漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写 rop 及进行堆漏洞利用

此外,利用 stack pivoting 有以下几个要求

  • 可以控制程序执行流
  • 可以控制 sp 指针。一般来说,控制栈指针会使用 ROP,常见的控制栈指针的 gadgets 一般是
1
pop rsp/esp

frame faking

构造一个虚假的栈帧来控制程序的执行流。

要求:只能控制ebp和eip,要求知道可写入地址

  • 入口点
1
2
push ebp  # 将ebp压栈
mov ebp, esp #将esp的值赋给ebp
  • 出口点
1
2
leave
ret #pop eip,弹出栈顶元素作为程序下一个执行地址
  • leave 指令相当于
1
2
mov esp, ebp # 将ebp的值赋给esp
pop ebp # 弹出ebp

一般栈结构:

1
buffer padding|ebp|ret addr|

payload:

1
buffer padding|fake ebp|leave ret addr|
1
2
3
4
fake ebp
|
v
ebp2|target function addr|leave ret addr|arg1|arg2

then 栈结构:

1
2
3
4
ebp
|
v
ebp2|leave ret addr|arg1|arg2

partial overwrite

在开启了随机化(ASLR,PIE)后, 无论高位的地址如何变化,低 12 位的页内偏移始终是固定的, 也就是说如果我们能更改低位的偏移, 就可以在一定程度上控制程序的执行流, 绕过 PIE 保护。

要求:绕过PIE


格式化字符串漏洞


常用:

  • %N$s
  • %N$p
  • %n$nx
  • %n$n
  • 可以利用 %hhn 向某个地址写入单字节,利用 %hn 向某个地址写入双字节。

泄露任意地址内存

1
addr%k$s

覆盖任意地址内存

覆盖小数字

1
aa%k$nxx

覆盖大数字

1
p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+pad1+'%k$hhn'+pad2+'%(k+1)$hhn'+pad3+'%(k+1)$hhn'+pad4+'%(k+1)$hhn'

小技巧总结

  1. 利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
  2. 利用 %s 来获取变量所对应地址的内容,只不过有零截断。
  3. 利用 %order$x 来获取指定参数的值,利用 %order$s 来获取指定参数对应地址的内容。

scanf 函数会对 0a,0b,0c,00 等字符有一些奇怪的处理,,导致无法正常读入,,

整数溢出


上界溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 伪代码
short int a;

a = a + 1;
# 对应的汇编
movzx eax, word ptr [rbp - 0x1c]
add eax, 1
mov word ptr [rbp - 0x1c], ax

unsigned short int b;

b = b + 1;
# assembly code
add word ptr [rbp - 0x1a], 1

上界溢出有两种情况,一种是 0x7fff + 1, 另一种是 0xffff + 1

有符号短整型 无符号短整型
溢出点 0x7fff + 1 0xffff + 1
汇编语言 add eax, 1

mov word ptr [rbp - 0x1c], ax | add word ptr [rbp - 0x1a], 1 |

共点: 从正数变成负数 or 0

下界溢出

第一种是 sub 0x0000, 1 == 0xffff,对于有符号来说 0 - 1 == -1 没问题,但是对于无符号来说就成了 0 - 1 == 65535

第二种是 sub 0x8000, 1 == 0x7fff,对于无符号来说是 32768 - 1 == 32767 是正确的,但是对于有符号来说就变成了 -32768 - 1 = 32767

共点: 从负数 or 0变成正数

常见情况

  • 未限制范围
  • 错误的类型转换

堆漏洞

calloc

1
2
3
4
calloc(0x20);
//等同于
ptr=malloc(0x20);
memset(ptr,0,0x20);

除此之外,还有一种分配是经由 realloc 进行的,realloc 函数可以身兼 malloc 和 free 两个函数

realloc 的操作并不是像字面意义上那么简单,其内部会根据不同的情况进行不同操作

  • 当 realloc(ptr,size) 的 size 不等于 ptr 的 size 时
    • 如果申请 size > 原来 size
      • 如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
      • 如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
    • 如果申请 size < 原来 size
      • 如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
      • 如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
  • 当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
  • 当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作

###off-by-one 利用思路

  1. 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
  2. 溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的后一块(理论上是当前正在 unlink 的块)与当前正在 unlink 的块大小是否相等。

最新版本代码中,已加入针对 2 中后一种方法的 check ,但是在 2.28 前并没有该 check 。

TIPS

系统调用

eax:编号 ebx,ecx,edx,esi,edi:参数及顺序

ROP 常见的拼凑效果是实现一次系统调用,Linux系统下对应的汇编指令是 int 0x80。执行这条指令时,被调用函数的编号应存入 eax,调用参数应按顺序存入 ebx,ecx,edx,esi,edi 中。例如,编号125对应函数

1
mprotect (void *addr, size_t len, int prot)

,可用该函数将栈的属性改为可执行,这样就可以使用 shellcode 了。假如我们想利用系统调用执行这个函数,eax、ebx、ecx、edx 应该分别为“125”、内存栈的分段地址(可以通过调试工具确定)、“0x10000”(需要修改的空间长度,也许需要更长)、“7”(RWX 权限)。

64位传参

顺序 RDI, RSI, RDX, RCX, R8, R9, more on the stack

libc_csu_init 妙用

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
gef➤  x/5i 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
gef➤ x/5i 0x000000000040061b
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
gef➤ x/5i 0x000000000040061A+3
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
gef➤ x/5i 0x000000000040061e
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
0x400625: nop
gef➤ x/5i 0x000000000040061f
0x40061f <__libc_csu_init+95>: pop rbp
0x400620 <__libc_csu_init+96>: pop r14
0x400622 <__libc_csu_init+98>: pop r15
0x400624 <__libc_csu_init+100>: ret
0x400625: nop
gef➤ x/5i 0x0000000000400620
0x400620 <__libc_csu_init+96>: pop r14
0x400622 <__libc_csu_init+98>: pop r15
0x400624 <__libc_csu_init+100>: ret
0x400625: nop
0x400626: nop WORD PTR cs:[rax+rax*1+0x0]
gef➤ x/5i 0x0000000000400621
0x400621 <__libc_csu_init+97>: pop rsi
0x400622 <__libc_csu_init+98>: pop r15
0x400624 <__libc_csu_init+100>: ret
0x400625: nop
gef➤ x/5i 0x000000000040061A+9
0x400623 <__libc_csu_init+99>: pop rdi
0x400624 <__libc_csu_init+100>: ret
0x400625: nop
0x400626: nop WORD PTR cs:[rax+rax*1+0x0]
0x400630 <__libc_csu_fini>: repz ret

函数输入

  • read 不会给输入末尾补上 ‘\0’

变量的储存

  • 变量的存储格式为以小端存储,即最低有效位存储在低地址
  • 已初始化的全局变量储存在代码中,不在堆栈中

hijack GOT

  • 这一步一般来说需要我们利用函数的漏洞来进行触发。一般利用方法有如下两种

    • 写入函数:write 函数。
    • ROP
    1
    2
    3
    pop eax; ret;           # printf@got -> eax
    pop ebx; ret; # (addr_offset = system_addr - printf_addr) -> ebx
    add [eax] ebx; ret; # [printf@got] = [printf@got] + addr_offset
    • 格式化字符串任意地址写

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. LINUX PWN
    1. 1.1. Linux Pwn
    2. 1.2. 安全防护机制
      1. 1.2.1. canary
        1. 1.2.1.1. 泄漏栈中的Canary
        2. 1.2.1.2. one-by-one 爆破 Canary
        3. 1.2.1.3. 劫持__stack_chk_fail 函数
        4. 1.2.1.4. 覆盖 TLS 中储存的 Canary 值
      2. 1.2.2. PIE 和 ASLR
      3. 1.2.3. RELRO
    3. 1.3. 栈溢出
      1. 1.3.1. stack pivoting
      2. 1.3.2. frame faking
      3. 1.3.3. partial overwrite
    4. 1.4. 格式化字符串漏洞
      1. 1.4.1. 泄露任意地址内存
      2. 1.4.2. 覆盖任意地址内存
        1. 1.4.2.1. 覆盖小数字
        2. 1.4.2.2. 覆盖大数字
    5. 1.5. 整数溢出
      1. 1.5.1. 上界溢出
      2. 1.5.2. 下界溢出
      3. 1.5.3. 常见情况
    6. 1.6. 堆漏洞
      1. 1.6.1. calloc
  2. 2. TIPS
    1. 2.1. 系统调用
    2. 2.2. 64位传参
    3. 2.3. libc_csu_init 妙用
    4. 2.4. 函数输入
    5. 2.5. 变量的储存
    6. 2.6. hijack GOT
载入天数...载入时分秒...