我们这一节以一个实现了计时器的ROP为例,讲解写ROP的方式和注意事项,顺便复习前面的内容。(仅适用于991ES PLUS)
程序本体(作者是user202729)
home:
# num y = 1
xr0 = 0x8154, 0x01, 0x30
num_fromdigit
# num z = 1
er0 = 0x8160
num_fromdigit
r0 = r2 = 0
er0 = use_buffer
[er0] = r2
loop:
# y += z
xr0 = 0x8154, 0x8160
num_add
er0 = 0x8154
num_output_print
# set lr
# render
set lr
er0 = 0x03e8
delay
# restore the stack
xr0 = 0x82B8, 0x8254
strcpy_null
goto loop
一行行讲解:
- # 开头的行是注释
-
home: 和 loop: 是标签,只是一个标识,不会被执行。
标签home就是0x8DA4,即基本溢出时执行关键的pop pc
时的sp值 -
xr0 = 0x8154, 0x01, 0x30
设置了寄存器xr0的值,为下一步调用num_fromdigit函数设置参数。0x8154
就是随便找的一个内存中空闲的位置,可以修改。 -
num_fromdigit
调用了该函数。记住要跳转到push lr
指令的后面。这个函数的功能是把r2的值转化为计算器运算所使用的数据类型,并存储到er0的地址中。 -
er0 = 0x8160
设置寄存器er0的值。注意,函数执行完过后往往寄存器的值会发生变化。具体哪些寄存器不会变,哪些会变要看代码具体分析。这里已经知道num_fromdigit函数不会改变xr0的值,因此没有重新设置整个xr0,而只设置了er0。 -
num_fromdigit
再次调用了该函数。原因很简单,计时器需要一个加数和被加数,注释写的很明白。 r0 = r2 = 0 er0 = 0x811D [er0] = r2
这三行每一行都是一个gadget,意思很明确就不赘述了。[er0]意思是er0中的地址对应的位置,也就是把er0视为一个指针(*er0
)并解引用。学过C语言的应该很容易理解。这三行把数值0写入到了内存单元0x811D中,这个内存单元指示了是否使用屏幕缓存区。那些打印函数会根据这个内存单元的值决定是写入到屏幕缓存还是真实的屏幕上。
xr0 = 0x8154, 0x8160
同理,设置寄存器,为了下一步调用加法函数num_add
调用加法函数,把*er2
的计算器数值加到*er0
里面去-
er0 = 0x8154 num_output_print
把位于0x8154的计算器数值打印在屏幕上
set lr
设置lr,前面讲过lr的作用,这个gadget的作用就是设置lr寄存器,让lr指向一条pop pc
指令,这样的lr被我们称作Good,当然与之对应存在Bad的lr。只有lr是Good的时候,跳转到push lr
处或者以rt
结尾的函数处才是可接受的,也就是说程序流依然可以控制。如果lr的值是bad的情况下跳转,会导致程序流执行到不可预料的部分。所有以pop pc
结尾的函数都会破坏寄存器lr的值。-
er0 = 0x03e8 delay
这两条调用了
delay(0x03E8)
,这个函数以 rt 结尾,因此要保证 lr 的值是 Good。delay函数会延时er0毫秒后返回。 -
xr0 = 0x82B8, 0x8254 strcpy_null
这两条调用了
strcpy_null(0x82B8, 8254)
。还原了ROP本身。原理和基本溢出类似。总之执行完后可以还原因为执行函数而破坏了的ROP程序 最后一个
goto loop
跳转回loop标签处继续执行。当然在翻译时要注意扩充成上一节提到的10个字节。
翻译
16进制
写好程序后要对照函数表和gadget表,先把程序翻译成16进制形式。
这两张表在user202729位于github的fxesplus项目中以文本文档的形式给出。
函数表是文件labels,gadget表是文件gadgets。如果没有电脑可以抄下来。
链接如下:
991ES PLUS
82ES PLUS A
注意:这两张表的内容不一定完全,计算器中还含有大量实用的gadget和函数,可以通过查阅源码获取更多。
翻译后结果如下:
EE 54 31 30 ;pop xr0
54 81 01 30 ;0x8154, 0x01, 0x30
F6 B0 31 30 ;num_fromdigit 将数值1存储到0x8154
A0 32 30 30 ;pop er0
60 81 ;0x8160
F6 B0 31 30 ;num_fromdigit 将数值1存储到0x8160
3A 85 31 30 ;r0 = r2 = 0
A0 32 30 30 ;pop er0
1D 81 ;0x811D
6A 27 30 30 ;[er0] = r2 关闭屏幕缓冲区
EE 54 31 30 ;pop xr0
54 81 60 81 ;0x8154, 0x8160
B4 4A 31 30 ;num_add 0x8154处的数值自增1
A0 32 30 30 ;pop er0
54 81 ;0x8154
06 2C 30 30 ;num_output_print 将0x8154处的数值显示到屏幕上
63 1E 31 30 ;set lr
A0 32 30 30 ;pop er0
E8 03 ;0x03E8
41 46 30 30 ;delay 延时1秒
EE 54 31 30 ;pop xr0
C2 82 5E 82 ;0x82C2, 0x825E
60 27 30 30 ;strcpy_null 复原栈中的ROP
6A 4F 30 30 ;pop er14
FE 8C ;0x8CFE
68 4F 30 30 ;sp=er14, pop er14
再次提醒,翻译结果不唯一,因为内存中全是hackstring的副本,所以strcpy函数的参数和er14的值只要合理即可。
计算器式子
下一步是把上面的16进制式子对照符号表翻译成符号。注意:因为程序的开头位于输入区的第53个字节,所以需要把程序的开头放在该位置,如果剩余位置不足,就跳转到最前面继续输入,翻译结果如下:
sin( 2 0 0 M 𝐞 cs6 , 0 0 Abs( cs17 1 0 sin( 2 0 0 cv18 cs3 A F 0 0 cv24 M 1 0 RanInt#( π ^( π - cs23 0 0 ∫( ÷ 0 0 cv40 Ran# log( ÷ 0 0 7 8 9 0 1 2 cv24 M 1 0 M 𝐞 cs1 0 cv32 sin⁻( 1 0 sin( 2 0 0 - 𝐞 cv32 sin⁻( 1 0 : ° 1 0 sin( 2 0 0 cs16 𝐞 ∫( cs23 0 0 cv24 M 1 0 M 𝐞 - 𝐞 cs30 →D 1 0
之后的工作就非常简单了,直接把这串式子用前面提到的bug输到输入区里就行。打不出来的字符要用不稳定字符刷,不再赘述。
操作视频
资源下载
计算器反编译源码:
度盘 提取码: nykm