第一次打国外的比赛 题型也都很新颖 记录一下做出来的题目
two-sum 签到题 考的是整形溢出 直接给了源码
#include <stdio.h> #include <stdlib.h> static int addIntOvf (int result, int a, int b) { result = a + b; if (a > 0 && b > 0 && result < 0 ) return -1 ; if (a < 0 && b < 0 && result > 0 ) return -1 ; return 0 ; } int main () { int num1, num2, sum; FILE *flag; char c; printf ("n1 > n1 + n2 OR n2 > n1 + n2 \n" ); fflush(stdout ); printf ("What two positive numbers can make this possible: \n" ); fflush(stdout ); if (scanf ("%d" , &num1) && scanf ("%d" , &num2)) { printf ("You entered %d and %d\n" , num1, num2); fflush(stdout ); sum = num1 + num2; if (addIntOvf(sum, num1, num2) == 0 ) { printf ("No overflow\n" ); fflush(stdout ); exit (0 ); } else if (addIntOvf(sum, num1, num2) == -1 ) { printf ("You have an integer overflow\n" ); fflush(stdout ); } if (num1 > 0 || num2 > 0 ) { flag = fopen("flag.txt" ,"r" ); if (flag == NULL ){ printf ("flag not found: please run this on the server\n" ); fflush(stdout ); exit (0 ); } char buf[60 ]; fgets(buf, 59 , flag); printf ("YOUR FLAG IS: %s\n" , buf); fflush(stdout ); exit (0 ); } } return 0 ; }
我们直接输入sum1 sum2的值 只要满足两个判断式中的一个就行
if (a > 0 && b > 0 && result < 0 )if (a < 0 && b < 0 && result > 0 )
这里我选择打第一个判断式 只要a+b超过了无符号int型范围就行
二者值都为2147483647即可
babygame01 脑洞比较大的一道题 不过还在逻辑之内 好好分析一番
ida看一下伪代码
int __cdecl main (int argc, const char **argv, const char **envp) { char v4; int v5[2 ]; char v6; char v7[2700 ]; unsigned int v8; int *v9; v9 = &argc; v8 = __readgsdword(0x14 u); init_player(v5); init_map(v7, v5); print_map(v7, v5); signal(2 , (__sighandler_t )sigint_handler); do { do { v4 = getchar(); move_player(v5, v4, v7); print_map(v7, v5); } while ( v5[0 ] != 29 ); } while ( v5[1 ] != 89 ); puts ("You win!" ); if ( v6 ) { puts ("flage" ); win(); fflush(stdout ); } return 0 ; }
写博客重新复现的时候 决定把每个函数都过一遍 顺便加强我的代码审计能力
int __cdecl init_player (int a1) { int result; *a1 = 4 ; *(a1 + 4 ) = 4 ; result = a1; *(a1 + 8 ) = 0 ; return result; }
a1的地址等同于v5 v5这个数组就定义了两个元素 分别用来存放此时我们处于map上的位置
Elf32_Dyn **__cdecl init_map (int a1, _DWORD *a2) { Elf32_Dyn **result; int i; int j; result = &GLOBAL_OFFSET_TABLE_; for ( i = 0 ; i <= 29 ; ++i ) { for ( j = 0 ; j <= 89 ; ++j ) { if ( i == 29 && j == 89 ) { *(a1 + 2699 ) = 'X' ; } else if ( i == *a2 && j == a2[1 ] ) { *(90 * i + a1 + j) = player_tile; } else { *(90 * i + a1 + j) = '.' ; } } } return result; }
a1相当于v7 v7定义了非常大一串 用来开辟一块内存给地图的存放
利用for循环对map进行了初始化 除了两个特殊的地方 用户的初始位置和终点 其他都为’.’
int __cdecl print_map (int a1, int a2) { int i; int j; clear_screen(); find_player_pos(a1); find_end_tile_pos(a1); print_flag_status(a2); for ( i = 0 ; i <= 29 ; ++i ) { for ( j = 0 ; j <= 89 ; ++j ) putchar (*(90 * i + a1 + j)); putchar (10 ); } return fflush(stdout ); }
for循环遍历打印出map
接下来用getchar赋值了v4 传入下面这个函数充当a2
a1用来存储用户位置 a3则是存放map
_BYTE *__cdecl move_player (_DWORD *a1, char a2, int a3) { _BYTE *result; if ( a2 == 'l' ) player_tile = getchar(); if ( a2 == 'p' ) solve_round(a3, a1); *(a1[1 ] + a3 + 90 * *a1) = '.' ; switch ( a2 ) { case 'w' : --*a1; break ; case 's' : ++*a1; break ; case 'a' : --a1[1 ]; break ; case 'd' : ++a1[1 ]; break ; } result = (a1[1 ] + a3 + 90 * *a1); *result = player_tile; return result; }
大体是利用’wsad’控制用户位置 不过存放两个特殊的指令
‘l’可以把player_tile替换成其后面的字符
‘p’则可以直接把用户位置移动到终点
最后利用result存储用户位置移动后的地址 在对应地址存入player_tile
if ( v6 ) { puts ("flage" ); win(); fflush(stdout ); }
最后回到main函数 如果到达了终点 则对v6进行判断 如果为真 就输出flag
分析完了程序 来捋一捋思路 目的无非就是要修改到v6的值 不过程序不存在任意写也无法栈溢出覆盖到v6
不过还是存在着一个细微的漏洞 可以做到数组溢出 如果我们使得v5中的元素值为负呢?
也就是达到了map边界后仍然向边界外面移动 这个时候就会往非法内存处写入值
这对于v6的值会有什么影响呢 我们知道 在栈结构不被破坏的情况下 固定索引到的栈地址存放的一定是v6
那么我们只需要破坏栈结构 使其索引到的是其他值就行了
这个时候我们来尝试一下 进行一下数组溢出 将位置移动到左上角后 再输入a
可以看到此时栈的结构就成功被破坏了 不过此时我们仍然无法通过if
byte ptr的作用在于指明访问的内存单元是一个字节单元 也就是只读入一个字节的数据
此时还是0 那么只需要利用同样的手法多试几次 最后成功获得flag
VNE 从来没做过这样的题目 说实话还是挺好玩的 感觉和awd有点像
一开始没有给我们附件 启动靶机后 提示让我们进行ssh连接
这里我使用的软件是finalshell
连上了以后 照着提示下载了bin文件 是题目的附件
保护全开 ida看一看
int __cdecl main (int argc, const char **argv, const char **envp) { __int64 v3; int v4; __int64 v5; __int64 v6; __int64 v7; const char *v8; __int64 v9; __int64 v10; char v12; unsigned int v13; char *v14; char v15[32 ]; char v16[40 ]; unsigned __int64 v17; v17 = __readfsqword(0x28 u); v14 = getenv ("SECRET_DIR" ); if ( v14 ) { v5 = std::operator <<<std::char_traits<char >>(&std::cout, "Listing the content of " ); v6 = std::operator <<<std::char_traits<char >>(v5, v14); v7 = std::operator <<<std::char_traits<char >>(v6, " as root: " ); std::ostream::operator <<(v7, &std::endl<char ,std::char_traits<char >>); std::allocator<char >::allocator (&v12); std::__cxx11::basic_string<char ,std::char_traits<char >,std::allocator<char >>::basic_string (v16, v14, &v12); std::operator +<char >(v15, "ls " , v16); std::__cxx11::basic_string<char ,std::char_traits<char >,std::allocator<char >>::~basic_string (v16); std::allocator<char >::~allocator (&v12); setgid (0 ); setuid (0 ); v8 = (const char *)std::__cxx11::basic_string<char ,std::char_traits<char >,std::allocator<char >>::c_str (v15); v13 = system (v8); if ( v13 ) { v9 = std::operator <<<std::char_traits<char >>(&std::cerr, "Error: system() call returned non-zero value: " ); v10 = std::ostream::operator <<(v9, v13); std::ostream::operator <<(v10, &std::endl<char ,std::char_traits<char >>); v4 = 1 ; } else { v4 = 0 ; } std::__cxx11::basic_string<char ,std::char_traits<char >,std::allocator<char >>::~basic_string (v15); } else { v3 = std::operator <<<std::char_traits<char >>(&std::cerr, "Error: SECRET_DIR environment variable is not set" ); std::ostream::operator <<(v3, &std::endl<char ,std::char_traits<char >>); v4 = 1 ; } return v4; }
用c++编写的程序 结合直接运行大概能看懂主要流程 就是根据环境变量SECRET_DIR的值
执行ls SECRET_DIR
加上题目给的提示 所以这里把SECRET_DIR值设置为/root/ 看看root目录下有什么东西
我们的目标就是flag.txt了 当然由于权限问题 我们肯定不能直接cat
这里学习了幽林师傅的思路 我们把/bin目录里面的cat文件拷贝下来 上传到/home/ctf-player 目录中 顺便更改一下PATH 顺便更改cat文件名为ls
这样在调用ls的时候 相当于调用的就是cat了
然后再把SECRET_DIR更改为/root/flag.txt 就可以获取flag了
不过不知道什么问题 使用finalshell执行最后一步的时候 会报如下错 所以我更换了windterm才解决问题