2017看雪秋季CTF--第二题分析笔记
首先拿到题目打开,
crackme
程序要求输入注册码,失败输出Bad register-code
按常规思路,想直接丢IDA里F5大法,找到字符串,尝试F5失败
只能逐行看汇编
main
发现 41B034为0,则输出“You get it!\n”;
而变量41B034初始值为2,但是紧跟着三个调用401050 401090 4010E0
则初步判断函数在这三个调用里对输入的注册码进行校验和处理的
双击进去看看,401090 4010E0里发现有运算,F5分析
0x401090 0x4010e0
两个if得出4个等式
5 * (v1 - v0) + v1 = 0x8F503A42 (等式1)
13 * (v1 - v0) + v0 = 0xEF503A42
17 * (v1 - v0) + v1 = 0xF3A94883 (等式2)
7 * (v1 - v0) + v0 = 0x33A94883
if语句通过的话刚好把变量41B034的值减到0,就可以通过验证了
本以为这题就结束了
但是
仔细看等式2-等式1 等式是永远无法成立的,需要换个思路
IDA继续往下看
发现一大堆数据
有点奇怪但不知道问题在哪
只能从已知的内容下手,返回刚才的401050
401050
发现了该函数在栈中分配了一块12字节大小的局部空间
(var_C = dwrod ptr -0Ch)
该空间提供给scanf做输入缓冲区
F5,我们看到v1(缓冲区)大小为12
而且发现序列号没有限制输入位数,这里就考虑到scanf的栈溢出
简单验证一下
OD载入,在输入前下段,查看堆栈情况
输入123测试,看堆栈
scanf 123.png
scanf存储输入的缓冲区是地址为12FF6C,这样以来,如果输入的字符长度大于8,则会将堆栈中的数据覆盖掉。
当前程序控制权在0x401050,也就是说我们想从当前溢出,就必须输入13位以上字符去覆盖0x401050的返回地址,才能在调用结束后溢出到我们想要的位置。 我们当然想要程序直接溢出到输出“You get it!\n”(0x0040102F)的位置,这样我们的任务就完成了。
但这样与题不符,如果输入13个字符,将1C改成2F,由于scanf会在读取字符的末尾加上0x00(结束符)会将0x10改成00,也就是将地址改成了40002F,这样肯定不对。那么只能输入15位了。因为恰好40102F的最高字节也是00,不怕被00覆盖。这样就可构造出序列号成功溢出了,但是这样有与题不符,无法保证序列号唯一性(前12位)并且不符合题目所给序列号要求(大小写字母和数字)
问题又来了,我们到底溢出到哪呢
继续从IDA里面找
想起来之前IDA里看到的一大堆数据
仔细看前面的数据,很多7开头的,查手册
jxx.png
都是跳转啊!很明显的程序加花指令,明显是作者不想让我们看些到什么
而地址为00413131, 又考虑到0x14 0x31都符合要求(注册码要求的值的ascii),构造溢出点11A(00413131)
溢出
可以看到已经溢出到我们想要的位置
dd.pngOD进去发现也是一堆数据,但是程序竟然在数据上跳转,立马反应过来这段并不是数据而是代码,可能是程序加花
参考看雪的《加密与解密》,抵御静态分析主要从汇编代码可读性入手,对反汇编来说其中一个关键的问题就是数据与代码的区分
大量花指令,汇编指令长度和多种多样间接跳转,导致反汇编工具把代码段当作数据段来处理
输入12345678901211A,OD跟进,处理数据 右键->分析->下次分析时视为commands 这时汇编指令已经出来了
//或者IDA选中代码块,右键->分析选中代码->force->yes
程序加花指令后汇编的可读性很差,但还要一步步调,
我想偷个懒
在程序 push验证失败信息 0x40103F 处下段
尝试 查看->RUN跟踪 打开跟踪窗口 调试->跟踪步入
只截了一部分图
最后程序停在了 “验证失败”的信息处 证明程序验证我们输入的字符串,再看EAX=34333231,存入了我们输入的前4个值并进行验证,也就是说我们猜测的溢出点正确
OD记录了经过的所有汇编指令,去除花指令(各种跳)后记录分析
发现
最后 sub eax, 0xEAF917E2 (记录为校验1)之后验证就结束了,跳到验证失败
尝试在 sub eax, 0xEAF917E2 下段并修改eax的值,使sub运算结束eax为0,继续执行验证。
再次RUN跟踪(记为校验2) 这次在 sub eax,0xE8F508C8 跳出
再次重复操作 使sub后为0 再次跟踪(记为校验3)
至此我们已经完整记录了三次(12字符)的计算流程
然后化简出公式算出三次校验的正确输入值
如下
sub esp,10
xor eax,eax
mov dword_41B034, eax
pop eax
mov ecx,eax
pop eax
mov ebx,eax
pop eax
mov edx,eax
;校验1 地址:4131B9
mov eax,ecx
sub eax,ebx ecx-ebx
shl eax,2 (ecx-ebx)*4
add eax,ecx (ecx-ebx)*2+ecx
add eax,edx (ecx-ebx)*2+ecx+edx
sub eax, 0xEAF917E2 (ecx-ebx)*2+ecx+edx-0xEAF917E2 == 0 eax清0
;校验2 地址:413455
add eax,ecx ecx
sub eax,ebx eax-ebx
mov ebx,eax
shl eax,1 (ecx-ebx)*2
add eax,ebx (ecx-ebx)*2 +(ecx-ebx)
add eax,ecx (ecx-ebx)*2 +(ecx-ebx) + ecx
mov ecx, eax //这一行校验3后面用
add eax,edx (ecx-ebx)*2 +(ecx-ebx) + ecx +edx
sub eax,0xE8F508C8 (ecx-ebx)*2 +(ecx-ebx) + ecx +edx -0xE8F508C8 == 0 eax清0
;校验3
mov eax,ecx (ecx-ebx)*2 +(ecx-ebx) + ecx
sub eax,edx (ecx-ebx)*2 +(ecx-ebx) + ecx -edx
sub eax, 0xC0A3C68 (ecx-ebx)*2 +(ecx-ebx) + ecx -edx - 0xC0A3C68 == 0
换算
4 * (v0-v1)+v0+v2 == 0xEAF917E2
2 * (v0-v1)+(v0-v1)+v0+v2 == 0xE8F508C8
2 * (v0-v1)+(v0-v1)+v0-v2 == 0xC0A3C68
可直接百度在线计算
v0=0x7473754A
v1=0x726F6630
v2=0x6E756630
放进十六进制编辑器
key.png
注册码:Just0for0fun11A
成功