Reversing.kr Writeup(11-15)

Easy_ELF

ida 打开

gdb 跟一下,知道0x804a20是输入(猜也能猜到)
最后答案L1NUX

WindowKernel

第一次接触这种类型的题目。放在 32 位下来做题。
打开发现报了 error,想了想试试管理员权限打开,可以正常运行了。

点 enable 之后应该是调用了 sys 的函数记录键盘输入,然后进行分析。
查了下程序没有壳,直接放到 ida 里面。这种 gui 的程序,直接看DialogFunc函数,过一下发现关键在sub_401110函数里面。

可以看出进行输入判断的关键函数是sub_401280,这个函数跟进去看果然看到了DeviceIoControl函数。正常情况下,应用程序和驱动程序通信的基本过程是,应用程序应用程序使用CreateFile函数打开设备,然后使用用DeviceIoControl与驱动程序进行通信,包括读写两种操作,结束之后使用CloseHandle函数来关闭设备。

接下来看看 sys 文件,拖进 ida 看到函数不多,可以直接读,
然后从DirverEntry挨个过一遍的时候看到了比较有意思的函数

这里等于 0x1000 的时候和等于 0x2000 的时候赋值,和之前的sub_401280函数一对比,很明显等于 0x1000 的时候开始,然后等于 0x2000 的时候进行判断。所以等于 0x2000 的时候这个*(_DWORD *)&v3->Type = dword_13024;赋值就很引人注目了。看看dword_13024的引用,跟到了sub_110D0,不大看得懂这个函数,没关系继续网上跟.
最后确定是sub_11266->sub_111DC->sub_11156->sub_110D0
而我们看看sub_11266函数,看到了关键的READ_PORT_UCHAR函数,READ_PORT_UCHAR这个 API 是从端口中获取字节信息,0x60是 PS2 键盘的数据端口,必须在内核模式下运行。查阅了资料,了解到大多数 PC 键盘控制器在地址0x60和0x64上是可寻址的,键盘和鼠标是共享 0X60 端口的,所以在使用之前,系统一般会先读取0X64端口,判断是鼠标还是键盘。

回到题目,这里也就清楚了,传入sub_111DCv4就是我们的输入,然后就是连等判断,地址dword_13034是一个计数器,计数器大于一定值之后又进入sub_11156sub_110D0
最后看下来,我们的输入值如下:

0xa5, 0x92, 0x95, 0xb0,
0xb2^0x12, 0x85^0x12, 0xa3^0x12, 0x86^0x12
0xb4^0x12^0x5, 0x8f^0x12^0x5, 0x8f^0x12^0x5, 0xb2^0x12^0x5

总共 12 个字母,一看不像是可显字符,不过这里从端口读下来的数据可不直接就是可显字符,是键盘扫描码,还得进行一下转换,参照链接http://www.voidcn.com/article/p-syxmbgac-sm.html
转换之后得到 flag:keybdinthook

AutoHotkey1

拿到程序之后,发现加了壳,UPX,懒得用工具,手动简单跟一下,很快就发现入口,

同样用 lordpe 转储一下,然后 importrec 回复 IAT。然后运行,发现报错

程序崩溃,但是这不是系统的错误,程序正常运行然后的弹窗,猜测就是程序进行了自校验,然后脱壳之后无法通过自校验,先放进 ida 里面,然后手动跟一下,迅速定位了校验函数sub_4508C7
结合 IDA 边调边看,主要分为两部分

上述为第一部分,主要是将文件除去倒数四位进行一堆复杂的运算然后和最后四位对比。


上述为第二部分,主要先验证前 16 字节数据为某一固定值,然后第 17 等于 3,18-22 位与 0xFAC1 异或用作下一次读取的长度,然后读出来进入sub_450ABA函数进行一大段复杂的运算,然后计算结果似乎只是存入了某段内存,这让人很在意,跟了一下发现结果如下:

很明显的 md5 值,根据 readme 这应该就是其中的一部分了。
解密的结果是isolated
然后后续开始复杂了起来,看得我一头包,然后干脆直接开始找GetWindowTextA,但是发现有一堆调用,其中看到一个调用存在DialogFunc中,然后确定应该是需要的,地址为00425FB7

下断之后成功截获输入:

接下来应该就是某个地方对其进行了处理,现在 ida 中简单跟了一下,跟的我一头雾水,接下来的几个函数逻辑都比较复杂,看得我一头包,索性不跟了,直接 OD 中对保存我们输入的地方下了个硬件访问断点。然后F9之后程序断在了如下所示的一个cmp指令,这里将输入和一段疑似 md5 的值进行逐位对比,这一段多半就是答案了。

54593f6b9413fc4ff2b4dec2da337806解密之后的值是pawn
在和前一部分组合一下获得 flag:isolated pawn

CSHOP

查了下壳发现是.NET 程序,用.net reflector打开看看,主要是调用这个框架

看看InitializeComponent函数,新建了 1 个按钮和 10 个 label,其中按钮的大小被设置为 0 了,然后按钮一旦被按下就会触发click函数,该函数就是对标签进行赋值,猜测就是 flag,但是需要知道标签的排列顺序,

这个时候我们可以回到InitializeComponent函数,看到每个标签都有一个 location 属性,通过这个属性我们可以把标签排列,然后根据 click 里面的赋值就能获取 flag。

但是还有一个问题,下图两个标签在反编译看到的都是lbl\n,即是一样的无法区分,这两个标签分别决定了按钮按下生成值的前两位。而后八位可以分析出来是W6RP6SES

而 click 函数中对lbl\n赋值总共有三个分别是P,4,5,那可能性就不多,总共 6 种

P4W6RP6SES
P5W6RP6SES
4PW6RP6SES
5PW6RP6SES
45W6RP6SES
54W6RP6SES

尝试之后得到答案P4W6RP6SES

那做完之后看别人的题解得到了几种新的解题方法。
法一
直接摁回车,在对一些程序我们摁 TAB 键的时候,它会切换按钮或者标签,这个TabIndex属性就意味着 TAB 键的切换顺序,而按钮的顺序是 0 即默认选中,所以打开程序直接摁回车就出 flag 了。。。。。

法二
尝试将原来 size 为 0 的按钮调出来。

利用spy++定位到 button 的窗口句柄:


然后 52 破解工具包中有个窗口信息查看器spy,找到这个 button,之前说过了,button 的 size 为 0,所以没法儿点击,但是通过这个工具我们可以将按钮最大化:

点后面的勾,然后发现程序窗口中的按钮最大化了,直接摁点一下,再把这个 button 最小化就能看到 flag。

法三
我自己做的时候由于 reflector 可能版本比较老?导致两个变量无法区分,后来看了别人用dnspy,试了试,如下图:

直接就能看出来,那就没啥说的了,直接就可以怼出 flag 了。
ok 换工具了。。。。。。

PEPassword

题目给了两个代码,一个origin.exe,一个packed.exe,简单分析就知道 origin.exe 是程序的原始程序,packed.exe 是程序加密之后的,origin.exe运行效果如下:

分析一下之后确实没有 password,那么猜想是packed.exe里面会有,但是要运行packed.exe需要输入一个密码,所以工作就来了,找到破解加密,然后运行程序应该就会有下一步了。
那么来看看这个packed.exe,放到 IDE 里面,没啥信息,OD 跟一下之后发现一直在这里循环,

ctrl+F8跑起来,然后看到最后那个 cmp,数据窗口跟随发现我们输入就在后面,纯属偶然发现。那就在我们的输入下一个硬件访问的断点:

断下来之后发现调用源自0x409190


那这里其实正常可以对WM_KEYDOWN消息下消息断点。
然后往下走,进入了一串复杂的计算函数0x4091d8,将我们的输入进行了一串及其复杂的运算之后得到四字节的数据与0xe98f842a对比,分析了一下这个函数之后实际上是一个0x10000*输入长度的大循环,具体不多说了,那这个代码也不是很想看,所以直接绕过 cmp,阻止0x4091a6处的跳转,然后继续往下跟,之后代码运行到了0x409200,来到了关键的加密函数。

首先这一段选中代码计算了一个ebx和一个eax,用于后面的解密运算:

edi处存储加密的代码,然后 edx 赋初值之后进入解密循环,解密之后运行,如果能够解密成功就能成功运行。解密循环如下

很简单的一段迭代算法,我们根据origin.exePacked.exe两个程序可以很容易异或出每个阶段的 eax,然后需要爆破一下 ebx,总共爆破空间为0xffffffff,还可以。
那刚开始用 python 写的,如下:

from pwn import u32,p32
import threading

ori=['\x81\xec\x4c\x01','\x00\x00\x56\x57']
en=['\x17\x2e\xe6\xb6','\x05\x7e\x0c\x0d']

eax=u32(ori[0])^u32(en[0])
eax2=u32(ori[1])^u32(en[1])

print 'eax:',hex(eax)
print 'eax2:',hex(eax2)


def ro(lst, k,flag):
    if flag==-1:
        k=k%32
        k=-k
    else:
        k=k%32
    x = lst[:k]
    x.reverse()
    y = lst[k:]
    y.reverse()
    r = x+y
    return list(reversed(r))

def go(start,end):
    for i in xrange(start,end):
        ebx=i
        ebx_tmp=int("".join(i for i in ro(list("{:0>32}".format(bin(ebx)[2:])),ord(p32(eax)[0])%32,1)),2)
        eax2_tmp=ebx_tmp ^ eax
        if eax2==int("".join(i for i in ro(list("{:0>32}".format(bin(eax2_tmp)[2:])),ord(p32(ebx_tmp)[1])%32,-1)),2) :
            print "ebx:",ebx


if __name__ == "__main__":
    t_pool=[]
    for j in xrange(0x10-1):
        t=threading.Thread(target=go,args=(j*0xfffffff,(j+1)*0xfffffff))
        t_pool.append(t)
    for j in t_pool:
        j.start()
    while 1:
        pass

跑了好一会儿才意识到,emmm 有点慢,换 c 之后可以使用内联汇编,这样就不用重写代码了,如下:

// vs2012

#include "stdafx.h"

int cal(int eax_1,int ebx_1){
    unsigned int result;
    __asm{
        mov eax,eax_1;
        mov ebx,ebx_1;
        mov cl,al;
        rol ebx,cl;
        xor eax,ebx;
        mov cl,bh;
        ror eax,cl;
        add ebx,eax;
        mov result,eax;
    }
    return result;

}

int main(){
    unsigned int eax=0xb7aac296;
    unsigned int ebx;
    unsigned int eax2=0x5a5a7e05;
    for(unsigned int i=0;i<0xffffffff;i++){
        ebx=i;
        if(eax2==cal(eax,ebx)){
            printf("success: %x\n",ebx);
        }
        if(i%0xf00000==0){
            printf("%x\n",i);
        }
    }
}

运行结果如下:

跑出了两个答案,试一下就知道正确值是0xc263a2cb,然后在进解密循环的时候修改 eax 和 ebx,从而使程序正常解密并运行:

得到 flag:From_GHL2_!!