第一步 checksec,并检查该题的保护机制,32位
- Arch: i386-32-little
- 这表示程序的架构是32位的i386(即x86),并且使用小端序(little-endian)存储方式。这意味着程序是为32位系统设计的。
- RELRO: Partial RELRO
- RELRO(Relocation Read-Only)是一种安全特性,用于减少对全局偏移表(GOT)的攻击。Partial RELRO表示只对部分GOT表进行了保护。如果采用完全RELRO,则GOT表在程序启动时就变成只读,从而提高安全性。Partial RELRO的保护级别低于完全RELRO。
- Stack: No canary found
- 栈保护(Stack Protection)通常通过在栈中插入一个随机值(称为“canary”)来实现。如果发生栈溢出,canary值会被修改,程序可以检测到这种修改并终止执行,从而防止攻击。这里显示“No canary found”,意味着程序没有使用栈保护,这使得它更容易受到栈溢出攻击。
- NX: NX enabled
- NX(No-eXecute)是一种安全特性,用于防止在栈或堆上的数据被执行。这里显示“NX enabled”,意味着程序启用了NX保护,这可以有效地防止某些类型的缓冲区溢出攻击。
- PIE: No PIE (0x8048000)
- PIE(Position Independent Executable)是一种安全特性,使得程序的代码可以在内存中的任意位置加载,每次加载的位置都可能不同。这样可以增加攻击者预测内存地址的难度。这里显示“No PIE”,意味着程序没有使用PIE,其代码在内存中的位置是固定的(此处为0x8048000),这使得它更容易受到攻击。
第二步 进入主函数,发现只有一个vuln函数,跟进
跟进后如下
下面详细分析一下:
- printf("Tell me something about yourself: ");
这行代码向用户输出提示信息,要求用户输入一些内容。 - fgets(s, 32, edata);
fgets函数从edata流中读取最多31个字符到s中(加上一个终止符\0,总共32个字符)。这通常用于从标准输入读取用户输入。 - std::string::operator=(&input, s);
这一行代码将字符串s赋值给一个std::string对象input。operator=是std::string类的赋值运算符,将C风格的字符串char*转化为C++风格的std::string。 - std::allocator<char>::allocator(&v5);
这行代码创建了一个std::allocator<char>对象v5,它是C++标准库中用于管理内存分配的工具。 - std::string::string(v4, "you", &v5);
这行代码使用分配器v5创建了一个包含字符串"you"的std::string对象v4。 - std::allocator<char>::allocator(v7);
这行代码创建了另一个std::allocator<char>对象v7。 - std::string::string(v6, "I", v7);
这行代码使用分配器v7创建了一个包含字符串"I"的std::string对象v6。 - replace((std::string *)v3);
这行代码表示一个字符串替换操作,可能是在v3指向的字符串中将某个子串替换为另一个子串。 - std::string::operator=(&input, v3, v6, v4);
这行代码表示可能是在字符串input中,将子字符串v4替换为v6。如果v4是"you",v6是"I",则这可能是将字符串中的"you"替换为"I"。(我们输入的其它字符只占一个字节,而我们输入的“I”经过replace函数后会变成“you”也就是放大了三倍,我们的输入只有31个字节,而我们需要64位的填充数据) - std::string::~string(v3);
销毁v3指向的std::string对象,释放其占用的资源。 - std::string::~string(v6);
销毁v6指向的std::string对象,释放其占用的资源。 - std::allocator<char>::~allocator(v7);
销毁v7对象,释放内存分配器所管理的资源。 - std::string::~string(v4);
销毁v4指向的std::string对象,释放其占用的资源。 - std::allocator<char>::~allocator(&v5);
销毁v5对象,释放内存分配器所管理的资源。 - v0 = (const char *)std::string::c_str((std::string *)&input);
将input对象的内容转换为C风格字符串指针v0。 - strcpy(s, v0);
将v0指向的字符串复制到s中。 - return printf("So, %s\n", s);
最后输出字符串s的内容,并返回printf的返回值。
第三步 我们进入一下与flag有关的get_flag函数
该函数的地址是0x8048F0D
计算填充数据,跟进数组s
两个s之间的距离为0x3c即60,因为前面的替换操作 I(一个字节)=you(三个字节)
而fgets限制为32个,60个字节刚好可以用20个I填满, db 4 dup(?) 这里还有4个字节内存要被占!!所以一共为20+4个(上面提到的64个填充数据=20*3+4)
第四步 编写脚本
第五步 运行