0x01 bin100(ebCTF 2013)
运行可执行文件
是一个掷筛子游戏
需要我们每一步掷出特定数字
然后得出flag
记住关键字
关键字
载入IDA,查找字符串:
找到关键函数:WinMain
大致看一下流程,发现到最后:
v55 = std::operator<<<std::char_traits<char>>(&std::cout, "[*] You rolled 3-1-3-3-7, what does that make you? ELEET! \\o/");
std::ostream::operator<<(v55, std::endl<char,std::char_traits<char>>);
v56 = std::operator<<<std::char_traits<char>>(&std::cout, "[*] Nice job, here is the flag: ");
v57 =std::operator<<<char,std::char_traits<char>,std::allocator<char>>(v56, &v84);
我们看到最后输出字符串v84
Alt+T查找v84
Ctrl+T查看下一个v84
找到两处关键:
std::string::operator=(&v84, &unk_444240);
i = 0;
for ( j = 0; ; ++j )
{
fctx.call_site = 1;
v52 = std::string::size((std::string *)&v84);
if ( j >= v52 )
break;
v64 = (_BYTE *)std::string::operator[](&v84, j);
v53 = (_BYTE *)std::string::operator[](&v87, i);
*v64 ^= *v53;
++i;
v54 = std::string::length((std::string *)&v87);
if ( i >= v54 )
i = 0;
}
fctx.call_site = 1;
if ( std::string::find((std::string *)&v84, "ebCTF", 0) == -1 )
{
fctx.call_site = 1;
v59 = std::ostream::operator<<(&std::cout, std::endl<char,std::char_traits<char>>);
v60 = std::operator<<<std::char_traits<char>>( v59, "[!] It seems you did something wrong :( No flag for you.");
可以看出:
- &unk_444240处的数据以字符串形式赋值给v84
- 在满足每一次掷数字的条件后,会将v84与v87的每一位进行异或运算,且当v87位数不足时,v84再与v87第一位运算(可以看成v87头尾相接的循环)
下面查找v87:
发现几个关键点:
std::string::operator=(&v87, &unk_444309);
for ( i = 0; ; ++i )
{
fctx.call_site = 1;
v36 = std::string::size((std::string *)&v87);
if ( i >= v36 )
break;
v37 = (_BYTE *)std::string::operator[](&v87, i);
*v37 ^= v86;
}
for ( i = 0; ; ++i )
{
fctx.call_site = 1;
v50 = std::string::size((std::string *)&v87);
if ( i >= v50 )
break;
v51 = (_BYTE *)std::string::operator[](&v87, i);
*v51 ^= v85;
}
可以看出:
- &unk_444309处的数据以字符串形式赋值给v87
- v87的每一位先与v86异或,再与v85异或
下面查找v86和v85:
v86:
if (isDebuggerPresent() )
v86 = 66;
else
v86 = 16;
这里调用 isDebuggerPresent() 检测是否在调试环境下运行:
显然:
v86=16
v85则是每一步掷数字成功都进行判断,仔细跟进:
v85 = 6
v85 *= 2
v85 += 4
v85 *= 3
v85 += 2
v85 *= 2;
v85 *= 50;
v85 /= 50;
v85 += 65;
v85 -= 65;
v85 *= 42;
v85 /= 42;
得到:
v85=100
而后:
Shift+E提取&unk_444240和&unk_444309处的数据
注意:字符串以00结尾,在脚本破解的时候注意把提取的00删去(或者直接不提取)
提取后脚本破解:
key1="132138153D3357472D276A73440526595C79174445771A75497D054A78746A70420271050F2208"
key2="02370F350F3C15073C302A30551237151E350151"
key3=16
key4=100
flag=""
for i in range(0,len(key1),2):
ans1=int(key1[i:i+2],16)
j=i%len(key2)
ans2=int(key2[j:j+2],16)
flag+=chr(ans2^key3^key4^ans1)
print flag
有几点注意:
- 这里每一步都调用了time(),以防掷数字中间设置断点,从而防止调试:
OD载入我们看到这里:
time
这里调用了time类函数
我们可以进入此地址:
00412690 - FF25 F4C24400 jmp dword ptr ds:[<&msvcrt.time>] ; msvcrt.time
可以看出这里是函数列表,所有call指令都先对应到这个列表,再进行对应跳转,我们将此处改为retn,便会使其不能调用time类的函数
不过有一个问题,从ida也可以看到,其会比较两次time之差:
v16 = time(0);
v79 = v16;
v81 = v16 - v80;
if ( v16 - v80 > 2 )
v85 *= 2;
如果不调用time,那么比较的就会是默认值
我们调试一下,定位到这里:
.text:00401AF8 call _time
.text:00401AFD mov [ebp+var_68], eax
.text:00401B00 mov edx, [ebp+var_64]
.text:00401B03 mov eax, [ebp+var_68]
.text:00401B06 sub eax, edx
.text:00401B08 mov [ebp+var_60], eax
.text:00401B0B cmp [ebp+var_60], 2
.text:00401B0F jle short loc_401B19
可以看出,这里将两个time结果放到 [ebp+var_64]和[ebp+var_68]中
然后再传入eax和edx进行比较
如果不调用这里就会使用原来的edx和eax中的值,也可能>2,从而不能达到预期结果
所以这里需要在每次判断下断点修改,或者找到所有判断位置修改汇编代码
- 注意isDebuggerPresent()的那一步判断:
0040175A . E8 FD170000 call Dice.00402F5C
0040175F . 85C0 test eax,eax
00401761 . 74 09 je XDice.0040176C
00401763 . C745 D4 42000>mov dword ptr ss:[ebp-0x2C],0x42
0040176A . EB 07 jmp XDice.00401773
0040176C > C745 D4 10000>mov dword ptr ss:[ebp-0x2C],0x10
这里同样在判断处下断点,修改标志寄存器即可
- 上面都是分析方法,最简单的还是修改每一次的判断跳转为nop,然后保存成新文件,五次enter之后即得flag
- 这里留个问题,isDebuggerPresent()在OD下调试会直接返回false,在ida下返回true,这个问题有待解决
0x02 该题不简单
根据题目描述:
找出用户名为hello的注册码
下载并运行程序,输入用户名:hello
注册码随意输入
发现关键字:
这里两种方法:
直接调试分析:
OD载入:
通过中文字符查找到关键部分,并在跳转上一步下断点调试:
即可在堆栈窗口得到所需注册码:
flag
IDA分析
载入IDA:
Alt+T查找关键字符:“密钥”
结果
可以看到几点:
- aZgb和Text变量分别保存了“密钥无效”和“密钥正确”的返回信息
- 两者都在DialogFunc中被调用
我们查看一下DialogFunc函数的源码,看到关键判断部分:
if ( !sub_4011D0() )
{
MessageBoxA(hWnd, Text, Caption, 0);
return 0;
}
MessageBoxA(hWnd, aZgb, Caption, 0);
}
可以看到关键的判断函数是sub_4011D0:
其中关键部分:
v1 = 0;
if ( strlen(String) != 0 )
{
do
{
*(&String2 + v1) = (v1 + v1 * String[v1] * String[v1]) % 0x42 + 33;
++v1;
}
while ( v1 < strlen(String) );
}
strcpy(String, "Happy@");
lstrcatA(String, &String2);
result = lstrcmpA(&String1, String) != 0;
可以看到String保存用户名
String1保存注册码
而后经过运算生成新的String最后与String1比较
我们需要result返回false,故而需要两者相等
下面计算hello为String时对应的String1:
flag="Happy@"
i=0
for j in "hello":
flag+=chr((i+i*ord(j)*ord(j))%0x42+33)
i+=1
print flag
运行脚本即得flag
0x03 证明自己吧
题目描述:
The Key is your Correct Input
我们要求的便是正确的输入Key的值
打开程序,随便输入,发现关键字:
将程序载入IDA:
打开查找字符串并看到字符串定义信息:
字符串
很快定位到两个关键函数:
_main和sub_401060
看到_main的关键部分:
if ( sub_401060((const char *)&v4) )
{
sub_4011BA((int)aGoodTheKeyIsYo, v4);
result = 0;
}
else
{
sub_4011BA((int)aYouDonTGuessIt, v4);
result = 0;
}
return result;
我们需要sub_401060返回true
看到sub_401060关键部分:
if ( strlen(a1) == strlen((const char *)&v5) )
{
v1 = 0;
if ( strlen(a1) != 0 )
{
do
a1[v1++] ^= 0x20u;
while ( v1 < strlen(a1) );
}
v2 = 0;
if ( strlen((const char *)&v5) != 0 )
{
do
*((_BYTE *)&v5 + v2++) -= 5;
while ( v2 < strlen((const char *)&v5) );
}
v3 = 0;
if ( strlen((const char *)&v5) == 0 )
return 1;
while ( *((_BYTE *)&v5 + v3 + a1 - (const char *)&v5) == *((_BYTE *)&v5 + v3) )// a1 - (const char *)&v5=0
{
++v3;
if ( v3 >= strlen((const char *)&v5) ) // >=0
return 1;
}
}
return 0;
显然有以下两点:
- 我们输入的a1长度需要等于&v5长度
- a1的每一位与0x20异或后和&v5的每一位减5相同
首先在
if ( strlen(a1) == strlen((const char *)&v5) )
处下断点调试,发现关键汇编代码段:
.text:00401092 mov edi, esi
.text:00401094 or ecx, 0FFFFFFFFh
.text:00401097 xor eax, eax
.text:00401099 mov edx, dword_407094
.text:0040109F repne scasb
.text:004010A1 not ecx
.text:004010A3 dec ecx
.text:004010A4 mov [esp+1Ch+var_8], edx
.text:004010A8 mov edx, ecx
.text:004010AA lea edi, [esp+1Ch+var_10]
.text:004010AE or ecx, 0FFFFFFFFh
.text:004010B1 repne scasb
.text:004010B3 not ecx
.text:004010B5 dec ecx
.text:004010B6 cmp edx, ecx
.text:004010B8 jnz loc_40115A
可以看出,edx和ecx中利用 repne scasb分别存放两字符串长度,再进行比较,由此可得到我们需要输入的字符串长度:
ecx
继续调试:
在:
*((_BYTE *)&v5 + v2++) -= 5;
处下断点:
.text:004010F7 mov al, byte ptr [esp+edx+1Ch+var_10]
.text:004010FB lea edi, [esp+1Ch+var_10]
.text:004010FF add al, 0FBh
.text:00401101 or ecx, 0FFFFFFFFh
.text:00401104 mov byte ptr [esp+edx+1Ch+var_10], al
.text:00401108 xor eax, eax
.text:0040110A inc edx
.text:0040110B repne scasb
.text:0040110D not ecx
.text:0040110F dec ecx
.text:00401110 cmp edx, ecx
.text:00401112 jb short loc_4010F7
可以看出,&v5的每一位便是每一次循环的ax中的值:
AX
得到每一个值后脚本破解即得结果:
key=[0x63,0x52,0x14,0x43,0x4B,0x69,0x53,0x73,0x4F,0x65,0x14,0x53,0x59,0x1]
flag=""
for i in key:
flag+=chr(i^0x20)
print flag
网友评论