文章目录
1、阅读 PE文件格式全接触
https://bbs.pediy.com/thread-22892.htm
PE文件格式被组织为一个线性的数据流。开始的是MS-DOS头,然后是实模式的程序根,再就是PE文件签名,紧随其后的便是PE文件头和可选头。在这之后,出现的是所有的节头,再跟着的就是所有节的节身。文件常以一些其它方面的杂项信息,包括重定位信息、符号表信息、行数信息以及字串表数据等作为结尾。
2、阅读 从reflector实现看.net的混淆与反混淆技术
https://www.pediy.com/kssd/pediy08/pediy8-523.htm
作者主要是想利用高级语言编译器的优化能力来实现反混淆,因为要利用高级语言编译器,所以首先得将IL反编译成高级语言语法。
switch的处理主要是考虑用switch跳转表作goto的这种情况,reflector里就有很多这样的,在文中给出的变换就是将这种goto直接还原,之后的工作由C++编译器来优化。
后面说的程序流图不是有许多现成工具会生成,而是说用程序流图来识别循环与
来回跳转的优化是多数编译器的基本优化功能。
反编译工具的主要思路是将IL等价变换为高级语言,主要是考虑到现有的反编译工具多数不能正确处理基于堆栈的混淆,重点也是解决这个问题,至于来回跳转那些混淆,编译优化能很好处理,所以作者在反编译时不处理。
反编译工具的工作过程:
1、对IL文件进行词法、语法分析,提取名空间、类、方法、方法实现指令的信息;
2、建立类之间的依赖关系图(因为我要生成的C++,必须自己解决交叉引用问题,若是C#则不需要建立这个);
3、对方法实现部分建立程序流图,异常块表;
4、对流图的每个基本块进行模拟IL执行(主要是堆栈的操作),生成计算表达式,临时变量,异常块间跳转的goto目标调整,用switch跳转表实现的goto的调整等;
5、将基本块间堆栈传递的数据记录为临时变量;
6、输出最终C++代码。
3、(选做)What does the following code do?
csc .NET compiler (MSVS 2010), ildasm output
.method public hidebysig static uint8 f(uint8 a) cil managed
{
// Code size 36 (0x24)
.maxstack 2
.locals init (uint8 V_0)
IL_0000: nop //未执行任何有意义的操作
IL_0001: ldarg.0 //将索引为 0 的参数加载到计算堆栈上
IL_0002: conv.u8 //将位于计算堆栈顶部的值转换为 unsigned int64,然后将其扩展为 int64
IL_0003: ldc.i8 0x202020202 //将所提供的 int64 类型的值作为 int64 推送到计算堆栈上
IL_000c: mul //将两个值相乘并将结果推送到计算堆栈上
IL_000d: ldc.i8 0x10884422010 //将所提供的 int64 类型的值作为 int64 推送到计算堆栈上
IL_0016: and //计算两个值的按位“与”并将结果推送到计算堆栈上
IL_0017: ldc.i4 0x3ff //将所提供的 int32 类型的值作为 int32 推送到计算堆栈上
IL_001c: conv.i8 //将位于计算堆栈顶部的值转换为 int64
IL_001d: rem //将两个值相除并将余数推送到计算堆栈上
IL_001e: conv.u1 //将位于计算堆栈顶部的值转换为 unsigned int8,然后将其扩展为 int32。
IL_001f: stloc.0 //从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中
IL_0020: br.s IL_0022 //无条件地将控制转移到目标指令(短格式)
IL_0022: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上
IL_0023: ret //返回
} // end of method e25::f
假设最初的八位二进制为ABCDEFGH(仅表示位置,值都为0或1),乘完0x202020202后变为ABCDEFGH ABCDEFGH ABCDEFGH ABCDEFGH ABCDEFGH 0,再与0x10884422010按位与后,得到A0000F000 B0000G000 C0000H000 D0000000 0E0000,再求对1023的余数。这里采用对1023求余等于除以1024的商+余数再对1023求余,然后递归,最后得到余数为HGFEDCBA.故这段代码的作用是将一个数逆序。
一样的作用,只不过换成了Java的字节码:
Java 1.8 compiler:
public static byte f(byte);
descriptor: (B)B
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=1
0: iload_0 //第一个int型局部变量进栈
1: i2l //栈顶int值强转long值,并且结果进栈
2: ldc2_w #2 // long 8623620610l(最后这个是字母l) //将long或double型常量值从常量池中推送至栈顶(宽索引)
5: lmul //栈顶两long型数值相乘,并且结果进栈
6: ldc2_w #4 // long 1136090292240l //将long或double型常量值从常量池中推送至栈顶(宽索引)
9: land //栈顶两long型数值按位与,并且结果进栈
10: ldc2_w #6 // long 1023l //将long或double型常量值从常量池中推送至栈顶(宽索引)
13: lrem //栈顶两long型数值作取模运算,并且结果进栈
14: l2i //栈顶long值强转int值,并且结果进栈
15: i2b //栈顶int值强转byte值,并且结果进栈
16: ireturn //当前方法返回int
4、shadow文件的MD5密码破解
root:$1$abcde$LULigrJwcdszq2ReOX7bG/:15933:0:99999:7:::
hint:密码长度不超过6
root:$1$abcde$LULigrJwcdszq2ReOX7bG/:15933:0:99999:7:::
这是在linux下的shadow文件中,用来存放用户的账户和密码,在/etc/shadow目录下。
其内容用“:”号隔开,分别表示不同的内容:
1)“登录名”(root):是与/etc/passwd文件中的登录名相一致的用户账号。
2)“口令”($1$abcde$LULigrJwcdszq2ReOX7bG/
):字段存放的是加密后的用户口令字,如果为空,则对应用户没有口令,登录时不需要口令;星号代表帐号被锁定;双叹号表示这个密码已经过期了。
$6开头的,表明是用SHA-512加密的,$1表明是用MD5加密的,$2是用Blowfish加密的,$5是用 SHA-256加密的;
$abcde表示加密算法所加的盐值为abcde;
$LULigrJwcdszq2ReOX7bG/表示加密算法得到的密文为LULigrJwcdszq2ReOX7bG/。
3)“最后一次修改时间”(15933):表示的是从某个时刻起,到用户最后一次修改口令时的天数,时间起点对不同的系统可能不一样,例如在SCOLinux中,这个时间起点是1970年1月1日。
4)“最小时间间隔”(0):指的是两次修改口令之间所需的最小天数。
5)“最大时间间隔”(99999):指的是口令保持有效的最大天数。
6)“警告时间”(7):字段表示的是从系统开始警告用户到用户密码正式失效之间的天数。
7)“不活动时间”():表示的是用户没有登录活动但账号仍能保持有效的最大天数。
8)“失效时间”():字段给出的是一个绝对的天数,如果使用了这个字段,那么就给出相应账号的生存期。期满后,该账号就不再是一个合法的账号,也就不能再用来登录了。
9)保留字段()
所以题目是要我们破解Linux下shadow文件的密码,其中的关键是$1$abcde$LULigrJwcdszq2ReOX7bG/
,表示盐值为abcde的MD5加密的密文LULigrJwcdszq2ReOX7bG/,我们需要解出明文密码。
一、直接代码暴力破解
descarck.c
//编译选项:gcc -O3 descrack.c -lcrypt -o descrack
#define _XOPEN_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
#include <stdio.h>
#include <crypt.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
char data[37] = "0123456789abcdefghijklmnopqrstuvwxyz";
//待解密的密文
char * goalPass = "$1$abcde$LULigrJwcdszq2ReOX7bG/";
//存储遍历的明文
char mypwd[10];
//最短的密码长度
int minlen = 1;
//最长的密码长度
int maxlen = 6;
//当前时间
time_t global_start;
void subgenerate(int index, int pwdlen)
{
if (index == pwdlen)
return;
int i;
for (i = 0; i < 36; i++)
{
mypwd[index] = data[i];
memset(mypwd + index + 1, data[0], pwdlen- index -1);
if (i != 0)
{
//printf("%s ",mypwd);
//strcmp:相等则返回0,!0让程序输出退出
if (!strcmp(goalPass, crypt(mypwd, "$1$abcde$")))
{
printf("当前程序花费的时间: %ld (秒)\n",
(time(NULL) - global_start));
printf("解密后的明文密码是:%s\n", mypwd);
exit(0);
}
}
subgenerate(index + 1, pwdlen);
}
}
void generate(int pwdlen, int start, int end)
{
int i;
// 多线程可分段
for (i = start; i < end; i++)
{
mypwd[0] = data[i];
//填充长度
memset(mypwd + 1, data[0], pwdlen-1);
//printf("%s ",mypwd);
if (!strcmp(goalPass, crypt(mypwd, "$1$abcde$")))
{
printf("当前程序花费的时间: %ld (秒)\n",
(time(NULL) - global_start));
printf("解密后的明文密码是:%s\n", mypwd);
exit(0);
}
subgenerate(1, pwdlen);
}
}
int main()
{
//赋值为当前时间
global_start = time(NULL);
char mypwd[10];
int i,threadnum = 10;
for (i = minlen; i <= maxlen; i++)
{
printf("当前破解密码的长度:%d\n", i);
//password length
memset(mypwd, 0, 10);
//留作多线程
generate(i,0,36);
printf("当前程序花费的时间: %ld (秒)\n",
(time(NULL) - global_start));
}
puts("在指定的范围内没有找到正确的密码");
return 0;
}
运行结果:
得到解密后的明文密码123qwe,大概需要32005秒也就是8个小时53分25秒。
二、工具破解(John the Ripper)
John the Ripper的安装过程可以参考我的另一篇文章:Bugku-加密-Crack it(shadow文件解密)
根据提示下载 John 并拷贝到虚拟机
进 入 src 目 录 , 使 用 make 指 令 查 看 可 安 装 的 系 统
选择第七个 linux-x86-mmx,安装完成后,退出 src 并进入 run 目录,在 run 目录下新建文件,把我们要破解的内容放进去,这里我将它命名为 root:
文件创建完成后就可以使用 John 破解了,先进入root权限(不然会提示权限不够),输入./john root 指令
1秒钟就得到了结果,破解结果也是123qwe,的确是不超过 6 位的密码,验证了我们的结果。