逆向工程实验——pre5(指令集和MD5破解)

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 位的密码,验证了我们的结果。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值