CTF逆向题——IgniteMe
点击蓝字
关注我们
01
OD篇
1.1使用工具:
OD(OllyDbg)
功能:用于动态调试
1.2该调试过程中常用快捷键
F8 单步步过
F7 单步步入(查看函数)
F9 直接运行程序,遇到断点则暂停
1.3步骤:
首先,运行一遍IgniteMe.exe软件,记录下关键语句Give me your flag。
在OD里面载入待调试的程序IgniteMe.exe 。
单击鼠标右键,选择中文引擎搜索——智能搜索,进入智能搜索界面后,单击鼠标右键选择Find,输入“Give me your flag”,单击确认按钮,双击出现该关键语句处,程序自动跳转到反汇编窗口的对应行,在此处我们设置断点。
在弹出的DOS界面中,随便输入一个flag,回车。继续查看代码。
继续单步步过浏览代码,遇到函数不暂停,但是要注意步过函数之后,寄存器里面值的改变(返回值的改变),从而猜测到函数的作用(如步过函数IgniteMe. 0041F2D0,eax里的内容改变,经过输入字符串对比,可知道该函数的作用是将输入字符串的长度返回到eax里)。函数后面常常跟着cmp,用于比较返回值和给定值的关系。通过观察我们可以发现,上面所述函数下方的cmp的具体作用是用来比较字符串的长度是否为小于4或大于等于30。是则输出Sorry,keep trying! ,flag输入错误。其实在此我们就应该知道输入字符串长度应为29。
(相关代码如下:)
00401050 |. E8 7BE20100 call IgniteMe.0041F2D0 ; 字符串长度
00401055 |. 83C4 04 add esp,0x4
00401058 |. 83F8 1E cmp eax,0x1E
0040105B 73 11 jnb short IgniteMe.0040106E ; 长度大于等于30跳转
0040105D |. 8D55 80 lea edx,[local.32] ; 为输入的字符串
00401060 |. 52 push edx
00401061 |. E8 6AE20100 call IgniteMe.0041F2D0
00401066 |. 83C4 04 add esp,0x4
00401069 |. 83F8 04 cmp eax,0x4
0040106C 77 25 ja short IgniteMe.00401093 ;
长度大于4则跳转
在第二个Sorry,keep trying!之前发现寄存器里面出现EIS{,容易知道这是大赛flag的输入格式要求,这个地方就是来判断输入字符串的是否以EIS{开头的。(相关代码如下:)
004010CC |. E8 FFE10100 |call IgniteMe.0041F2D0 ; “EIS{”
长度存入eax ,用来比较长度
004010D1 |. 83C4 04 |add esp,0x4
004010D4 |. 3985 74FFFFFF |cmp [local.35],eax
004010DA |. 73 44 |jnb short IgniteMe.00401120
004010DC |. 8B8D 74FFFFFF |mov ecx,[local.35]
004010E2 |. 0FBE540D 80 |movsx edx,byte ptr ss:[ebp+ecx-0x80] ;
输入的字符串逐字节移入
004010E7 |. 8B85 74FFFFFF |mov eax,[local.35]
004010ED |. 0FBE8C05 78FF>|movsx ecx,byte ptr ss:[ebp+eax-0x88] ; EIS{
004010F5 |. 3BD1 |cmp edx,ecx
字头与EIS{相比较
004010F7 74 25 je short IgniteMe.0040111E
相同则跳转
当输入满足以上两个条件之后,继续单步步过,发现此时出现一个比较,将edx(存的是输入字符串的最后一个字符)中的内容与
0X7D进行比较,查看ASCII码表,发现0X7D对应的ASCII码为“}”,可知为检测当字符串长度为29时,最后一位是否为}。是则跳过Sorry,keep
trying! 不是则输出Sorry,keep trying!结束程序。(相关代码如下:)
00401120 |> \0FBE55 9C movsx edx,byte ptr ss:[ebp-0x64] ; 0X80-0X64=0X1c
00401124 |. 83FA 7D cmp edx,0x7D
比较最后一位是否为“}”
00401127 74 22 je short IgniteMe.0040114B ;
相等则跳转
接下来继续步过,这一次我们通过单步步过的方式很难判断出进行了什么变换和判断,于是我们选择单步步入到IgniteMe.004011C0这个函数中去。
此时进入了一个循环,可看出该循环的作用是把输入的字符串去掉EIS{}格式之后存入到ss堆栈中。
字符串完全存入到ss中之后,又进入一个循环,通过寄存器内容的改变可以简单判断这个循环进行了一个大小写交换的修改。修改后将值存入edx中。但是该循环并不只是仅仅做了这个简单的改变。
观察 push edx之后的语句,着重观察和edx有关的语句。
通过单步步过,发现mov edx,[local.45] 和xor edx,ecx语句,下次调试时,认真观察[local.45]和ecx的值,可以发现如下关系:
以上步骤很关键,总结起来即为对字符串EIS{ }括号中的内容逐个进行大小写交换,对[arg.1]的内容逐个赋给eax,进行一个异或并相加的操作,结果与关键字符串进行异或(关键字符串为ds:[eax+0x4420B0]中的内容,可通过数据窗口跟随查看其中的内容)。最终的结果存入ss堆栈中。(相关部分代码)
0040134C |. |E8 6F000000 |call IgniteMe.004013C0 ;
关键函数,eax异或相加
00401351 |. |83C4 04 |add esp,0x4 ;
00401354 |. |8985 4CFFFFFF |mov [local.45],eax
0040135A |. |8B85 7CFFFFFF |mov eax,[local.33]
00401360 |. |0FBE88 B02044>|movsx ecx,byte ptr ds:[eax+0x4420B0] ; 关键字符串
00401367 |. |8B95 4CFFFFFF |mov edx,[local.45]
0040136D |. |33D1 |xor edx,ecx
0040136F |. |8B85 7CFFFFFF |mov eax,[local.33]
00401375 |. |889405 54FFFF>|mov byte ptr ss:[ebp+eax-0xAC],dl ;
存入修改之后的
0040137C |. |C785 50FFFFFF>|mov [local.44],0x0
00401386 |.^\E9 0BFFFFFF \jmp IgniteMe.00401296
0040138B |> \8D8D 54FFFFFF lea ecx,[local.43]
继续单步步过,遇到函数IgniteMe.0041F390,单步步入可发现改变后的字符串与IgniteMe.0x43B188(内容为GONDPHyGjPEKruv{{pj]X@rF)进行了比较,若相同则输入字符串正确,返回Congratulation!,否则错误,返回Sorry,keep trying。
比较函数为:00401397 |. E8 F4DF0100 call IgniteMe.0041F390
根据这个思路,我们可以进行编程。
最后的结果是EIS{wadx_tdgk_aihc_ihkn_pjlm}
2
IDA篇
我们如果用IDA对IgniteMe.exe进行分析,选择Generate pseudocode 将伪代码调出,会发现这比在OD中进行分析容易得多。但是,值得一提的是,在OD中进行调试,更有益于我们对汇编程序及过程的进一步了解。
2.1以下为在IDA中进行分析的过程:
选择Generate pseudocode调出伪代码后,我们首先对主函数进行分析,它的思路非常清晰,就是通过四个主要判断对输入的flag进行验证,如果判断均正确,则flag正确。
这四个判断分别是:
(1)if ( strlen(v8) < 0x1E && strlen(v8) > 4 ) //v8为输入字符串
判断输入字符串长度是否在大于4且小于30
(2)
strcpy(v7, "EIS{");
for ( i = 0; ; ++i )
{
v4 = strlen(v7);
if ( i >= v4 )
break;
if ( v8[i] != v7[i] )
{
sub_402B30((int)&unk_446360, "Sorry, keep trying! ");
sub_4013F0(sub_403670);
return 0;
}
这里运用了一个循环函数,即将输入字符串前四位与“EIS{”进行一个比较,全部相等则通过验证,不相同则flag错误。
(3) if ( v9 == 125 ) //v9为输入字符串最后一位
判断输入字符串最后一位是否为“}”
(4)
if ( sub_4011C0(v8) )
{
sub_402B30((int)&unk_446360, "Congratulations! ");
sub_4013F0(sub_403670);
result = 0;
}
else
{
sub_402B30((int)&unk_446360, "Sorry, keep trying! ");
sub_4013F0(sub_403670);
result = 0;
}
我们点击sub_4011C0这个函数进行查看,看看它对v8进行了什么样的变换。
由于该函数的返回值是result,我们先来看看result的值是怎么得来的。我们发现这样一个语句:
意思就是v7和GONDPHyGjPEKruv{{pj]X@rF相等时result=1,否则为0。
此时向上观察这段代码:
注:a1为该函数的传入参数,在此处即为v8(输入字符串的内容)。
程序释义:
从前面的判断可知,v8的形式应为“EIS{XX…X}”。1处的循环意在于将括号中的内容传入v11中。
对v11中的字母进行大小写交换。
函数sub_4013C0的内容为:
int __cdecl sub_4013C0(int a1)
{
return (a1 ^ 0x55) + 72;
}
可知3处的代码意在于将v11中的字符数组内容逐个与0x55异或后加上72再与j(0~24)进行异或。最终得到的内容就是v7的全部内容。
结合上面的分析,当v7与GONDPHyGjPEKruv{{pj]X@rF相等时,输入的flag即为正确的flag。
通过这个思路我们就进行编程了。
总结:前三个flag判断点比较简单,我们通过主函数就能确认,该程序的重点就在于最后一次判断。我们只要能够对sub_4011C0()这个函数进行正确的分析,就能很容易地进行编程并得到最后的flag。
该程序的大体思路可总结为以下:
附上c语言代码。
c逆向代码:
//逆向:关键字符串与GONDPHyGjPEKruv{{pj]X@rF异或逐个减去0X48再与0x55异或,最后将大小写交换
#include<stdio.h>
#include<string.h>
int main(){
char a[]={0x0D, 0x13, 0x17, 0x11, 0x2, 0x1, 0x20, 0x1D, 0x0C, 0x2, 0x19, 0x2F, 0x17, 0x2B, 0x24, 0x1F, 0x1E, 0x16, 0x9, 0x0F, 0x15, 0x27, 0x13, 0x26};
//关键数组
char b[]="GONDPHyGjPEKruv{{pj]X@rF"; //关键字符串
int i;
for(i=0;i<24;i++)
{
a[i]^=b[i];
a[i]=(a[i]-0x48)^0x55;
if(0x41<=a[i] && a[i]<=0x5A)
a[i]+=0x20;
else if(0x61<=a[i] && a[i]<=0X7A)
a[i]-=0x20;
printf("%c",a[i]);
}
printf("\n");
return 0;
}
C代码正向:
//正向:每个元素大小写交换,结果分别与0x55异或再加上0x48,再与关键字符串异或,结果与GONDPHyGjPEKruv{{pj]X@rF进行比较,相等则flag正确。
#include <stdio.h>
#include <string.h>
int main(){
char a[]={0x0D, 0x13, 0x17, 0x11, 0x2, 0x1, 0x20, 0x1D, 0x0C, 0x2, 0x19, 0x2F, 0x17, 0x2B, 0x24, 0x1F, 0x1E, 0x16, 0x9, 0x0F, 0x15, 0x27, 0x13, 0x26, 0x0A, 0x2F, 0x1E, 0x1A, 0x2D, 0x0C, 0x22,0x4};
char b[]="GONDPHyGjPEKruv{{pj]X@rF";
int i;
char j;
for(i=0;i<24;i++)
{
for(j=0X0;j<0X7f;j++){
if(b[i]==(a[i]^((j^0x55)+0x48)))
{
if (0x40<j&&j<0x5B)
j+=0x20;
else if(0x60<j&&j<0x5B)
j-=20;
printf("%c",j);
}
}
}
printf("\n");
return 0;
}
别忘了投稿哟!!!
合天公众号开启原创投稿啦!!!
大家有好的技术原创文章。
欢迎投稿至邮箱:edu@heetian.com;
合天会根据文章的时效、新颖、文笔、实用等多方面评判给予100元-500元不等的稿费哟。
有才能的你快来投稿吧!
合天智汇
网址 : www.heetian.com
电话:4006-123-731
长按图片,据说只有颜值高的人才能识别哦→