当前位置:网站首页>【逆向初级】独树一帜
【逆向初级】独树一帜
2022-07-06 09:33:00 【TiggerRun】
独树一帜
题干
题目地址:https://ctf.pediy.com/itembank.htm
是一个验证器,也可以叫做注册机。
题意是让我们拿到用户 :CTFHUB的Code
0x1 查壳
0x2 OD调试
我第一想法就是下消息断点,断在按钮类上
点击 “check” 后断下,但是消息断点的知识掌握的还不是很好,我没能追溯到消息处理后跳回的地址。
所以换了思路
有 EditText
,所以想到了之前使用过的方法
分析:
- 编辑框中的内容存在内存中
- 点击check后访问内存取出值
通过取值操作对内存的访问,定位函数的位置
果然成功的追到了函数,同时看到了win32API GetDialogTextA()函数,这里也给自己扩展了知识点,以后想断获取编辑框内容的断点可使用API断点 GetDialogTextA
我首先对函数头下断点,发现这个函数很大可能是一个窗口消息的回调函数,里面是个switch,对消息进行分类操作。
call .00401000 这个函数前 push了两个参数
压入参数分别为:
- Name
- KeyCode
随着 cmp eax,0x1,然后jnz switch的default分支,不再继续执行
所以可以猜测如果验证成功那么 eax中存放的应该是 0x1
这时候我开始耍小聪明,然后以为是只要弹出信息框就过了,在jnz的时候把ZFLAG改一下即可,或者把eax改为0x1,后来仔细去看题目,是让我提交一个Code
0x3 获取Code分析
首先要分析注册机的原理,我们输入一个注册码,程序生成一个正确的注册码,两者进行对比,从而判断是否正确。
那么获取Code的方式就有2种:
- 解析生成Code的算法
- 通过动态调试去找到生成后的Code直接拿来用
0x4 分析汇编
00401000 /$ 53 push ebx ; 独树一帜.00406A30
00401001 |. 8B5C24 0C mov ebx,dword ptr ss:[esp+0xC] ; 独树一帜.0040125C
00401005 |. 55 push ebp
00401006 |. 56 push esi ; 独树一帜.00406930
00401007 |. 8B7424 10 mov esi,dword ptr ss:[esp+0x10] ; 独树一帜.00406930
0040100B |. 8A0B mov cl,byte ptr ds:[ebx]
0040100D |. 33ED xor ebp,ebp
0040100F |. 57 push edi
00401010 |. 8A06 mov al,byte ptr ds:[esi]
00401012 |. 3AC1 cmp al,cl
00401014 |. 0F85 69010000 jnz 独树一帜.00401183
压入 0x1
赋值 ebx 为 "keycode"
压入 ebp 不知道是啥
压入 esi 中 getDialogTextA()函数地址
赋值 esi 为 账号
赋值 cl 为 [ebx]的一个字节 'cl 为keycode第一个字符'
异或 ebp,ebp
压入 edi 0x0
赋值 al 为 [esi]的第一个字节 'al 为用户名的第一个字符'
比较 al 和 cl是否相等
如果相等则继续,否则跳转到 00401183 '结束函数'
//-----------------------------------------------这里分析出keycode首字符为C
0040101A |. 8BFE mov edi,esi ; 独树一帜.00406930
0040101C |. 83C9 FF or ecx,-0x1
0040101F |. 33C0 xor eax,eax
00401021 |. F2:AE repne scas byte ptr es:[edi]
00401023 |. F7D1 not ecx ; user32.75860043
00401025 |. 49 dec ecx ; user32.75860043
00401026 |. 83F9 05 cmp ecx,0x5
00401029 |. /0F82 54010000 jb 独树一帜.00401183
知识点:
repne scas 在汇编中用于遍历字符串
ecx 通过or ecx,-0x1 初始化为 FFFFFFFF
在edi中存放字符串,在AL中存放字符
每次循环 ecx - 1
not ecx 取反
dec ecx 减一 # 因为字符串遍历到最后一个截至符后需要再减一才是真正的字符串长度
赋值 edi 为 esi '用户名'
获取到用户名长度到 ecx
比较ecx和0x5
//-----------------------------------------------这里可以分析出用户名长度至少为5
0040102F |. 807B 01 2D cmp byte ptr ds:[ebx+0x1],0x2D
00401033 |. 0F85 4A010000 jnz 独树一帜.00401183
//-----------------------------------------------判断KeyCode第二个字符是否为 0x2D '-'
分析到这里我们可知道以下几点:
- CODE 前缀为 ‘C-’
- 用户名长度至少为5
00401039 |. 8BFE mov edi,esi ; 独树一帜.00406930
0040103B |. 83C9 FF or ecx,-0x1
0040103E |. 33C0 xor eax,eax
00401040 |. 33D2 xor edx,edx
00401042 |. F2:AE repne scas byte ptr es:[edi]
00401044 |. F7D1 not ecx
00401046 |. 49 dec ecx
这一段代码很眼熟,前面有,就是取出EDI中字符串长度
00401049 |> /0FBE0C32 /movsx ecx,byte ptr ds:[edx+esi]
0040104D |. |03E9 |add ebp,ecx
0040104F |. |8BFE |mov edi,esi ; 独树一帜.00406930
00401051 |. |83C9 FF |or ecx,-0x1
00401054 |. |33C0 |xor eax,eax
00401056 |. |42 |inc edx
00401057 |. |F2:AE |repne scas byte ptr es:[edi]
00401059 |. |F7D1 |not ecx
0040105B |. |49 |dec ecx
0040105C |. |3BD1 |cmp edx,ecx
0040105E |.^\72 E9 \jb short 独树一帜.00401049
add ebp,0x6064
这里可看出是一个循环,弹出条件是 edx == ecx,ecx中存放的是用户名的长度
循环的操作是遍历字符串每个字符,字符的ASCII值加到ebp中
第一句是取出edx + esi地址的一个字符,edx初始值为0,随着循环 inc edx递增
跳出循环后 ebp 又加上了一个常数
00401066 |. 55 push ebp
00401067 |. 68 34604000 push 独树一帜.00406034 ; ASCII "%lu"
0040106C |. 68 306B4000 push 独树一帜.00406B30 ; ASCII "49796"
00401071 |. E8 B6030000 call 独树一帜.0040142C
后面觉得越看越不对劲,第一题不会这么难把??
然后我想着从后往前追溯,看看能不能找到生成好的CODE。
果然,不停的F8就可获取到
0040118E |. BF 446B4000 mov edi,独树一帜.00406B44 ; ASCII "C-B25120-49796"
那么虽然我有了这个正确的Code,如果题目出的难一点,他要随机生成用户的注册码,那么解决方案有2种:
- HOOK 生成Code的函数,获取到正确的Code
- 分析加密算法
0x5 分析加密算法实现注册机
这里其实就已经超纲了,题目解题在0X4就已经结束。有兴趣可继续往下看。
如何去判断正确的算法流程呢?我们已经有了正确的Code,所以跟着正确的Code应该就可以跑出正确的流程。
00401076 |. 8A16 mov dl,byte ptr ds:[esi]
00401078 |. 8BFE mov edi,esi ; 独树一帜.00406930
0040107A |. 83C9 FF or ecx,-0x1
0040107D |. 33C0 xor eax,eax
0040107F |. 8815 446B4000 mov byte ptr ds:[0x406B44],dl
00401085 |. C605 456B4000>mov byte ptr ds:[0x406B45],0x2D
0040108C |. F2:AE repne scas byte ptr es:[edi]
0040108E |. F7D1 not ecx
00401090 |. 49 dec ecx
00401091 |. 0FBE4431 FF movsx eax,byte ptr ds:[ecx+esi-0x1]
00401096 |. 50 push eax
00401097 |. E8 C4020000 call 独树一帜.00401360
这一段汇编,先是取出用户名长度
然后 movsx eax,byte ptr ds:[ecx+esi-0x1],取出最后一个字符
push eax 把这个字符压栈,执行下面的函数,但是下面的这个函数执行后对程序的堆栈和寄存器没产生影响,所以这个函数是可忽略的。
0040109C |. A2 466B4000 mov byte ptr ds:[0x406B46],al
004010A1 |. BF 306B4000 mov edi,独树一帜.00406B30 ; ASCII "25120"
004010A6 |. 83C9 FF or ecx,-0x1
004010A9 |. 33C0 xor eax,eax
004010AB |. F2:AE repne scas byte ptr es:[edi]
004010AD |. F7D1 not ecx
004010AF |. 2BF9 sub edi,ecx
004010B1 |. 81C5 64600000 add ebp,0x6064
这段代码又是很眼熟的,然后后面又加上了0x6064
这里看到了C-B,然后往上看赋值语句
猜想 0x406B44为字符串数组的首地址
通过后面的调试发现 key格式为 C-{用户最后一个字符}{计算而来}-{计算而来}
地址为 00406B30,这个数据为重要的数据
所以对他下断点,定位程序什么时候对这个内存存在写入操作
就这样我定位到了算法内部,然后返回上层函数后,定位到了真正的加密函数中
但是又发现已经在堆栈中存在这个值,说明在执行这个函数之前,就已经进行了加密。
所以追溯之前调用的函数
00401F2A |> /8B45 F0 |/mov eax,[local.4]
00401F2D |. |FF4D F0 ||dec [local.4]
00401F30 |. |85C0 ||test eax,eax
00401F32 |. |7F 06 ||jg short 独树一帜.00401F3A
00401F34 |. |8BC6 ||mov eax,esi
00401F36 |. |0BC7 ||or eax,edi
00401F38 |. |74 3B ||je short 独树一帜.00401F75
00401F3A |> |8B45 F4 ||mov eax,[local.3]
00401F3D |. |99 ||cdq
00401F3E |. |52 ||push edx
00401F3F |. |50 ||push eax
00401F40 |. |57 ||push edi
00401F41 |. |56 ||push esi
00401F42 |. |8945 C0 ||mov [local.16],eax
00401F45 |. |8955 C4 ||mov [local.15],edx
00401F48 |. |E8 03150000 ||call 独树一帜.00403450
00401F4D |. |FF75 C4 ||push [local.15]
00401F50 |. |8BD8 ||mov ebx,eax
00401F52 |. |83C3 30 ||add ebx,0x30
00401F55 |. |FF75 C0 ||push [local.16]
00401F58 |. |57 ||push edi
00401F59 |. |56 ||push esi
00401F5A |. |E8 81140000 ||call 独树一帜.004033E0
00401F5F |. |83FB 39 ||cmp ebx,0x39
00401F62 |. |8BF0 ||mov esi,eax
00401F64 |. |8BFA ||mov edi,edx
00401F66 |. |7E 03 ||jle short 独树一帜.00401F6B
00401F68 |. |035D D4 ||add ebx,[local.11]
00401F6B |> |8B45 F8 ||mov eax,[local.2]
00401F6E |. |FF4D F8 ||dec [local.2]
00401F71 |. |8818 ||mov byte ptr ds:[eax],bl
00401F73 |.^\EB B5 |\jmp short 独树一帜.00401F2A
00401F75 |> 8D45 B7 |lea eax,dword ptr ss:[ebp-0x49]
00401F78 |. 2B45 F8 |sub eax,[local.2]
00401F7B |. FF45 F8 |inc [local.2]
这里就是我们要的加密函数,其中第一个CALL为关键CALL
反复分析后发现,前面这段汇编是没有影响的, 取esp + 0x8位置是重要的,然后通过div ecx,有了进一步发现,发现ECX的值是恒定的,从外层函数可看出,0XA是个常数,div ecx,除数放在EAX中,处理后商放在EAX中,余数放在EDX中,然后进入下一次循环,这个商又作为被除的数,然后我也发现余数就是我们的第一个加密参数的逆序输出。
那么问题又回到了追溯这个EAX初始值 0x6261是如何来的?
在之前分析过有个 ebp += 用户名字符的ASCII 然后加一个0x6064
为了验证,我也对eax的来源进行追溯,下了内存访问断点,也正好回到了之前分析的位置。
然后就是用c++写个demo测试以下想法
这里是 6220 因为我测试的时候用的用户名和在OD里的不一样,但是如果一样 这个值是相等的。
所以我的猜想是正确的。
这里发现25120 02152是反过来的,我仔细一想… % 0xA 不就是一个十六进制转十进制的过程嘛?所以完全没必要重新计算。
第一个参数出来了,第二个参数也不遥远了,通过同样的内存写入断点,追溯关键CALL,发现最后还是来到了我们分析过的加密算法,只不过eax初始值不是 0x6220了,而是一个其他的数字,我想到之前不是还有个ebp+0x6064的地方嘛?
果然就是加上一个 6064就可可以了,然后就是转十进制的事情!
如果按照原算法复现,那应该是这样
但是实际上并不是那么复杂的,最后的代码
#include<iostream>
#include<string>
using namespace std;
int main(){
string name;
cout<<"输入用户名:";
cin>>name;
int strLen = name.size();
int ebp,code1,code2;
code1 = code2 = 0;
ebp = 0x0;
for(int i=0;i < strLen;i++){
ebp += int(name[i]);
}
ebp += 0x6064;
code1 = ebp;
code2 =code1 + 0x6064;
cout<<"keys:"<<"C-"<<name[strLen-1]<<code1<<"-"<<code2<<endl;
return 0;
}
0x6
转发请亲们注明出处,有兴趣大家一起学习~
边栏推荐
- Design of DS18B20 digital thermometer system
- 服务器端渲染(SSR)和客户端渲染(CSR)的区别
- 8086 segmentation technology
- Prototype chain inheritance
- The difference between URI and URL
- 字节跳动春招攻略:学长学姐笔经面经,还有出题人「锦囊」
- Activiti directory (III) deployment process and initiation process
- Flink源码解读(一):StreamGraph源码解读
- "One year after graduation, I won ACL best paper"
- Go language uses the thrift protocol to realize the client and service end reports not enough arguments in call to oprot Writemessagebegin error resolution
猜你喜欢
EasyRE WriteUp
MySQL日期函数
When it comes to Google i/o, this is how ByteDance is applied to flutter
Some instructions on whether to call destructor when QT window closes and application stops
Which is more important for programming, practice or theory [there are some things recently, I don't have time to write an article, so I'll post an article on hydrology, and I'll fill in later]
Logical operation instruction
案例:检查空字段【注解+反射+自定义异常】
汇编语言段定义
MySQL字符串函数
High performance mysql (Third Edition) notes
随机推荐
À propos de l'utilisation intelligente du flux et de la carte
唯有学C不负众望 TOP5 S1E8|S1E9:字符和字符串&&算术运算符
逻辑运算指令
On the clever use of stream and map
SQL调优小记
Only learning C can live up to expectations top2 P1 variable
QT system learning series: 1.2 style sheet sub control lookup
吴军三部曲见识(七) 商业的本质
After the subscript is used to assign a value to the string type, the cout output variable is empty.
Log4j2 major vulnerabilities and Solutions
8086 分段技术
Compile homework after class
Akamai浅谈风控原理与解决方案
Description of project structure configuration of idea
redux使用说明
TCP的三次握手和四次挥手
Programmer orientation problem solving methodology
Resume of a microservice architecture teacher with 10 years of work experience
Activiti directory (V) reject, restart and cancel process
JVM garbage collector part 2