在ARMv7的有效负载中调用函数

时间:2018-09-05 21:39:04

标签: assembly arm shellcode cortex-m3 thumb

我想为我的ARMv7平台编写一个简单的有效负载。首先,我尝试了一个通过UART发送字符的简单循环:

{rand-guid}

这是我想要的有效载荷,一次在C中,一次在汇编中。

void payload()
{
  while(1)
  {
    USART3->DR = 0x68;
  }
}

08000358 <payload>:
 8000358:   b480        push    {r7}
 800035a:   af00        add r7, sp, #0
 800035c:   4b01        ldr r3, [pc, #4]    ; (8000364 <payload+0xc>)
 800035e:   2268        movs    r2, #104    ; 0x68
 8000360:   809a        strh    r2, [r3, #4]
 8000362:   e7fb        b.n 800035c <payload+0x4>
 8000364:   40004800    andmi   r4, r0, r0, lsl #16

这很好用,字符通过UART发送。现在,我想在有效负载中调用一个函数:

int main()
{
  uint32 buffer[4];
  buffer[0] = 0xaf00b480;
  buffer[1] = 0x22684b01;
  buffer[2] = 0xe7fb809a;
  buffer[3] = 0x40004800;
  memcpy(0x20004000,&buffer,4*sizeof(uint32));
  goto *(void *)((uint32_t) buffer | 1);

  return 0;
}
再次,我想要的有效载荷,一次在C中,一次在汇编中。

void payload()
{
  while(1)
    {
      USART3->DR = 0x68;
    asm volatile(
      "bl 0x08000348\n"
    );
  }
}

08000358 <payload>:
 8000358:   b480        push    {r7}
 800035a:   af00        add r7, sp, #0
 800035c:   4b02        ldr r3, [pc, #8]    ; (8000368 <payload+0x10>)
 800035e:   2268        movs    r2, #104    ; 0x68
 8000360:   809a        strh    r2, [r3, #4]
 8000362:   f7ff fff1   bl  8000348 <function>
 8000366:   e7f9        b.n 800035c <payload+0x4>
 8000368:   40004800    andmi   r4, r0, r0, lsl #16

角色现在仅发送3次,然后出现崩溃(处理器的故障处理程序)。我检查了函数的内存区域和缓冲区,两者看起来相同:

void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}

int main()
{
  uint32 buffer[5];
  buffer[0] = 0xaf00b480;
  buffer[1] = 0x22684b02;
  buffer[2] = 0xf7ff809a;
  buffer[3] = 0xe7f9fff1;
  buffer[4] = 0x40004800;

  memcpy(0x20004000,&buffer,5*sizeof(uint32));
  goto *(void *)((uint32_t) buffer | 1);

  return 0;
}

1 个答案:

答案 0 :(得分:1)

goto *(void *)((uint32_t)buffer | 1);

对于使用不必要的内联汇编的人来说,

很奇怪。

编译时,我得到:

  38:   2201        movs    r2, #1
  3a:   4313        orrs    r3, r2
  3c:   469f        mov pc, r3
  3e:   46c0        nop 

那应该马上失败,

来自arm文档:

  

ADD(寄存器)和MOV(寄存器)分支没有互通。

您可以做的是使用一些真实的asm

.thumb
.thumb_func
.globl HOP
HOP:
   orr r0,#1
   bx r0


void HOP ( uint32_t *);
void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}

int main()
{
  uint32_t buffer[5];
  buffer[0] = 0xaf00b480;
  buffer[1] = 0x22684b02;
  buffer[2] = 0xf7ff809a;
  buffer[3] = 0xe7f9fff1;
  buffer[4] = 0x40004800;

//  memcpy(0x20004000,&buffer,5*sizeof(uint32));
//  goto *(void *)((uint32_t) buffer | 1);
  HOP(buffer);
  return 0;
}

  38:   0018        movs    r0, r3
  3a:   f7ff fffe   bl  0 <HOP>

现在,如果您不希望使用分支链接,而是希望使用分支,并且由于您对内联程序集的使用是自由的,则这更像您的样式,并且应该不会产生至少我的编译器生成的mov问题。顺便说一句,我们需要查看您的编译器生成了什么,以查看为什么您没有立即崩溃。

void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}
int main()
{
  uint32_t buffer[5];
  buffer[0] = 0xaf00b480;
  buffer[1] = 0x22684b02;
  buffer[2] = 0xf7ff809a;
  buffer[3] = 0xe7f9fff1;
  buffer[4] = 0x40004800;
  asm (
      "add r3,r7,#4\n"
      "mov pc,r3\n"
  );
  return 0;
}

  36:   1d3b        adds    r3, r7, #4
  38:   469f        mov pc, r3

bx可以用来使它感觉更好

int main()
{
  uint32_t buffer[5];
  buffer[0] = 0xaf00b480;
  buffer[1] = 0x22684b02;
  buffer[2] = 0xf7ff809a;
  buffer[3] = 0xe7f9fff1;
  buffer[4] = 0x40004800;
  asm (
      "add r3,r7,#5\n"
      "bx r3\n"
  );
  return 0;
}

  36:   1d7b        adds    r3, r7, #5
  38:   4718        bx  r3

一旦fifo不再填充字符,您就无法以这种方式对待uart数据寄存器。

您也无法复制此代码并以这种方式运行它。

 8000362:   f7ff fff1   bl  8000348 <function>

对于内置的Flash来说很好,只要8000348保留在该函数所在的位置,但是bl是与pc相关的,因此如果您要将其复制到sram(我知道我自己的问题的答案,将让您知道我知道这是一个微控制器,所以您想询问的是armv7-m而不是armv7)。

 20004000:   b480        push    {r7}
 20004002:   af00        add r7, sp, #0
 20004004:   4b02        ldr r3, [pc, #8]    ; 20004010 
 20004006:   2268        movs    r2, #104    ; 0x68
 20004008:   809a        strh    r2, [r3, #4]
 2000400A:   f7ff fff1   bl  20003FF0
 2000400E:   e7f9        b.n 20004004
 20004010:   40004800 

我想您没有将function()复制到0x20003FF0吗?运行该程序时,您在该地址有哪些数据?看起来是什么样的?

因此,大多数情况下都是使用汇编语言来“获取它”,但是却错过了一些事情。

现在您可以做的就是编写所需的代码:

.thumb
top:
ldr r3,=0x40004800
mov r2,#68
str r2,[r3,#4]
ldr r0,=function
blx r0
b top

无需链接

00000000 <top>:
   0:   4b02        ldr r3, [pc, #8]    ; (c <top+0xc>)
   2:   2244        movs    r2, #68 ; 0x44
   4:   605a        str r2, [r3, #4]
   6:   4802        ldr r0, [pc, #8]    ; (10 <top+0x10>)
   8:   4780        blx r0
   a:   e7f9        b.n 0 <top>
   c:   40004800    andmi   r4, r0, r0, lsl #16
  10:   00000000    andeq   r0, r0, r0

并使用您的样式

void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}
int main()
{
uint32_t buffer[10];
buffer[0]=0x22444b02;
buffer[1]=0x4802605a;
buffer[2]=0xe7f94780;
buffer[3]=0x40004800;
buffer[4]=((uint32_t)function)|1;
  asm (
      "add r3,r7,#5\n"
      "bx r3\n"
  );
  return 0;
}

使用我的编译器:

08000000 <function>:
 8000000:   b580        push    {r7, lr}
 8000002:   af00        add r7, sp, #0
...
 8000016:   af00        add r7, sp, #0
 8000018:   003b        movs    r3, r7
 800001a:   4a0c        ldr r2, [pc, #48]   ; (800004c <main+0x3a>)
 800001c:   601a        str r2, [r3, #0]
...
 800003a:   1d7b        adds    r3, r7, #5
 800003c:   4718        bx  r3
...
 800004c:   22444b02
 8000050:   4802605a
 8000054:   e7f94780
 8000058:   40004800
 800005c:   08000001

仍然像您的代码一样有点骇人听闻,但是现在您可以从4字节对齐的地址开始重新定位此代码,并且不会出现无法调用的问题 函数功能。

是的,在这个示例中,我欺骗了链接,但没有引导程序。真正困扰我的人默认情况下使用堆栈帧,这真是浪费。我想知道是否可以在没有该工具的情况下构建我的工具链。否则,确实可以使此hacky解决方案更好地工作:

08000000 <function>:
 8000000:   46c0        nop         ; (mov r8, r8)
 8000002:   46c0        nop         ; (mov r8, r8)
 8000004:   46c0        nop         ; (mov r8, r8)
 8000006:   4770        bx  lr

08000008 <main>:
 8000008:   b08a        sub sp, #40 ; 0x28
 800000a:   466b        mov r3, sp
 800000c:   4a0a        ldr r2, [pc, #40]   ; (8000038 <main+0x30>)
 800000e:   601a        str r2, [r3, #0]
 8000010:   466b        mov r3, sp
 8000012:   4a0a        ldr r2, [pc, #40]   ; (800003c <main+0x34>)
 8000014:   605a        str r2, [r3, #4]

然后可以使用

void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}
int main()
{
uint32_t buffer[10];
buffer[0]=0x22444b02;
buffer[1]=0x4802605a;
buffer[2]=0xe7f94780;
buffer[3]=0x40004800;
buffer[4]=((uint32_t)function);
  asm (
      "mov r3,sp\n"
      "orr r3,r3,#1\n"
/*    "add r3,#1\n" */
      "bx r3\n"
  );
  return 0;
}

是的,是的,您不必为该工具链已经设置好就可以设置为1的功能。

08000008 <main>:
 8000008:   b08a        sub sp, #40 ; 0x28
 800000a:   4b09        ldr r3, [pc, #36]   ; (8000030 <main+0x28>)
 800000c:   9300        str r3, [sp, #0]
 800000e:   4b09        ldr r3, [pc, #36]   ; (8000034 <main+0x2c>)
 8000010:   9301        str r3, [sp, #4]
 8000012:   4b09        ldr r3, [pc, #36]   ; (8000038 <main+0x30>)
 8000014:   9302        str r3, [sp, #8]
 8000016:   4b09        ldr r3, [pc, #36]   ; (800003c <main+0x34>)
 8000018:   9303        str r3, [sp, #12]
 800001a:   4b09        ldr r3, [pc, #36]   ; (8000040 <main+0x38>)
 800001c:   9304        str r3, [sp, #16]
 800001e:   466b        mov r3, sp
 8000020:   f043 0301   orr.w   r3, r3, #1
 8000024:   4718        bx  r3
 8000026:   2300        movs    r3, #0
 8000028:   4618        mov r0, r3
 800002a:   b00a        add sp, #40 ; 0x28
 800002c:   4770        bx  lr
 800002e:   bf00        nop
 8000030:   22444b02    subcs   r4, r4, #2048   ; 0x800
 8000034:   4802605a    stmdami r2, {r1, r3, r4, r6, sp, lr}
 8000038:   e7f94780    ldrb    r4, [r9, r0, lsl #15]!
 800003c:   40004800    andmi   r4, r0, r0, lsl #16
 8000040:   08000001    stmdaeq r0, {r0}

现在有了内联的asm魔术,您可以将缓冲区的地址加载到r3中,而不必依赖于代码的反汇编来解决这一问题。请注意,尽管您将其称为armv7,但您似乎仍在使用armv7-m。您可以使用thumb2指令orr r3,r3,#1在精神上是正确的,您希望在该位置上的位不添加它。但是,如果这是一个类似于cortex-m0的armv6-m,或者您想要可移植性,则只需像add r3,#1一样丑陋地进行添加,因为这不是thumb2指令,而且是可移植的。

因此,如果您输入了几个字符,我怀疑您的goto工作正常,也许听起来您正在使用qemu?也许那是怎么回事?但是没有尝试在sram中运行代码的副本,而是向后分支以查找function(),但在那里找到了数据,这使您崩溃了,或者因为那是在复制代码之前,它再次运行了复制代码几次然后回到那里的一切最终脱离了轨道。也许是因为您没有正确使用uart导致缓冲区溢出,这至少在这些品牌之一上可能导致uart停止工作,直到您清除缓冲区溢出标志。假设您依靠uart输出来查看成功或失败。 (您会在运行该程序时遇到失败,而没有调用函数,这使我相信这是一个模拟,而不是真正的硬件,或者真正的硬件,您只是看到了要推入uart的部分值)。

试试看

void payload()
{
  uint32_t ra; 
  for(ra=0x30;;ra++)
  {
    ra&=0x37;
    USART3->DR = ra;
  }
}

作为起点,您将永远看到0123456701234567,然后再看我在说什么,那么我怀疑您使用的是模拟器而不是硬件。...

编辑:

orr 1是goto的问题,您可以这样做:

void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}
int main()
{
    uint32_t buffer[10];
    buffer[0]=0x22444b02;
    buffer[1]=0x4802605a;
    buffer[2]=0xe7f94780;
    buffer[3]=0x40004800;
    buffer[4]=((uint32_t)function);

    goto *(void *)((uint32_t) buffer);
    return 0;
}

EDIT2

该程序已经在SRAM中,您只需将其从SRAM复制到SRAM ...如果该副本是“漏洞利用”,那么就可以了,但是在微控制器上,您不能将其复制到其他代码之上,大多数当所有代码用完Flash时,一个sram位置和另一个sram位置一样好。 无论如何,这个词在这里不是问题。

在下面的评论中

  

在主要情况下,我分支到地址0x20004001。

不是,这是同样的错误,如果您想使用一个正确的地址来计算地址,则需要使用正确的指令。

void function()
{
    asm volatile(
      "nop\n"
      "nop\n"
    );
}
int main()
{
    uint32_t buffer[10];
    buffer[0]=0x22444b02;
    buffer[1]=0x4802605a;
    buffer[2]=0xe7f94780;
    buffer[3]=0x40004800;
    buffer[4]=((uint32_t)function);

    //memcpy...
    //goto *(void *)((uint32_t) buffer);
    goto *(void *)(0x20004000);
    return 0;
}

计算机上的编译器给出

  36:   4b06        ldr r3, [pc, #24]   ; (50 <main+0x3e>)
  38:   469f        mov pc, r3
  3a:   46c0        nop         ; (mov r8, r8)
  3c:   22444b02    subcs   r4, r4, #2048   ; 0x800
  40:   4802605a    stmdami r2, {r1, r3, r4, r6, sp, lr}
  44:   e7f94780    ldrb    r4, [r9, r0, lsl #15]!
  48:   40004800    andmi   r4, r0, r0, lsl #16
  4c:   00000000    andeq   r0, r0, r0
  50:   20004000    andcs   r4, r0, r0

链接后仍然可以使用。

但是,如果您这样做

goto *(void *)(0x20004001);

  36:   4b06        ldr r3, [pc, #24]   ; (50 <main+0x3e>)
  38:   469f        mov pc, r3
  3a:   46c0        nop         ; (mov r8, r8)
  3c:   22444b02    subcs   r4, r4, #2048   ; 0x800
  40:   4802605a    stmdami r2, {r1, r3, r4, r6, sp, lr}
  44:   e7f94780    ldrb    r4, [r9, r0, lsl #15]!
  48:   40004800    andmi   r4, r0, r0, lsl #16
  4c:   00000000    andeq   r0, r0, r0
  50:   20004001    andcs   r4, r0, r1

与该答案的最高错误相同:

  

ADD(寄存器)和MOV(寄存器)分支没有互通。

在arm文档中搜索该行和/或该术语。

相关问题