逆向工程实验Lab6

赞赏码 & 联系方式 & 个人闲话

逆向工程前言

Lab6

1、Yet another crackme (or rather keygenme).

Executables:

Linux x64

Windows x64

It is just to be run with name and serial number as command line arguments. Valid ones are:

2Z7A7-EK270-TMHR4-BHC71-CEB52-HELL0-HELL0-EONP9

2Z7A7-6I7R9-MZGO9-FDQJ3-JN0Q6-HELL0-HELL0-72KJ9

The first serial number has E feature turned off, the second has E feature turned on.

There are two tasks:

Easy: by patching, turn on/off all 5 features.

Medium: generate a serial number with arbitrary features turned on/off.

Good luck!

hint:

https://down.52pojie.cn/Tools/Debuggers/x64dbg_2018-04-05.zip

https://stackoverflow.com/questions/5475790/how-to-disassemble-the-main-function-of-a-stripped-application

 

Task1:by patching, turn on/off all 5 features.

Step1简单运行exe文件

先尝试一下题目所给的两个序列号,可以看到5个功能各自状态:

逆向工程实验Lab6_第1张图片

其中功能E在使用第1个序列号时被关闭,第2个序列号时被打开。

Step2使用IDA反编译源程序

使用IDA反编译后查看其伪码,不难找到其进行功能判断的地方:

逆向工程实验Lab6_第2张图片

在汇编码中我们也可以轻松找到其对应的部分:

逆向工程实验Lab6_第3张图片

Step3修改跳转部分的十六进制码

对于其中的JNZ,查找资料可知:

JNZ/JNE   不为零/不等于   对应16进制码:75

段内直接短转移Jmp short   (IP)←(IP)+8位位移量   对应16进制码:EB

这里我们想无条件地执行或跳过判断语句中的操作,就必须要用到无条件跳转指令JMP,并且借助其后的跳转地址来控制功能的ON/OFF。若想实现所有功能全部打开(ON),那么地址偏移应为:1400012B2- 1400012AB=7。

修改前:

逆向工程实验Lab6_第4张图片

修改后

逆向工程实验Lab6_第5张图片

Step4验证程序修改后的运行结果

逆向工程实验Lab6_第6张图片

再次执行第1个序列号,原本4个ON、1个OFF的状态已经变成全ON。

Step5:再次修改程序使所有功能关闭

若想实现所有功能全部关闭(OFF),那么地址偏移应为:0。

再次修改:

逆向工程实验Lab6_第7张图片

运行结果:

逆向工程实验Lab6_第8张图片

从运行结果可以看出,所有功能都已关闭(OFF)。

 

Task2:generate a serial number with arbitrary features turned on/off.(未做出)

要想生成打开/关闭任意功能的序列号则必须对源码的验证流程有充分的理解,仔细分析源程序,发现其在3个函数中都在不断地对序列号进行反复验证,要想完整理解透彻非常困难。具体的代码分析注释如下几幅图:

1、主函数main:

逆向工程实验Lab6_第9张图片

2、函数sub_140001480(验证2部分):

逆向工程实验Lab6_第10张图片

逆向工程实验Lab6_第11张图片

3、函数sub_140001150(验证3部分):

逆向工程实验Lab6_第12张图片

逆向工程实验Lab6_第13张图片

4、函数sub_1400010B0(在函数sub_140001150中):

逆向工程实验Lab6_第14张图片

 

逆向工程实验Lab6_第15张图片

最主要的是整个序列号都会在sub_1400010B0函数进行遍历,这一块觉得是最难的,很遗憾并没有研究清楚。所以虽然代码基本看懂,并如上图注释出来了,但是序列号背后的生成机理,特别是sub_1400010B0函数中的移位操作的实际意义并不是很理解。而由于位数太长,仅靠确定的几处去暴力破解剩下部分也基本是不可行的,很可惜第1题暂时只能到此结束,缺少继续下去的思路。

 

2、CTFx6412.exe

Step1:寻找校验函数

虽然IDA反汇编源程序后,没有看到主函数。但经过动态调试,可以找到主函数的位置。可以发现,0x140002E00这个函数被主函数调用且多达2400行,所以猜测这是校验函数。

Step2:分析校验函数

第一处校验是长度校验,要求整个输入的长度必须为30:

逆向工程实验Lab6_第16张图片

第二处校验是整个输入中‘9’的个数必须大于等于3。程序对每个输入计算FNVHASH(vc-x64标准库中std::hash的算法),将得到的哈希值和‘9’的哈希值进行比较:

逆向工程实验Lab6_第17张图片

第三处校验规定了前9个字符的取值。程序给出了前9个字符的FNV值,通过暴力破解可以得出这9个字符:

逆向工程实验Lab6_第18张图片

第四处校验规定整个输入的FNV值为5728707748789076223:

逆向工程实验Lab6_第19张图片

第五处校验规定输入中第一个9往后5个字符为系统dll的名字,第二个9和第三个9之间为该dll调用的api名称。

分析如下:

a.调用findch函数搜索输入(从第10位开始)中9所在的位置,从而以9为分界符对输入进行分割:

逆向工程实验Lab6_第20张图片

b.使用FNV算法对kernel32.dll(通过动态调试可知)的api函数进行逐个哈希。将kernel32.dll的所有api进行枚举找到匹配哈希值的api,为LoadLibraryA:

逆向工程实验Lab6_第21张图片

c.对从输入中读取的dll名字调用LoadLibraryA:

逆向工程实验Lab6_第22张图片

d.调用GetProcAddress从对应dll基址处加载从输入读取的api名字,并调用该api函数。如果返回值是负数,则注册成功:

逆向工程实验Lab6_第23张图片

逆向工程实验Lab6_第24张图片

Step3+暴力破解key

根据前面的分析,可知key的形式为{prefix}9{dllName}9{symBolName}9。前9字符的hash都已给出,只要枚举所有字符并将它们的hash值与给出hash比对就可以得出{prefix}的值。{dllName}是系统的dll,其名称长度为5,猜测应该是NTDLL。{symBolName}是{dllName}导出的api函数,它的长度为30(总长)-9({prefix})-3(分隔用的9)-5({dllName})=13。再结合key的hash为5728707748789076223i64,就可以暴力破解出key了。

代码如下:

#include 
#include   //ida的一些系统定义
#include

char test[0x100] = { 0 };  
char ans[10] = { 0 };   

//NTDLL导出的api函数
const char symbol[][0x100] = { "TppTimerpFree","TpReleaseWork","TpReleaseWait","RtlAreBitsSet","LdrpUnloadDll","LdrpSnapThunk","RtlUnlockHeap","RtlLoadString","TppTimerAlloc","EtwEventWrite","RtlStartRXact","RtlAbortRXact","TpWaitForWait","RealSuccessor","RebalanceNode","RtlGetVersion","RtlpNtOpenKey","RtlCopyString","RtlSetAllBits","RtlFreeHandle","ZwQueryObject","NtOpenProcess","NtOpenSection","ZwCreateEvent","ZwSetValueKey","NtCancelTimer","ZwAccessCheck","NtAlertThread","NtCompactKeys","NtCompressKey","ZwConnectPort","ZwCreateTimer","NtCreateToken","ZwFilterToken","ZwOpenSession","ZwQueryEaFile","ZwQueryMutant","NtRequestPort","NtSetUuidSeed","ZwStopProfile","ZwUnloadKeyEx","DbgBreakPoint","DebugService2","RtlFillMemory","RtlZeroMemory","StringCbCopyW","RtlRemoteCall","LdrpCreateKey","PfxFindPrefix","PfxInitialize","IsTimeExpired","WaitForWerSvc","WerpProcessId","DbgUiContinue","RtlpLockStack","RtlApplyRXact","TpReleasePool","TpWaitForWork","LdrReadMemory","ResCHitsFlush","RtlCreateHeap","RtlIdnToAscii","RtlpTpIoAlloc","LdrResRelease","Wow64LogPrint","NameToOrdinal","Wow64FreeHeap","whNtWriteFile","whNtReplyPort","whNtCreateKey","whNtOpenEvent","whNtDeleteKey","whNtLoadKeyEx","whNtOpenKeyEx","whNtOpenTimer","whNtRenameKey","whNtSaveKeyEx","whNtSetEaFile","whNtTestAlert","whNtUnloadKey","Wow64pLongJmp" };

//前9个字符hash值+9的hash值
signed __int64 keys[] = { -5808510693665524758i64,
-5808494200991101593i64,
-5808519489758550446i64,
-5808507395130640125i64,
-5808522788293435079i64,
-5808606351177179115i64,
0xAF63AD4C86019CAFi64,
0xAF63AC4C86019AFCi64,
0xAF63B54C8601AA47i64,
0xAF63B44C8601A894i64,0 };

//程序使用的hash函数
signed __int64 fnvHash(char* a1)
{
	char v1; // dl
	signed __int64 result; // rax
	signed __int64 v3; // rax

	v1 = *a1;
	for (result = 0xCBF29CE484222325i64; *a1; result = 0x100000001B3i64 * v3)
	{
		++a1;
		v3 = result ^ v1;
		v1 = *a1;
	}
	return result;
}

int main() {
	unsigned __int64 v183;
	LOWORD(v183) = 0;

	//求出前10个字符
	for (char x = 1; x < 127; x++) {
		LOBYTE(v183) = x;
		signed __int64 hash = fnvHash((char*)&v183);
		for (int i = 0; keys[i]; i++) {
			if (keys[i] == hash) {
				if (ans[i] == 0)ans[i] = x;
			}
		}
	}

	//遍历得出使用的api函数,并打印最终结果
	for (int i = 0; symbol[i][0]; i++) {
		if (strlen(symbol[i]) == 13) {
			strcpy(test, ans);
			strcat(test, "NTDLL9");
			strcat(test, symbol[i]);
			strcat(test, "9");
			if (5728707748789076223i64 == fnvHash(test))
				printf("Key: %s\n", test);
		}
	}
	return 1;
}

 运行结果如下:

故密钥为:KXCTF20189NTDLL9DbgUiContinue9。

Step4验证

至此可以看到控制台显示:注册成功。

 

3、某文件被加密了,最后修改时间是2019/04/11 22:10:34。猜测大概2019/04/11    22点11分之前,某一刻用户运行了genprik,生成了密钥加密文件。加密算法已知,通过分析密文,只能得出密钥第1个字节是0x25第2个字节是0x61。求完整的16个字节的密钥?

Step1用upx给genprik.exe脱壳

逆向工程实验Lab6_第25张图片

Step2使用IDA反汇编已脱壳程序

逆向工程实验Lab6_第26张图片

主函数的代码如上图所示。程序进行一个16次的循环,每次循环产生密钥的一个字节(用rand函数产生一随机数v5,然后对v5进行移位和&操作)。因为rand函数产生的随机数是和种子有关的,所以只要我们能猜出种子的值,就能破解出密钥。种子由系统当前时间和进程ID构造,我们需要破解出这两个值,即v3和PID。

Step3确定v3和PID的范围

题目提示说,2019/04/11 22点11分之前运行的程序且时间离得不是很远。那我们猜测是在2019/04/11 21:00~22:11这个时间段运行的,调用_time32函数可得出v3的取值范围为1554987616~ 1554991872。PID是进程的id号,一般来说,它不会超过100000。

Step4暴力破解密钥

已知密钥第1个字节是0x25第2个字节是0x61,把满足该条件所有可能的密钥输出。代码如下:

#include
#include 
#include   

int main() {
	__time32_t v3; 
	DWORD Seed; 
	int v5; 
	signed int v7; 

	int flag1 = 0;
	int flag2 = 0;
	int pid;

	for (pid = 0; pid < 100000; pid++) {
		for (v3 = 1554987616; v3 <= 1554991872; v3++) {
			Seed = pid ^ v3;
			srand(Seed);
			v7 = 0;
			do
			{
				v5 = rand();
				if (v7 == 0 && ((v5 >> 7) & 0xFF) == 0x25)
					flag1 = 1;
				if (v7 == 1 && ((v5 >> 7) & 0xFF) == 0x61)
					flag2 = 1;
				if (v7 == 1 && flag1 && flag2)
					printf("25 ");
				if (flag1 && flag2) 
					printf("%0X ", (v5 >> 7) & 0xFF);

				++v7;
			} while (v7 < 16);
			if (flag1 && flag2)
				printf("\n");
			flag1 = 0;
			flag2 = 0;
		}
	}
}

运行结果如下:

逆向工程实验Lab6_第27张图片

虽然跑出了很多结果,但实际上都是一样的。

所以完整的16字节密钥为:25 61 6C D5 1D D3 4B CB E7 34 97 93 A4 92 53 1F

 

你可能感兴趣的:(逆向工程,逆向工程)