似乎每次比赛都会有一道游戏题,这次SCTF的游戏题就是这道了,话说着实没想明白为什么这题算是Misc……
题目描述就一句话:
简单的贪吃蛇,吃到30分它就告诉你flag!但是要怎么控制它呢?
首先将程序download下来,是一个后缀名为exe的程序,果断就给扔XP虚拟机里了,一运行就看到一个控制台窗口上一个光标跳来跳去,完全看不懂,说好的贪吃蛇呢……
然后往IDA里面扔的时候,却被自动识别为类型为ELF,当时就惊呆了……
然后放Unbuntu里跑,总算是正常看到了贪吃蛇,不过果不其然不知道怎么能够控制。
然后就只能规规矩矩看程序了,很明显程序是加了个UPX的壳(PEiD查的),于是马上aptget了一个upx,然后upx -d搞定。
看程序的时候,从start一路跟下去,会发现程序进到了如下一段代码中:
.text:08048C40 .text:08048C40 ; Segment type: Pure code .text:08048C40 ; Segment permissions: Read/Execute .text:08048C40 _text segment para public 'CODE' use32 .text:08048C40 assume cs:_text .text:08048C40 ;org 8048C40h .text:08048C40 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing .text:08048C40 .text:08048C40 .text:08048C40 ; Attributes: noreturn bp-based frame .text:08048C40 .text:08048C40 sub_8048C40 proc near .text:08048C40 lea ecx, [esp+4] .text:08048C44 and esp, 0FFFFFFF0h .text:08048C47 push dword ptr [ecx-4] .text:08048C4A push ebp .text:08048C4B mov ebp, esp .text:08048C4D push ecx .text:08048C4E sub esp, 4 .text:08048C51 call _initscr .text:08048C56 call sub_8048EA0 .text:08048C5B sub esp, 8 .text:08048C5E push offset handler ; handler .text:08048C63 push 0Eh ; sig .text:08048C65 call _signal .text:08048C6A pop eax .text:08048C6B pop edx .text:08048C6C push offset sub_8048E10 ; handler .text:08048C71 push 38h ; sig .text:08048C73 call _signal .text:08048C78 pop ecx .text:08048C79 pop eax .text:08048C7A push offset sub_8048E70 ; handler .text:08048C7F push 32h ; sig .text:08048C81 call _signal .text:08048C86 pop eax .text:08048C87 pop edx .text:08048C88 push offset nullsub_1 ; handler .text:08048C8D push 5 ; sig .text:08048C8F call _signal .text:08048C94 pop ecx .text:08048C95 pop eax .text:08048C96 push offset sub_8048E10 ; handler .text:08048C9B push 0Ah ; sig .text:08048C9D call _signal .text:08048CA2 pop eax .text:08048CA3 pop edx .text:08048CA4 push offset sub_8048E70 ; handler .text:08048CA9 push 0Ch ; sig .text:08048CAB call _signal .text:08048CB0 pop ecx .text:08048CB1 pop eax .text:08048CB2 push offset sub_8048DE0 ; handler .text:08048CB7 push 34h ; sig .text:08048CB9 call _signal .text:08048CBE pop eax .text:08048CBF pop edx .text:08048CC0 push offset sub_8048E40 ; handler .text:08048CC5 push 36h ; sig .text:08048CC7 call _signal .text:08048CCC add esp, 10h .text:08048CCF call sub_80492C0 .text:08048CCF sub_8048C40 endp .text:08048CCF
可以看到基本都是在发信号来完成整个程序,那么程序的关键肯定就在信号处理函数上,然后再看32h,34h,36h,38h这些数字很奇怪,正好就是2、4、6、8的ASCII码,这让我们不禁想到了程序运行时屏幕上的那句话:
UP----'8' DOWN----'2' LEFT----'4' RIGHT----'6'
然后我们尝试手动发下信号,很容易便会发现,我们的猜想是正确的,这4个信号正好对应着4个方向。
然后接下来的事就可以很简单了,写个程序,将方向键换成发这4个信号,然后玩游戏就好,玩到30之后,就会获得flag的base64编码的值,解码即可。
最后玩游戏这步是队友做的,程序如下:
#include <stdio.h> #include <termios.h> #include <term.h> #include <curses.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <signal.h> static struct termios initial_settings, new_settings; static int peek_character = -1; void init_keyboard(); void close_keyboard(); int kbhit(); int readch(); int main( int argc, char *argv[]) { int ch = 0; init_keyboard(); pid_t pid; pid = atoi(argv[1]); //先运行snake,然后获得进程pid,作为参数传进来 //printf(pid); while(ch != 'q') { //w,a,s,d经典上下左右按键 if(kbhit()) { ch = readch(); switch (ch) { case 'w': kill(pid, 56); break; case 'a': kill(pid, 52); break; case 's': kill(pid, 50); break; case 'd': kill(pid, 54); break; } } } close_keyboard(); exit(0); } void init_keyboard() { tcgetattr(0,&initial_settings); new_settings = initial_settings; new_settings.c_lflag &= ~ICANON; new_settings.c_lflag &= ~ECHO; new_settings.c_lflag &= ~ISIG; new_settings.c_cc[VMIN] = 1; new_settings.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &new_settings); } void close_keyboard() { tcsetattr(0, TCSANOW, &initial_settings); } int kbhit() { char ch; int nread; if(peek_character != -1) return 1; new_settings.c_cc[VMIN]=0; tcsetattr(0, TCSANOW, &new_settings); nread = read(0,&ch,1); new_settings.c_cc[VMIN]=1; tcsetattr(0, TCSANOW, &new_settings); if(nread == 1) { peek_character = ch; return 1; } return 0; } int readch() { char ch; if(peek_character != -1) { ch = peek_character; peek_character = -1; return ch; } read(0,&ch,1); return ch; }
我最开始是为了方便,想写个shell的脚本来做的,结果在网上各种搜不到,搜到的唯一一个还用不起来,我也是醉了……最后实现的是一个需要每次输入之后回车的,然后就感觉蛇跑太快了,玩不过去,于是就先放着给队友了……
其实当时还想过打补丁、改内存神马的方式来弄的,不过在Linux也不太会弄,然后就放弃了……
最后比赛结束后才想起来,可以把命令行的窗口拉大,这样游戏范围就变大了,这样就算是每次输入后回车也毫无压力玩过了,瞬间感觉自己当时好脑残……
按说这道题是可以完全靠逆向做出来,不用玩游戏的,不过自己水平不济,连程序整体都没大看明白,就更别谈那个复杂的要死的给答案的部分了。