Hgame-Final-Re

Hgame-Final-Re

shellcode_plus

这道题目的难点在于动态API的获取,因为这里所使用的加密方法就是MD5(我是通过动态调试才知道函数名称的),但是你不知道他获取的就是MD5相关的函数。但是要有反调试的话那么这个题目难度就加大了,只能静态分析他的API获取的方法。笔者在这里就是主要讲的是静态获取他的函数名称。

image-20230312165147481

动态API的获取的主要的步骤。首先dword_408148 = sub_4010B8();这个过程是获取kernel32.dll的基地址的,我们可以进去看一看

image-20230312165330464

就只有这一句,其实从这句再结合PEB表相关的内容可以知道这个是获取kernel32.dll的基地址。在这里有篇文章很清楚的介绍了这个原理PEB及LDR链,我就不过多的赘述。之后的一句dword_40814C = sub_4010CD(kernel32_addr, dword_408000);我根据后面的dword_408150 = dword_40814C(aNtdllDll);推断出这里的dword_40814C是LoadLibraryA ()函数。将指定模块加载到内存中并返回基地址,而结合这些sub_4010CD这个函数就有点像GetProcAddress ()的功能。我们进入这个函数看一看。将里面v2的结构设置为IMAGE_EXPORT_DIRECTORY因为第一句的0x3C的偏移位置指向PE文件头,0x78指向的是导出表,整个结构就会变得很清晰。

image-20230313091453366

好的逻辑清晰了起来,整个动态获取API的过程就是,将ntdll文件里面的导出函数名称用sub_401116进行加密得到一个hash值,再通过这个值将得到函数的基地址。那么我们的工作就是得到这个hash表。我们用VisualStudio自带的dumpbin.exe工具将ntdll文件里面所有的导出函数复制出来.\dumpbin /export ntdll.dll命令,一共2484条。

image-20230313092751469

将这些文件复制下来,然后再c++文件里面使用(我这里是用python生成一个c++能用的字符串数组)。然后再使用他的哈希函数生成hash值

with open("C:\\Users\\25485\\Desktop\\test.txt","rt") as f:
    lines = f.readlines()
    for line in lines:
        for index in range(len(line)-1,1,-1):
            if line[index] == " ":
                print("\""+line[index+1:-1]+"\"",end=',')
                break
'''
"A_SHAFinal","A_SHAInit","A_SHAUpdat",
'''

然后再使用c++脚本生成hash表

#include <iostream>
#include <cstdio>
#include "defs.h"
int  sub_401116(char *a1) {
	int i; // edi
	int v3; // eax

	for ( i = 0; ; i = v3 + __ROR4__(i, 13) ) {
		v3 = (unsigned __int8) * a1++;
		if ( (_BYTE)v3 == BYTE1(v3) )
			break;
	}
	return i;
}

int main() {
	/*
	  194   B9 0005C540 MD5Final
	  195   BA 0007F900 MD5Init
	  196   BB 0005C610 MD5Update
	*/
	char data[][100] = {
	"MD5Final", "MD5Init", "MD5Update"
	};
	uint32_t ans[3] = {0x561C85A7, 0x997B2150, 0x967F6C16};
	uint8_t num = 0;
	for (int i = 0; i < 2484; i++) {
		uint32_t temp = sub_401116(data[i]);
		for(uint32_t k:ans){
			if(k == temp){
				printf("%#X %s\n",k,data[i]);
				num++;
				break;
			}
		}
		if(num == 3){
			printf("查找完毕");
			break;
		}
	}
}

image-20230313093308107

这里没有把2000多个名称写出来,只写了三个。对了这里如果要使用IDA原生的hash函数的话就需要将IDA里面的defs.h头文件复制下来,还有这个文件里面用到了函数模板所以不能使用c来写这个脚本。那么得到的三个函数就是"MD5Final", "MD5Init", "MD5Update"。很明显的MD5加密,至此本题的精华就结束了。

最后的爆破脚本如下

import hashlib
enc ='40fe9ad4949331a12f5f19b477133924c04cd38aeb30f3ad1f8ab4e64a0ded7bfa290b9617a88ba01fff05847014ba19993560a792e622201090d67041f7882ea69913f66f2cfd4bd3f8ea75954ac476b90655bfbd35d2f7320894b26ebbdd0b2c3ba657da75eab82c88c429fbbf220777cbc257e66302866cf6191754c0c8e33c26ded50693926656dbaeb1c67eb876aa8743296e3ee58c31ea5d0492d6105bafc7e8a98f75755e513d9d5ead888e1dbfbec5d5556c3299d48dc888c812232d12eccbdd9b32918131341f38907cbbb5eabb18f0a40c9b3552370c9e1bc1d61eed84d589f231f3dc4203153e6fb4b4d5bfbec5d5556c3299d48dc888c812232d0cf81f9038402e85910cfad17d0051b3f3ed7234243583f554e27d349fe769c5818f9c45cfa30eeff277ef38bcbe9910ff108b961a844f859bd7c203b7366f8e5b344ac52a0192941b46a8bf252c859c1548af1c94ad45584324df8f08baf2277049c52bd1506a0845ab98fd09f39cc397d243cd9c2513d20fff6d5677b2b62bf05e43947edbff3f8b97071dd807598b5405bd07ea3eee84f8fbbbf3003ab33e925cc8d2953eba624b2bfedf91a91613eabb18f0a40c9b3552370c9e1bc1d61eb49511be6fca00a500dc7cd69ecc6c5824cafc74b88dfafb0524ecc85a76f8bdf970e2767d0cfe75876ea857f92e319b412ba0a6b8ebb926779191e75ac58b0a1051527638b9da6fe99e4242795a10eafd18772cbac19277b20dcccc1b90efb94810abe5ce246da8d68a136f15bc469fc4103f122d27677c9db144cae1394a66c4103f122d27677c9db144cae1394a66c4103f122d27677c9db144cae1394a66c4103f122d27677c9db144cae1394a66c4103f122d27677c9db144cae1394a66'
#32,
flag = ''
for index in range(0,len(enc),32):
    for i in range(0x1010,0xffff):
            #获取爆破的原文
            stri = chr((i & 0xff00) >> 8) + chr(i & 0xff)
            md5_object = hashlib.md5()    #创建一个MD5对象
            md5_object.update(stri.encode())    #添加去要加密的文本
            md5_result = md5_object.hexdigest()    #获取加密结果
            # print(md5_result)
            if md5_result == enc[index:index+32]:
                print("爆破成功")
                flag += stri
                break
print(flag)
#hgame{wind0ws_sh3l1code_rever5e_1s_very_easy_if_y0u_have_4_hash_t4ble}

double

double这个题目的考点在于linux下的ptrace调用,笔者在博客里面写了一篇关于ptrace的文章,虽然不是很好但是对这个题目也有些许帮助,那么将程序拖进IDA看看。去到main函数,首先映入眼帘的就是一个SMC。

image-20230312113211544

这段代码将位于0x4040B0处的数据与0x66异或再放入到another文件中。然后shm_open函数打开了一个共享内存区域,使得两个进程能够通过相同的名字操作同一个内存区域。往下走可以看到ftruncatemmap函数的调用。ftruncate(fd,length)会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。如果原来的文件大小比参数length大则超过的部分会被删去。返回值执行成功则返回0,失败返回-1。mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。

image-20230312114349213

那么这段代码的意思就是创建一个八字节的内存共享空间,而且是以文件映射的方式。接着就是输入字符串了。

image-20230312115450598

这一段是整个代码的核心逻辑,结合相关知识他做了那些事呢?首先调用fork()函数复制一个子进程,然后再子进程中让父进程跟踪自己然后运行another文件(不过这时的execl指令被停止了,父进程中的第二个个ptrace让他继续运行)。在父进程中首先跟踪子进程,在运行子进程,然后时sub_401230和密文的复制。在这里暴露出两个重要的信息anthoer可执行以及sub_401230函数。我们先将anthoer里面的数据dump出来,再用IDA打开。

image-20230312120448611

可以看到他将共享内存里面的数据也就是我们输入的内容放进了sub_401140里面。进入这个函数看一看,可以看到这个函数的伪代码扬了只能看汇编。

image-20230312121051696

其实看见shr 5和shl 4这种应该就容易想到TEA加密了。这里的信息已经结束的差不多了,只知道这里是对原文进行加密的地方。好的,返回到原来那个文件的sub_401230函数里面。

image-20230312122919599

首先父进程就是等待子进程停止下来,然而我们在前面附加跟踪之后不是让子进程继续运行了吗?他应该顺利执行了another里面的内容呀,不错确实如此,不过再another里面有int 3断电,是他让子进程停下来的。

image-20230312123240524

image-20230312123335498

一共就两个int 3断点,而且地址的位置正好对印着两个if判断语句。我们首先来看看第一个断电,首先第一次是CC所以不会跳到break语句而是往下执行。继续执行的第一件事就是将CC改为55,也就是取消断点,之后就不会再在这里停止下来,也不会执行后面的事情。第二件事就是将此时的rsi所指向的地址的数据改为这里的xmmword_402010,一共是16字节。那么rsi里面的是什么呢?我们知道64位程序的参数传递是rdi,rsi,rdx,rcx,r8,r9。也就是说他改变了函数调用的第二个参数。也就是对应在加密函数sub_401140里面的第二个参数。同样的逻辑第二个断点处,程序先将CC改为81(那么之后也不会继续在这里断下来),然后修改rdx寄存器里面的值为0xDEADBEEF。我们将这两个CC按他的意思改过来看看。

image-20230312124745358

惊奇的发现这个函数的伪代码出来了,很明显的TEA加密。不过这里的TEA是魔改过后的。而魔改的逻辑就在我们刚刚分析的那两个断点那里,我们将伪代码和汇编代码结合一起看。

image-20230312125110878

上面刚刚说过他将这个函数里面的第二个参数修改了,也就是TEA里面的k数组。

image-20230312125300482

第二个断点是在0x40192这里,这里是sum+=delta的时候,但是他在这里直接将sum置为0xDEADBEEF。那么我们就可以了解到这个魔改之后的TEA,首先k数组改变了,其次32轮加密的第一轮加密加的值不是0x9E3779B9而是0xDEADBEEF,他的31次是原来的值。因为k修改的是rsi指向的内存地址,而这个是通过修改寄存器的值来的,之后寄存器的值还是会加0x9E3779B9。只不过第一次CC断住了并且修改了他的值。魔改之后的TEA加密和解密如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
void encrypt(uint32_t* v, uint32_t* k) {
	uint32_t v0 = v[0], v1 = v[1], sum = 0x9E3779B9, i;
	uint32_t delta1 = 0xDEADBEEF;
	uint32_t delta2 = 0x9E3779B9;
	uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
	for (i = 0; i < 32; i++) {
		v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
		v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
		if(i == 0) sum += delta1;
		else sum += delta2;
	}
//	printf("%#x ",sum);
	v[0] = v0;
	v[1] = v1;
}
void decrypt(uint32_t* v, uint32_t* k) {
	uint32_t v0 = v[0], v1 = v[1], sum = 0xa59cf60f, i;
	uint32_t delta1 = 0xDEADBEEF;
	uint32_t delta2 = 0x9E3779B9;
	uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
	for (i = 0; i<32; i++) {
		if(i == 31) sum -= delta1;
		else sum -= delta2;
		v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
		v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
	}
	v[0] = v0; v[1] = v1;
}

int main(){
	uint32_t k[4] = {0xF10590EC, 0xA78F4AFA, 0x3971C0E9, 0xD15DAD97};
	uint32_t v[12] = {0x381EBD07, 0x8652465C, 0x4F062ABC, 0x78AF940B, 0xE5E5A4B8, 0x1C942AD4, 0x81B27D43, 0x4D12CB48, 
		0xB1431A8B, 0x23AE1460, 0xAE448739, 0x5DC1DEBD};
	for(int i = 10;i>=0;i-=2){
		decrypt(v + i,k);
	}
	char *p;
	p = &v;
	for(int i=0;i < 48;i++){
		printf("%c",p[i]);
	}	
}
//hgame{d0uble_proc3sses_4re_d1ffcult_to_d3bug}
posted @ 2023-03-13 09:51  比翼飞  阅读(73)  评论(0编辑  收藏  举报