学习逆向也有一段时间了 也是对于这段时间的一个总结
正所谓工欲善其事必先利其器,逆向之前要准备好我们的家伙什儿
链接:https://pan.baidu.com/s/1tCijS4s_g6KFwV9ycCPVkw 提取码:nd46 反编译工具,还有各种插件实现不同功能,主要应用于静态分析
吾爱破解专用版就不戳,可能会报毒,一般破解软件会报毒 动态分析神器 爱盘
.NET逆向
.jar文件逆向
软件查壳
逆向分析中较多elf文件需要在linux环境下调试分析,可以gdb动态调试elf,也可以配合IDA进行动态调试
IDA动调可参考
.py逆向可以通过线上反编译 .py文件在线反编译
题目叫开源,就真开源呗,附件.c文件
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { if (argc != 4) { printf("what?\n"); exit(1); } unsigned int first = atoi(argv[1]); if (first != 0xcafe) { //first == 0xcafe 跳过 printf("you are wrong, sorry.\n"); exit(2); } unsigned int second = atoi(argv[2]); if (second % 5 == 3 || second % 17 != 8) { //second % 17 == 8 && second % 5 != 3 printf("ha, you won't get it!\n"); exit(3); } if (strcmp("h4cky0u", argv[3])) { //argv[3] == h4cky0u len == 7 printf("so close, dude!\n"); exit(4); } printf("Brr wrrr grr\n"); unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207; printf("Get your key: "); printf("%x\n", hash); return 0; }代码审计 经过三个判断输出flag(hash值)
int first = 0xcafe; unsigned int hash = first * 31337 + 8 * 11 + 7 - 1615810207; printf("%x\n", hash);c0ffee
查壳,upx壳,还是elf文件,kali命令脱壳 IDA载入 开幕雷击 flag{Upx_1s_n0t_a_d3liv3r_c0mp4ny}
IDA 找到main f5反汇编 4007C0函数输出密码错误 脚本输出flag eg.大数v7代码中8个字节拆分为8份调用
string a = ":\"AL_RT^L*.?+6/46"; long long v7 = 28537194573619560LL; for (int i = 0; i < a.size(); i++) { cout << (char)(*((char*)&v7 + i % 7) ^ a[i]); }RC3-2016-XORISGUD
IDA 生成随机数 先查看strs值 9447{This_is_a_flag} 这就是flag。。。。 hhhh
直接IDA开干
f5 反汇编
直接看到文件操作 生成文件名为flag.txt 大胆猜测应该是经过操作在文件中生成flag
分析各种字符串 可知
s = { "c61b68366edeb7bdce3c6820314b7498" }; t = { "SharifCTF{????????????????????????????????}" } u = { "*******************************************" }分析一开始的循环
脚本得到处理之后的字符串t
char s[100] = { "c61b68366edeb7bdce3c6820314b7498" }; char t[100] = { "SharifCTF{????????????????????????????????}" } for (int i = 0; i < s.length(); i++) { if (i & 1) v3 = 1; else v3 = -1; t[i+10] = (char)(s[(signed int)i] + v3); }得到新的字符串t
char t[100] = { "SharifCTF{b70c59275fcfa8aebf2d5911223c6589}" };对于int 数组 p[43] 观察到汇编代码中以十六进制四字节存储 组成p[43]
int p[43] = {30,24,25,32,40,36,28,17,34,39,16,33,19,26,5,3,29,27,31,4,8,15,37,42,14,41,2,23,21,0,10,20,7,11,1,13,6,38,18,35,12,22,9 };文件操作直接脚本
#include <bits/stdc++.h> using namespace std; int main() { int v3; char s[100] = { "c61b68366edeb7bdce3c6820314b7498" }; char b[100] = { "b70c59275fcfa8aebf2d5911223c6589" }; char u[100] = { "*******************************************" }; char t[100] = { "SharifCTF{b70c59275fcfa8aebf2d5911223c6589}" }; int p[43] = {30,24,25,32,40,36,28,17,34,39,16,33,19,26,5,3,29,27,31,4,8,15,37,42,14,41,2,23,21,0,10,20,7,11,1,13,6,38,18,35,12,22,9 }; FILE * stream; char * filename; filename = new char; strcpy(filename, "flag.txt");//直接源目录生成文件 stream = fopen(filename, "w");//打开文件 fprintf(stream, "%s\n", u, strlen(s));//以字符串u初始化文件 for (int i = 0; i < strlen(t); ++i) { fseek(stream, p[i], 0);//转移文件指针至一定位置 fputc((char)*(t + p[i]),stream);//修改转移后文件位置的字符 //以下代码将flag再次初始化为u 迷惑操作 注释掉 //fseek(stream, 0LL, 0); //fprintf(stream, "%s\n", u); } fclose(stream); //remove删掉flag文件 注释掉 //remove(filename); return 0; }奥里给
SharifCTF{b70c59275fcfa8aebf2d5911223c6589}
.py在线反编译 https://tool.lu/pyc/ 简单算法逆向
python脚本
import base64 correct = "XlNkVmtUI1MgXWBZXCFeKY+AaXNt" flag = '' correct = base64.b64decode(correct) for i in correct: s = i s -= 16 flag += chr(s ^ 32) print(flag)nctf{d3c0mpil1n9_PyC}
载入IDA分析
f5反汇编main函数
v3 = strcmp((const char *)&v5, &v9);重要判断条件 比较v5字符串和v9字符串
双击进入unk_413E90函数显示字符串get flag
v5应与v9相同
_mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&byte_413E34));作用类似于strcpy,将413E34值赋给v5 但是由于字符串结束判断条件为\0字符 即值为0的字节
而第一个0出现在413E44结尾,故整个判断字符串v5为DUTCTF{We1c0met0DUTCTF}
即为flag
eg. 关于data数据中可以通过快捷键 ‘r’ 将16进制数转换为字符
打开程序发现是一个关于开灯的游戏 输入不同的数字N可以改变第N排的灯,但是其周围的灯也会受到影响,可以通过分析得到答案(hhh)
既然不是输入key 那么就可以动调出答案 ida先瞅一眼 直接看main函数下面的关键判断,推断457AB4为结果
双击进入查看 发现关键字符串,也可通过Ctrl+T搜索字符串发现
并且可以发现,下面就是flag生成过程 这样静态分析也可以咯
两个异或 上面的一大堆数据相当于两个数组 变量在内存中顺序排列,所以*(&v2+i)就可以遍历v2下面的一系列变量的值 v59同理
脚本
a=[18,64,98,5,2,4,6,3,6,48,49,65,32,12,48,65,31,78,62,32,49,32,1,57,96,3,21,9,4,62,3,5,4,1,2,3,44,65,78,32,16,97,54,16,44,52,32,64,89,45,32,65,15,34,18,16,0] # v59-v115 b=[123,32,18,98,119,108,65,41,124,80,125,38,124,111,74,49,83,108,94,108,84,6,96,83,44,121,104,110,32,95,117,101,99,123,127,119,96,48,107,71,92,29,81,107,90,85,64,12,43,76,86,13,114,1,117,126,0] # v2-v58 flag = '' for i in range(56): b[i] ^= a[i] flag += chr(b[i]^0x13) print(flag)zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
祭出神器OD
回顾一下最后判断条件的部分 可以看到在判断条件之前出现字符串CLS 可以通过这个字符串入手找到判断跳转
主线程右键中文搜索引擎智能搜索 进入字符串界面 右键Find查找CLS 双击跟进 IDA和OD的虚拟地址后几位都是一样的 所以将参数"CLS"推进栈后调用的00F581B7函数就是j_system函数
下面一连串jnz判断跳转 最后call 输出flag的函数00F57AB4
直接跳过jnz jmp到00F57AB4
运行
zsctf{T9is_tOpic_1s_v5ry_int7resting_b6t_others_are_n0t}
载入IDA main函数F5反编译 简单的算法 将输入的字符串转化为16进制码与v13比较
在线转码
CrackMeJustForFun
elf文件 可能面临linux运行调试
IDA瞅瞅 main函数里面 这个authenticate是主要函数 函数还是比较简单的 虽然有一些wchar_t这样的陌生的数据形式 但是基本能猜到 s2为加密后的字符串 输入字符串和其比较 看看参数 s 和 8A90里面是什么
大概猜到这种类型数据占用4个Byte 真正存储数据的只有第一个Byte 因为其余三个Byte数据要么重复 要么没有价值 先用IDC内置脚本把s串内容搞出来
auto i; for(i=0;;i++) { Message("0x%x,",Byte(0x8048AA8+4*i)); if(!Byte(0x8048AA8+4*i)) break; } //shift+F2 打开IDA脚本编辑界面 IDA提供IDA(类C)和python两种语言 s = [0x3a,0x36,0x37,0x3b,0x80,0x7a,0x71,0x78,0x63,0x66,0x73,0x67,0x62,0x65,0x73,0x60,0x6b,0x71,0x78,0x6a,0x73,0x70,0x64,0x78,0x6e,0x70,0x70,0x64,0x70,0x64,0x6e,0x7b,0x76,0x78,0x6a,0x73,0x7b,0x80,0x0,]再看看decrypt
可以看到算法很简单,但是a2参数只有5个字符,而i越界,两个参数在内存中连续储存,所以a2越界后取到s字符串的值,相当于a2 = a2 + s
脚本
#include <bits/stdc++.h> using namespace std; int main() { vector<int> s = { 0x3a,0x36,0x37,0x3b,0x80,0x7a,0x71,0x78,0x63,0x66,0x73,0x67,0x62,0x65,0x73,0x60,0x6b,0x71,0x78,0x6a,0x73,0x70,0x64,0x78,0x6e,0x70,0x70,0x64,0x70,0x64,0x6e,0x7b,0x76,0x78,0x6a,0x73,0x7b,0x80,0x0 }; vector<int> a2 = { 0x1,0x2,0x3,0x4,0x5,0x0,0x3a,0x36,0x37,0x3b,0x80,0x7a,0x71,0x78,0x63,0x66,0x73,0x67,0x62,0x65,0x73,0x60,0x6b,0x71,0x78,0x6a,0x73,0x70,0x64,0x78,0x6e,0x70,0x70,0x64,0x70,0x64,0x6e,0x7b,0x76,0x78,0x6a,0x73,0x7b,0x80,0x0 }; int v4, v6, v7, i; v4 = 0; v6 = s.size(); v7 = 5; while (v4 < v6) { for (i = 0; i < v7 && v4 < v6; ++i) s[v4++] -= a2[i]; } for (i = 0; i < s.size(); i++) cout << (char)s[i]; }9447{you_are_an_international_mystery}
程序先进IDA看看 可见开了一段堆空间 sub_40102A函数永返回0 如果是动态debug时会进入循环结束进程 lpMem中存储乱码数据 经过sub_401000函数生成flag 但是没有生成对话框显示flag 不是debug时出现对话框显示flag 程序跑一下看看 输出乱码 动态调试 尝试运行401000函数后跳入对话框显示flag
开始是一个函数 F7步入
在这个函数出现对话框 下断点重新载入F7步入
步入继续运行看到停在MessageBox函数处
单步运行 找到判断条件IsDebuggerPresent()和102A函数
继续单步运行发现下面的跳转已实现 直接跳到MessageBox参数进栈时
所以要让跳转不实现 nop掉该je跳转 而下面的int3中断了程序 也nop掉
继续运行进程会直接结束 所以在函数结束时跳转至原MessageBox处
flag{reversing_is_not_that_hard!}
maze是迷宫的意思 所以这道题多少跟走迷宫沾点边
IDA看一看
进入main函数 首先看到输入后的第一个判断条件 长度必须是24而且以nctf{}为格式
下面是一波操作 先往后稍稍 送结果看起
看到一个数组的值不等于35(’#’)的话就跳转到Wrong Flag 从结构来看
8 * v10 + SHIWORD(v10)有点像二维数组用一维数组储存的取值方法 对应题目迷宫 可能出现一个8*8的地图
看下asc_601060的值 一个64位数组 随便写个脚本弄出地图
可以看到要从左上走到#
右下右右下下左下下下右右右右上上左左
a = " ******* * **** * **** * *** *# *** *** *** *********" index = 0 for i in range(8): f = '' for i in range(8): f += a[index] index += 1 print(f) ****** * * * *** * ** ** * ** * *# * ** *** * ** * ********再向上分析这个迷宫是怎么走的
上面有四个分支
有Oo.0四种字符
可以猜测对应上下左右了
再看看对应函数确认一下
Oo.0分别对应 左右上下
nctf{o0oo00O000oooo…OO}