目标文件: ciscn_2019_ne_5。
来源 :https://buuoj.cn/challenges
保护情况:保护是没有保护的
主要伪代码:
int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // [esp+0h] [ebp-100h] char src[4]; // [esp+4h] [ebp-FCh] char v5; // [esp+8h] [ebp-F8h] char s1[4]; // [esp+84h] [ebp-7Ch] char v7; // [esp+88h] [ebp-78h] int *v8; // [esp+F4h] [ebp-Ch] v8 = &argc; setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); fflush(stdout); *(_DWORD *)s1 = 48; memset(&v7, 0, 0x60u); *(_DWORD *)src = 48; memset(&v5, 0, 0x7Cu); puts("Welcome to use LFS."); printf("Please input admin password:"); __isoc99_scanf("%100s", s1); // 输入密码: administrator if ( strcmp(s1, "administrator") ) { puts("Password Error!"); exit(0); } puts("Welcome!"); while ( 1 ) { puts("Input your operation:"); puts("1.Add a log."); puts("2.Display all logs."); puts("3.Print all logs."); printf("0.Exit\n:"); __isoc99_scanf("%d", &v3); switch ( v3 ) { case 0: exit(0); // 退出 return; case 1: AddLog((int)src); // 输入 break; case 2: Display(src); // 删除输入的 break; case 3: Print(); break; case 4: GetFlag(src); // 将输入的输出 break; default: continue; } } }
int __cdecl AddLog(int a1) { printf("Please input new log info:"); return __isoc99_scanf("%128s", a1); }
int __cdecl GetFlag(char *src) { char dest[4]; // [esp+0h] [ebp-48h] char v3; // [esp+4h] [ebp-44h] *(_DWORD *)dest = 48; memset(&v3, 0, 0x3Cu); strcpy(dest, src); return printf("The flag is your log:%s\n", dest); }
GetFlag的栈结构示意
其它信息:
或者这个 "sh" 字符串也可以用其他方式找到:
调试目标 :构造GetFlag函数的栈溢出并借此开启一个shell。
先打上至少一个断点,不然待会干啥的白瞎
也可以直接对函数名下断点(如果程序没开PIE的话)
我将程序的执行断在了call进GetFlag前:
很明显,0xFFFFD3FC就是输入字符串的保存地,0x804891B是这个call的返回值。
用 "s"命令跟进这个call,直到抬栈完毕。( 想步过这个call的话用 "n"就好。)
先看看栈:
GetFlag函数中有个字符拷贝的行为,从GetFlag函数的栈顶开始写入0xFFFFD3FC之后的内容直到被 0 截断为止。
GetFlag函数的栈容量只有0x48个字节,再往后就是原 EBP 和返回值
思路:将GetFlag函数的返回值覆盖为system函数的地址 ,并在其后写入字符串 "sh"的地址 0x80482EA
接下来是利用 set 命令修改内存,但 set 命令一修改就是四个字节,有时候并不算太方便。
改原 EBP 到没什么讲究,只要四字节中没有为零的就好。
然后是覆盖返回值
这个不重要,占位子的,四字节中没有为零的就好
写入参数地址
至此,GetFlag的栈已经构造完成:
下面就是放飞自我的时候了( "go" 命令 :直接执行的下一个断点(但在这里是不可能的了)):
可以看到程序已经崩溃,并且如愿开启了shell。