[心得] 基于内存溢位存取进行程式码注入攻击

楼主: roger5486 (Cin_Sung)   2020-03-14 22:34:31
经验分享──基于内存溢位存取进行程式码注入攻击
序、
自己于慕尼黑工业大学电机学院(TUM EI)中修习Embedded System and Security课程,其
中作业与组合语言、嵌入式系统及资料安全相关,第一次学习这种系统安全的内容觉得相
当有趣,但搞这个作业时也觉得相当挫败,正好放假很闲,分享予各位。
平台为Infineon xmc4500 relax kit 开发板,arm cortex-m4
作业题目为在开发板上、于给定的程式中进行code injection attack,点亮开发板上的L
ED作为成功注入程式码的证据。题目给定之程式为一加密功能的程式,要求便是攻击此目
标程式。透过于电脑上执行以python传输明码(plaintext)至嵌入式平台,目标程式加密
此plaintext,并将密文回传至电脑上,流程图如下,【攻击方式便是透过特殊的plainte
xt输入来执行攻击】。作业内容算是偏简单,给无经验的骇客初学者练习个几天多半就能
完成。
https://i.imgur.com/vZ7Q2kF.jpg
Figure 0. flow chart
基本思路为透过stack overflow存取到return address (link register位置所储存之资
料),修改return address至injected code的位置,在pop pc指令时使pc (program coun
ter)跳转到injected code,完成code injection后再跳回正常程式的下个执行点。
自己在家要重现此事较为困难,由于需要有相似/相同之开发板XMC4500作为执行平台,并
且由于原始码著作权属于课程助教,在此我不会提供完整题目程式码,仅提供片段作为学
习用途,但我想可能得有些先备知识才较能看懂我在写什么,出于懒猫猫问题我没办法把
每件事情写到很详细,措辞可能会有不对的地方,例如lr其实不是个内存,是个regist
er,但我会混用把储存lr的内存位置称作lr,不过应该差不多啦,自己搞过一遍应该能
想通我在供三小。
各种内容都会中英混杂,搭配奇怪的缩写,修embedded system时最恨的就是缩写,教授
跟助教永远都用缩写但我永远都不知道是什么意思,简单列几个大概会用到的
pc, program counter
sp, stack pointer
lr, link register
fp, frame pointer
KEY WORDS: code injection attack, stack-based buffer overflow, embedded system
security
一、 基础知识,Instruction memory与data memory
在内存储存中的内容分为几种,我这次用上的是instruction区和stack,分别对应储存
CPU指令内容(机器码)跟local variable,其他还有像是heap储存dynamic allocated v
ariable、储存initialized global variable的空间、储存uninitialized global varia
ble的空间,不过这些不重要。这些空间通常有固定的起始、终点内存位置,可以透过a
rm给的datasheet查到。在我这次使用的平台,arm cortex-m4,Instruction memory始于
0x0800 0000,stack始于0x1000 0000,所以像是我的嵌入式系统在运作时,就会执行Ins
truction memory中的Machine Code (又或是可以视为反组译后的Assembly code),funct
ion中使用的local variable就储存在stack中。
在执行function call的时候,CPU会做几件事情,0. Branch到function,pc跳转到此fun
ction的instruction memory address,lr记录caller的下个instruction memory addres
s;1. 记录lr,即下个instruction的address,作为在function return时pc要回到的位
置,此数值push到stack;2. 记录其他杂七杂八的register,一样push到stack;3. 最后
会把sp减去一定数值,把这些空出来的位置存local variable。这部分内容看看下方asse
mbly
code应该可以理解。
在执行function return时,CPU会做几件事情,0. 把sp加回一定数值,因为local varia
ble在function return后就没用了;1. pop 一些register;2. pop pc,把过去lr记录的
数值弄回pc,这样pc就会回到caller的下个instruction memory。一样看看assembly cod
e应该可以理解。
https://i.imgur.com/BCv8mUJ.jpg
Figure 1. instruction memory
https://i.imgur.com/3kTHXgx.jpg
Figure 2. stack (local variable)
二、 基础知识,buffer overflow与code injection attack
由于local variable和register、return address (lr)都储存在stack中,如果在local
variable中有静态内存配置阵列,且user可以随意赋予值而没有长度确认,则可以轻松
做到stack-based buffer overflow,即为漏洞,vulnerability,常见于memcpy, strcpy

code injection attack就是透过把自己想要执行的程式码,存到stack中,并将PC设定到
这个地址来执行它。
透过写入超过给定array长度的资料,做到stack-based buffer overflow,修改到return
address的地址数值,就可以做到最简单的code injection attack。
三、 本次题目的vulnerability
找漏洞算是很麻烦的地方,在这次作业中很贴心的附上了原始码与debugger,但还是要些
经验才容易找到,至少一开始我花了整天在另一个function上。本题的加密其实跟要做co
de injection没什么关系,毕竟也不是要找ciphertext与plaintext的mapping,如下图,
加密只是XOR一个固定的数值,透过先加密个一串零就可以知道key是多少。
https://i.imgur.com/oME10vw.jpg
Figure 3. Vulnerability
上图程式码中,buf作为local variable且为静态内存配置之阵列,会储存128 bytes于
stack中,由于没有对长度作确认,当plaintext长度大于128 bytes时即发生stack overf
low。透过debugger可以知道此frame中的各种情报,包含buf内存位置始于多少、lr位
置为何,两个一减就知道要写入多长的资料到buf可以overflow到lr的位置。
https://i.imgur.com/e6lEXps.jpg
Figure 4. debugger, info frame
由上图debugger资讯中,info frame 给了lr资料储存于内存位置0x1000 07c4,print
&buf给了buf资料始于0x1000 0738,写入0x1000 07c4 - 0x1000 0738 + 4 bytes的资料
至buf即可将最后的4bytes写入到lr资料位置。
【只要将地址0x1000 07c4中的值,改写成0x1000 0739】(某些原因要多1),function re
turn后就会执行自buf开始的指令。意即,把自己要执行的机器码塞到buf中,并且把lr改
写,让return时pc跳转到自buf开始的instruction memory address。
四、 Code injection attack攻击实作
好,所以现在来回到一开始讲的攻击方式,透过输入特殊的plaintext来攻击,已知这段p
laintext有0x1000 07c4 - 0x1000 0738 + 4 bytes,最后4bytes要为0x1000 0739,且要
使buf前面一段作为injected code。
Injected code的生成,首先得搞出机器码,这里拿点亮板子上的LED为例,不过基本上就
是你想干嘛就干嘛。要搞出机器码也容易,写个点亮LED的C code→编译→反组译出assem
bly code打开就会看到assembly code和machine code排排站站好了,取自己要的部分。
前述提及XOR加密部分,所以弄出来的machine code要先XOR一遍key,在function执行时
会再XOR key一遍,才会回到正确的machine code。此外,需要逐byte作一些顺序上的交
换,毕竟到底内存要往上/下读、读完后要往上/下跑,这件事情很繁杂,不过拿debugg
er去查memory中存的值很好debug,就尝试看看慢慢调整就好。
把plaintext写到这里应该可以点亮LED了,不过有个问题是程式接下来不会正常运行,毕
竟把pc设到0x10开头的地方本身就很有问题,正常的machine code存放在0x08开头的地方
,所以要再做一些加工,不过这里就可能得写assembly code了。
要让程式回归正常运行的办法就是得把register中的值(包含pc/lr,其实它们也是个regi
ster)回复到正常的情况,当初为了修改到lr,一并修改了各个register中的值。因此在
点亮LED的machine code后面加入一些对register赋值的machine code,最后把pc设回正
常该回到的地方,就可以使程式继续正常运行而不会发生错误。这边由于得针对register
作处理,我自己写了几行assembly code来编译出machine code,这也是这次作业中自己
要写assembly code的地方了,其他就只是读,然后写C code。
五、 后话
基本上这份作业已经简化相当多东西了,首先是提供source code、debugger,这是对骇
客正常来说不会取得的东西(除非哪家厂商把自家产品firmware开源?),此外题目也经
设计,多花点时间观察、绕过各种陷阱就可以完成。
在软件上,可以使用canary保护来避免overflow,即透过将locol variable整个用struct
包起来,并在struct中最上方、最下方各摆一个canary变量,在一进function时赋同一值
予此二canary变量,需随机并且无法预判,如果overflow发生,则由于struct关系在改写
到register存放位置前一定会先改写到其中一个canary的值,故在function结束前比较两
canary值是否仍相等,如不相等则overflow发生,报错跳exception即可避免受攻击。
在嵌入式系统中,也存在硬件保护,常见的有MPU (Memory Protect Unit),可以对记忆
体区块设置rwx权限(read, write, execute),所以例如说把stack设为rw-,把机器码那
块设为r-x,当有人把对stack做code injection想要来执行时,pc就会跳到exception,
停止执行,亦或是假如哪个人有超神方法可以改写原始的machine code也会报错,不过这
就是作业第二题的内容了。相对应的攻击方式变为code reuse attack (return-oriented
attack),亦即不注入自己程式码,而是透过不断在原始machine code中return来、retu
rn去来达到自己要的目的,这个让pc跳来跳去的过程奇复杂无比,要找到能用的code片段
(称为gadget, 小工具)就是难事,还得考虑sp的位置、register的值等等才能让程式继续
正常运作而不报错。
作者: b0920075 (Void)   2020-03-15 04:04:00
找 gadget 的话 ropper 或 ROPgadget 或 pwntools 可能能帮上忙....?没碰过 arm的不太清楚,不过可以试试
作者: ando5566 (风云变色)   2020-03-15 14:30:00
离开时funtion时push 那段是不是改成pop离开function时push那段是不是应改成pop感谢分享 ..近来最像asm板文章的一篇
作者: descent (“雄辩是银,沉默是金”)   2020-03-18 18:01:00
感谢分享
作者: b0920075 (Void)   2020-03-15 12:04:00
找 gadget 的话 ropper 或 ROPgadget 或 pwntools 可能能帮上忙....?没碰过 arm的不太清楚,不过可以试试
作者: ando5566 (风云变色)   2020-03-15 22:30:00
离开时funtion时push 那段是不是改成pop离开function时push那段是不是应改成pop感谢分享 ..近来最像asm板文章的一篇
作者: descent (“雄辩是银,沉默是金”)   2020-03-19 02:01:00
感谢分享
作者: ccmite (0'w'0)   2020-04-28 09:55:00
感谢分享
作者: ccmite (0'w'0)   2020-04-28 01:55:00
感谢分享
作者: nini200 (200妮妮)   2020-05-14 00:07:00
玩破解蛮基本的 但你算厉害了 很快就上手

Links booklink

Contact Us: admin [ a t ] ucptt.com