似乎每次比赛都会有一道游戏题,这次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也不太会弄,然后就放弃了……
最后比赛结束后才想起来,可以把命令行的窗口拉大,这样游戏范围就变大了,这样就算是每次输入后回车也毫无压力玩过了,瞬间感觉自己当时好脑残……
按说这道题是可以完全靠逆向做出来,不用玩游戏的,不过自己水平不济,连程序整体都没大看明白,就更别谈那个复杂的要死的给答案的部分了。