CTF逆向——常规逆向篇(下)
CTF逆向——常规逆向篇(下)
题目:
- CrackMe.exe(NSCTF reverse第一题)
- WHCTF2017 reverse
- HCTF reverse(第一题)
CrackMe.exe(NSCTF reverse)
首先查壳
并没有壳,IDA打开,找到main函数,如下所示:
这里可以看到,只要sub_401005函数返回1,说明输入的flag正确,点进入查看
很简单,flag已经在这里给出了,
flag: flag{wow_flag_is_here}
WHCTF2017 reverse
首先查壳
并没有壳,拖去IDA分析,可以看到main函数如下
很简单的一个逻辑,首先将judge的每一位数据和0xC进行异或得到judge函数的原本形式,然后开始要求用户输入flag,输入的flag限制条件为:
(1)长度为14
(2)满足Judge函数的返回值为真
接下来在hex view中找到judge的十六进制数据,复制下来,写个脚本分别和0xC进行异或,再F2复制回去。
修改前
修改后
接下来就可以去查看judge的函数了,如下所示:
signed __int64 __fastcall judge(__int64 a1)
{
char v2; // [rsp+8h] [rbp-20h]
char v3; // [rsp+9h] [rbp-1Fh]
char v4; // [rsp+Ah] [rbp-1Eh]
char v5; // [rsp+Bh] [rbp-1Dh]
char v6; // [rsp+Ch] [rbp-1Ch]
char v7; // [rsp+Dh] [rbp-1Bh]
char v8; // [rsp+Eh] [rbp-1Ah]
char v9; // [rsp+Fh] [rbp-19h]
char v10; // [rsp+10h] [rbp-18h]
char v11; // [rsp+11h] [rbp-17h]
char v12; // [rsp+12h] [rbp-16h]
char v13; // [rsp+13h] [rbp-15h]
char v14; // [rsp+14h] [rbp-14h]
char v15; // [rsp+15h] [rbp-13h]
int i; // [rsp+24h] [rbp-4h]
v2 = 102;
v3 = 109;
v4 = 99;
v5 = 100;
v6 = 127;
v7 = 107;
v8 = 55;
v9 = 100;
v10 = 59;
v11 = 86;
v12 = 96;
v13 = 59;
v14 = 110;
v15 = 112;
for ( i = 0; i <= 13; ++i )
*(i + a1) ^= i;
for ( i = 0; i <= 13; ++i )
{
if ( *(i + a1) != *(&v2 + i) )
return 0LL;
}
return 1LL;
}
可以看到要令其返回1,只需要将用户输入的数据和处理后的数组一致即可,而处理数组的过程实际上就是将v2-v15分别与0-13进行异或,接下来就可以写脚本取得flag
flag = ''
c = [0x66,0x6d,0x63,0x64,0x7f,0x6b,0x37,0x64,0x3b,0x56,0x60,0x3b,0x6e,0x70]
for i in range(len(c)):
flag += chr(c[i]^i)
print flag
Flag:flag{n1c3_j0b}
HCTF reverse
首先查壳
没壳,拖进IDA,打开main函数,如图所示
跟进main_0函数,但是报错
Ok,在汇编界面找到main_0函数
选中整个main_0区域,点击u,再点击确定
再点击p,令IDA重新生成函数,接下来就可F5查看了,如下所示:
__int64 main_0()
{
int v0; // edx
__int64 v1; // ST08_8
char v3; // [esp+0h] [ebp-11Ch]
bool v4; // [esp+Fh] [ebp-10Dh]
char v5; // [esp+D7h] [ebp-45h]
int i; // [esp+E0h] [ebp-3Ch]
bool v7; // [esp+EFh] [ebp-2Dh]
bool v8; // [esp+FBh] [ebp-21h]
bool v9; // [esp+107h] [ebp-15h]
HMODULE v10; // [esp+110h] [ebp-Ch]
v10 = GetModuleHandleW(0);
sub_4113F7("Welcome to HCTF 2017nn", v3);
sub_4113F7("Mark.09 is hijacking Shinji Ikari now...nn", v3);
sub_4113F7("Check User: n", v3);
sub_411154("%s", (unsigned int)&Str);
if ( !sub_411316() )
{
sub_41114A();
exit(0);
}
sub_4111F9();
sub_4113F7("Check Start Code: n", v3);
sub_411154("%s", byte_41B4F0, 128);
while ( getchar() != 10 )
;
if ( j_strlen(byte_41B4F0) != 35 )
{
sub_411398();
sub_4110FF();
exit(0);
}
sub_41114F(&unk_41B570, byte_41B4F0);
sub_4110EB(sub_41105A, &sub_4111EA, dword_41B780, dword_41B784);
if ( sub_411361(1) )
{
sub_411398();
sub_4110FF();
exit(0);
}
v4 = sub_4110EB(sub_411023, sub_41139D, dword_41B770, dword_41B774) != 0;
v9 = v4;
sub_411023(byte_41B5F0, &unk_41B570);
sub_411258(dword_41B770, dword_41B774, 204);
if ( sub_411361(2) )
{
sub_411398();
sub_4110FF();
exit(0);
}
v4 = sub_4110EB(sub_41106E, &sub_411046, dword_41B778, dword_41B77C) != 0;
v8 = v4;
sub_41106E(byte_41B670, &unk_41B570);
sub_411258(dword_41B778, dword_41B77C, 205);
if ( sub_411361(3) )
{
sub_411398();
sub_4110FF();
exit(0);
}
v4 = sub_4110EB(sub_41105A, &sub_4111EA, dword_41B780, dword_41B784) != 0;
v7 = v4;
sub_41105A(byte_41B6F0, &unk_41B570);
sub_411258(dword_41B780, dword_41B784, 221);
for ( i = 0; i < 7; ++i )
{
byte_41B577[i] = byte_41B5F0[i];
byte_41B57E[i] = byte_41B670[i];
byte_41B585[i] = byte_41B6F0[i];
}
if ( sub_411447(&unk_41B570, &unk_41B0DC) )
{
MessageBoxA(0, "> DETONATION FUNCTIONn READY", "WILLE", 0);
sub_4113F7("[Y/N]?n", v3);
sub_411154("%c", &v5, 1);
if ( v5 != 89 && v5 != 121 )
{
sub_411398();
sub_4110FF();
}
else
{
sub_411082();
sub_4113F7("Prevent IMPACT successn", v3);
}
}
else
{
sub_411398();
sub_4110FF();
}
system("pause");
HIDWORD(v1) = v0;
LODWORD(v1) = 0;
return v1;
}
首先我们看到检查用户输入的user是否满足函数sub_411316,若满足,则继续,否则退出。点进该函数查看,如下所示:
第一个for循环的作用实际上就是将用户输入的user前后颠倒,下面是证明过程
A = A^B
B = B^A = B^(A^B) = A
A = A^B = (A^B)^A = B
然后第二个for循环对字符串进行一定的操作,然后再和v4-v14进行比较,若都一致,则说明用户输入的user正确,下面为求user的脚本
user_check = [164,169,170,190,188,185,179,169,190,216,190]
user_name = ""
for i in range(len(user_check)):
user_name += chr(user_check[i]^((((i^0x76)-52)^0x80)+43))
print user_name[::-1]
User为M.KATSURAGI
接下来回到主函数,我们看到sub_411398函数和sub_4110FF两个函数出现的特别多次,查看一下两个函数,可以知道这两个函数的作用是当用户输入错误时就通过这两个函数来弹窗,告诉用户输入错误。所以我们就可以通过避开这两个函数,找出一条通往正确结果的逻辑路径。
接下来我们来分析一下用户输入flag会经过什么操作,首先查看输入的长度是否为35
接下来查看sub_4114f函数,因为用户输入的字符串作为其参数传进去,然后它又传到了以下函数中:
可以看到它将用户输入的每一个字符的ascii码值与0x76异或,传给了地址为unk_41B570的字符串处,后面的处理都是对这个地址的字符串进行处理,所以不用再关心保存用户输入字符串的地址了。接下来紧跟着的sub_4110EB的参数中并没有unk_41B570,所以暂时不管它,我们继续跟进有使用unk_41B570这个地址的操作。
接下来,我们能看到三个几乎完全一样的部分
而且这三个部分也对地址unk_41B570处的字符串进行操作,所以我们点进去查看,首先是sub_411023函数,将unk_41B570处的字符串第7位开始后的七个字符进行一定的处理,保存在数组byte_41B5F0处
sub_41106E函数,将unk_41B570处的字符串第14位开始后的七个字符进行一定的处理,保存在数组byte_41B670处
Sub_41105A函数,将unk_41B570处的字符串第21位开始后的七个字符进行一定的处理,保存在数组byte_41B6F0处
接下来再查看下面代码
刚才的三个数组都保存到另外三个数组处,这个地址看着很熟悉,我们发现41B570刚好就是我们用户输入字符串的地址,所以上面我们分析的代码就是将用户输入字符串中第7位到第27位的字符进行一定的操作。最后再与unk_41B0DC处的字符比较是否相等,若相等,则输入的flag正确,因此我们就可以根据以上的分析,写出获取flag的脚本
a1 = [0x1E ,0x15 ,0x02 ,0x10,0x0D ,0x48 ,0x48 ]
a2 = [0x6F ,0xDD ,0xDD ,0x48,0x64 ,0x63 ,0xD7 ]
a3 = [0x2E ,0x2C ,0xFE ,0x6A,0x6D ,0x2A ,0xF2 ]
a4 = [0x6F ,0x9A ,0x4D ,0x8B,0x4B ,0x1A ,0xEA ]
a5 = [0x43 ,0x42 ,0x42 ,0x42 ,0x44 ,0x47 ,0x0B]
flag = ""
for i in range(7):
a1[i] = a1[i]^0x76
a5[i] = a5[i] ^0x76
for i in range(7):
for j in range(33, 127):
k1 = j ^ 0xad ^0x76
k1 = 2 * k1 & 0xaa | ((k1 & 0xaa) >> 1)
if k1 == a2[i]:
a2[i] = j
break
for i in range(7):
for j in range(33, 127):
k2 = j ^ 0xbe^0x76
k2 = (4 * k2 & 0xcc) | ((k2 & 0xcc) >> 2)
if k2 == a3[i]:
a3[i] = j
break
for i in range(7):
for j in range(33, 127):
k3 = j ^ 0xef^0x76
k3 = (16 * k3 & 0xf0) | ((k3 & 0xf0) >> 4)
if k3 == a4[i]:
a4[i] = j
break
for i in a1:
flag += chr(i)
for i in a2:
flag += chr(i)
for i in a3:
flag += chr(i)
for i in a4:
flag += chr(i)
for i in a5:
flag += chr(i)
print flag
这里将字符串分成了5组,其中第一和第五组与0x76异或,其它的通过爆破的方式得到正确的字符串。
Flag: hctf{>>D55_CH0CK3R_B0o0M!-87544421}
- 【学术】独热编码如何在Python中排列数据?
- 比特币的私钥【区块链生存训练】
- Unity3D学习笔记第一课
- Extjs4处理后台json数据中日期和时间的方法
- 机器学习:Python测试线性可分性的方法
- Java 机器学习库Smile实战(一)SVM
- 交易Transaction【区块链生存训练】
- 马尔可夫链文本生成的简单应用:不足20行的Python代码生成鸡汤文
- 最长递增子序列
- dedecms批量删除文档关键词可以吗
- 【学术】在C ++中使用TensorFlow训练深度神经网络
- 一个canonical标签解决site不在首页的问题
- 由一道面试题来了解进程间的通信
- 【教程】简单教程:用Python解决简单的水果分类问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 最长重复子数组 (难度:中等)-Day20200701
- Presto系列 | Presto基本介绍
- 如何在腾讯云中使用ExternalName类型的Service
- 一天一大 leet (990. 等式方程的可满足性)
- 一天一大 leet(有序矩阵中第 K 小的元素)难度:中等-Day20200702
- 一天一大 leet (126. 单词接龙 II)
- BigData--Yarn资源调度器
- 一天一大 leet(最长有效括号)难度:困难-Day20200704
- BigData--Zookeeper介绍和使用
- 一天一大 leet(通配符匹配)难度:困难-Day20200705
- 日志框架,选择Logback Or Log4j2?
- django-rest-framework配置json web token进行接口的认证
- spring中的SpEL表达式
- 京喜小程序首页无障碍优化实践
- 几种排序算法