XCTF game WriteUp——IDA远程调试的使用
XCTF game WriteUp——IDA远程调试的使用
准备工作
-
工具:IDA 7.0
-
首先查看该文件的类型,可以看出是一个32的PE文件,而且是在80386机器上执行的(这里要注意一下)
-
打开程序,界面如下,就像题目介绍的那样,通过做游戏,将所有灯全部点亮就可以得到flag
-
拖到IDA-32bit中查看
分析伪代码
-
在Function Name一栏中,发现符号表是被去掉的,此时可以根据刚才运行程序时显示在命令行窗口中的内容来判断
-
打开Strings窗口(View》Open Subviews》Strings,或者shift+F12),然后看到了熟悉的字符串,跟进去,X键查看交叉引用
-
跟进以后,F5出伪代码,发现要寻找的主函数
-
其中
sub_45A7B()
用来对之前找到的字符串进行处理,可以猜测,其为printf()
函数 -
第36行紧接着
n=
,可以判断其为scanf()
函数,用来处理用户输入的数字变量v1
-
再看第46-54行,如果输入为0,则执行其中的操作,联系窗口中的提示,所以这部分是重置灯笼状态,可以忽略;那42-45行就是用来点灯和关闭邻灯的(跟进去查看,也确实是这样)
-
第56行用来更新当前界面的显示,可以忽略
-
第57-67行,表示如果灯笼全都亮了,就执行
sub_457AB4()
-
第66行,跟进去发现是显示我们想要的flag的功能
-
-
有了上面的分析,就可以重命名上面的变量,思路可以更加清晰一些:本题通过做一个游戏,如果成功通关,就可以拿到flag。
int __cdecl main(int argc, const char **argv, const char **envp) { signed int i; // [esp+DCh] [ebp-20h] int choice; // [esp+F4h] [ebp-8h] printf(&unk_50B110); printf(&unk_50B158); printf(&unk_50B1A0); printf(&unk_50B1E8); printf(&unk_50B230); printf(&unk_50B278); printf(&unk_50B2C0); printf(&unk_50B308); printf(&unk_50AFD0); printf("| by 0x61 |\n"); printf("| |\n"); printf("|------------------------------------------------------|\n"); printf( "Play a game\n" "The n is the serial number of the lamp,and m is the state of the lamp\n" "If m of the Nth lamp is 1,it's on ,if not it's off\n" "At first all the lights were closed\n"); printf("Now you can input n to change its state\n"); printf( "But you should pay attention to one thing,if you change the state of the Nth lamp,the state of (N-1)th and (N+1)th w" "ill be changed too\n"); printf("When all lamps are on,flag will appear\n"); printf("Now,input n \n"); while ( 1 ) { while ( 1 ) { printf("input n,n(1-8)\n"); sub_459418(); printf("n="); scanf("%d", &choice); printf("\n"); if ( choice >= 0 && choice <= 8 ) break; printf("sorry,n error,try again\n"); } if ( choice ) { turnOnTheLight(choice - 1); } else { for ( i = 0; i < 8; ++i ) { if ( (unsigned int)i >= 9 ) j____report_rangecheckfailure(); isOn[i] = 0; } } j__system("CLS"); refreshGUI(); if ( isOn[0] == 1 && isOn[1] == 1 && isOn[2] == 1 && isOn[3] == 1 && isOn[4] == 1 && isOn[5] == 1 && isOn[6] == 1 && isOn[7] == 1 ) { showFlag(); } } }
-
下面,进入
showFlag()
函数分析,函数先是声明了110余个变量,然后进行如下操作:
-
那就直接对这里进行分析,就可以得到flag了。和同学交流,得到了两种方法,一种就是使用IDA Python脚本来复现函数过程,得到结果;另一种方法就是使用IDA调试的功能,让其强制输出flag(我还是刚知道IDA可以动态调试。。。)。下面就介绍IDA动态调试的功能。
本机调试
-
本题可以使用动态调试来执行的前提就是不需要与输入的字符串进行互动,可以顺序执行直接获取flag
-
注意:在下断点和调试的时候,最好是在汇编代码而不是伪代码中操作,因为IDA中生成伪代码不一定准确
-
这里,我们先将断点打在main()函数的某个输出处(双击相应代码行,或者F2)
-
然后,选择Local Windows debugger,先进行本地调试
-
点击左侧开始调试,但发现会报错:
Oops!internal error 1491 occured.
-
这里是由于IDA 7.0在本机调试80386的PE文件时会存在Bug,解决办法:要不升级IDA版本,要不使用远程调试。下面,开始在本机上进行远程调试。
动态调试
-
这里演示的是在本机上进行远程调试32位程序
-
在IDA的安装目录中,找到
dbgsrv
文件夹,打开win32_remote.exe
,相当于开启一个守护进程,等待“远程”IDA连接
-
然后,IDA重新选择Remote Windows debugger,开始调试
这里需要注意的是,被调试的程序,一定要放在打开守护进程的机器上。我一开始是将程序放在了实体机上,虚拟机中的IDA通过共享文件夹打开的程序,这样在调试的时候还是会报Oops!internal error 1491 occured.
的错。
-
在输入参数的时候,如果远程调试的机器(这里恰好是本机)就是一个实体物理机,那Hostname处就写127.0.0.1。如果远程调试的机器是一个虚拟机,比如我的就是一台虚拟机,那Hostname就是虚拟机的ip地址,这个在守护进程中有显示,我的ip地址是10.211.55.4。填好后,点击OK,开始调试(记得再次设置断点)
-
断点最好设置在输入之前,比如这样
-
然后,进入刚才显示flag的函数中,找到第一个函数,在声明变量的那里,右键==》set IP,IP表示指令指针寄存器,该操作强行将IP指向了这里,下次将从这里开始执行
-
这时,查看右侧寄存器,发现EIP确实指向了这里
-
然后F4(运行到光标处,光标往输出flag语句的后面放一放),成功找到使其强制输出flag,其值为
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
(图片里点多了,而且后面没设断点)