题目来源至 https://www.malwaretech.com/beginner-malware-reversing-challenges
所有挑战都是在不使用调试器的情况下完成的,你的目标应该是能够在不运行exe的情况下完成每个挑战。
exe包含一个或多个未加密的flag,需要找到正确flag。
string1难度等级为1星
strings1.exe包含存储在可执行文件中的未加密flag。运行时,程序将输出flag的MD5哈希值,但不输出原始值。你能得到flag吗?
本地解压后如下:
拖进IDA里,查看下。
很明显,IDA很智能地识别了一些关键的方法,如md5_hash,猜测这就是生成flag的主方法。
call上面为push eax,入栈操作,很智能地显示了eax的类型,这里可以断定eax为md5_hash函数的参数。
mov eax, off_432294
eax的值来自一个地址,跟进此地址中,在IDA中双击此地址,来到了数据区域,发现此地址为FLAG字符串的起始地址。
那 FLAG{CAN-I-MAKE-IT-ANYMORE-OBVIOUS} 到底是不是flag呢?
在网页中进行检查
是真的flag。
string2难度等级为1星
strings2.exe包含存储在可执行文件中的未加密flag。运行时,程序将输出flag的MD5哈希值,但不输出原始值。你能得到flag吗?
本地解压后如下:
拖进IDA里,查看下,此时与string1完全不一样了。
前面是一些IDA为局部变量与参数设置的标识,先不看,来到计算hash的地方。
这里,eax依然作为md5_hash函数的参数,eax的值取自var_28的地址。
从最上面翻到eax赋值这里,发现mov操作为规律赋值,猜测可能是一个字符串或者字符数组。
右键转换为字符,查看下var_28赋值了什么,发现是大写字母F。
发现了是FLAG{STA......,猜测这就是flag。
转换完成后,将这段字符串进行检测即可 ,这里不再记录过程。
string3难度等级为2星
strings3.exe包含存储在可执行文件中的未加密flag。运行时,程序将输出flag的MD5哈希值,但不输出原始值。你能得到flag吗?
本地解压后如下:
拖进IDA里,查看下,发现与string1与string2完全不一样。
还是原先的步骤,先找到hash生成函数。
这里发现生成hash的函数使用了类的结构,因为call ??0MD5@@QAE@XZ ; MD5::MD5(void) 执行了类对象的构造方法。
重点来到 call ?digestString@MD5@@QAEPADPAD@Z ; MD5::digestString(char *)
这里是生成hash的主方法,IDA标识了eax为char* 类型,与这个方法的参数类型一致,所以判断push eax为参数入栈。
lea eax, [ebp+var_4A0]
eax的值为var_4A0变量的地址,var_4A0变量在上面的call ds:LoadStringA这个API函数中有涉及。
LoadStringA函数
从与指定模块关联的可执行文件中加载字符串资源,并将字符串复制到具有终止空字符的缓冲区中,或者返回指向字符串资源本身的只读指针。
int LoadStringA( HINSTANCE hInstance, UINT uID, LPSTR lpBuffer, int cchBufferMax );
参数
hInstance
类型:HINSTANCE
可执行文件包含字符串资源的模块实例的句柄。要获取应用程序本身的句柄,请使用NULL调用GetModuleHandle函数。
uID
输入:UINT
要加载的字符串的标识符。
lpBuffer
类型:LPTSTR
接收字符串的缓冲区(如果cchBufferMax非零)或者是字符串资源本身的只读指针(如果cchBufferMax为零)。长度必须足以容纳指针(8个字节)。
cchBufferMax
输入:int
缓冲区的大小,以字符为单位。如果字符串长于指定的字符数,则该字符串将被截断并以null结尾。如果此参数为0,则lpBuffer将接收指向字符串资源本身的只读指针。
来源: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-loadstringa
win32 API函数的调用约定为stdcall,参数从右至左入栈。
所以上面的4个push操作就是参数入栈。
cchBufferMax为3FFh,十进制为1023
lpBuffer为ecx
uID为edx
hInstance为0,也就是为NULL
lpBuffer为ecx,ecx为var_4A0变量所在的地址,而往上查,发现var_4A0已经被赋值为0,所以ecx为0。
uID为edx,edx=[ebp+var_4],而[ebp+var_4]=eax。
eax的计算是下面这几条汇编指令:
最后算得eax=272。
LoadStringA(0, 272, &var_4A0, 1023);
这里还有个快速的方法,F5反编译。直接使用IDAF5插件反编译功能翻译成伪c代码。
LoadStringA函数是加载字符串资源,所以目前需要使用另一个工具,ResourceHacker。
uID为要加载的字符串的标识符,使用ResourceHacker打开string3,来到字符串资源处。
查找下272标识符里的内容
正确flag
这些挑战利用与位置无关的代码来解密flag,弄清楚shellcode的作用并自己解密。
shellcode1.exe
包含存储在可执行文件中的flag。运行时,程序将输出flag的MD5哈希值,但不输出原始值,难度等级1星。
本地解压后:
IDA打开,如下:
还是先找一下生成hash的方法,可以确定 call ?digestString@MD5@@QAEPADPAD@Z ; MD5::digestString(char *) 为生成hash的方法。
找这个函数的参数,发现前面存在一个push操作,为 push offset Str ; "2b\n:蹥B*bb"
发现Str为一个字符串的地址
可以先将字符串当做flag输入进行检测,经过实践后,发现这里的字符串并不是真正的flag。
说明这个地址保存的字符串在前面的流程中进行了改写,我们继续往上查看汇编代码。
call [ebp+Dst]
直接调用了一个地址,首先这个地址应该是一个函数的开始地址。那么如何得到这个地址的?
继续往上看
首先往上发现了memcpy方法,属于C库函数。
void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字符到存储区 str1。
edx为str1,unk_404068为str2,0Dh为n
这里发现将unk_404068的内容复制到了Dst地址处,恰巧Dst为下面call调用的地址。
这里使用VirtualAlloc生成了一块虚拟内存,并且设置为可执行,说明[ebp+Dst]处的地址是可执行的,具体可以参考MSDN此函数的用法。
我们跟进unk_404068看看,双击unk_404068进入如下:
目前是以数据显示,猜测这里应该是代码(不然call调用了无法执行)。所以选中unk_404068,Edit 选中Code,生成代码。
call [ebp+Dst] 这里已经解决了,接着查找这个函数的输入参数。
往上发现了mov esi, [ebp+var_4],而函数内部也对这个esi寄存器进行了操作,判断这里是寄存器传递参数。
现在的问题变成了(ebp+var_4)地址处的值是什么?
首先来到第一次对[ebp+var_4]进行操作的地方,这段汇编的意思是调用HeapAlloc生成了一段堆空间,并将起始地址赋值给了
[ebp+var_4]
接着[ebp+var_4]赋值给了eax,而eax地址处的存储值变成了Str字符串的起始地址。
Str字符串地址入栈,调用了strlen C库函数。将字符串长度eax赋值给了[ecx+4]。
[ecx+4]为[ebp+var_4][1]
现在我们发现
[ebp+var_4][0]=Str[0]
[ebp+var_4][1]=eax=strlen(Str)
进一步,[ebp+var_4]目前的值为Str存储的字符串的地址。
而上面我们发现,esi的值为[ebp+var_4]的值。
所以现在进入 call [ebp+Dst] 函数里进行分析
mov esi, [ebp+var_4]
call [ebp+Dst]
call strlen
add esp, 4
mov ecx, [ebp+var_4]
mov [ecx+4], eax
esi为Str字符串的起始地址,[esi+4]为eax的值,eax的值是strlen的结果,所以是字符串的长度。
rol byte ptr [edi+ecx-1], 5
这句汇编的含义是将Str的每个字符串循环左移5位之后再放回原位,所以最后的值是Str字符串。
之后可计算出flag
FLAG{SHELLCODE-ISNT-JUST-FOR-EXPLOITS}
shellcode2.exe
包含存储在可执行文件中的flag。运行时,程序将输出flag的MD5哈希值,但不输出原始值。
按照惯例,使用IDA打开。
发现了涉及hash的函数,接着找输入参数。
push edx ; char * 这里是通过edx传入参数
lea edx, [ebp+str[0]] ;edx保存了一个字符数组的首地址。
因为之前已做过了,这里已经手工对连续排列的参数进行创建数组的处理。
猜测数组保存的应该就是输入的flag,查看上述流程里对该数组做了哪些操作。
call [ebp+Dst]
依据前面的经验,这里Dst为堆空间地址,之后通过mencpy复制了内容,所以所在的地址为代码,所以现在找到这个地址查看下,
也就是push offset sub_404040 ; Src 中的sub_404040。
双击进入,如果发现是db ... 等显示,可以按字母c转换为代码格式。
这里已经转换为代码格式,按F5让IDA自动生成伪代码,看看这个函数的作用。
这里已经重命名了很多代码,当然主要的知识点就是 LoadLibraryA与GetProcAddress。
通过这个两个API函数,可以调用系统dll里具体的方法。
v78 = LoadLibraryA(&msvcrt.dll);
v27 = LoadLibraryA(&kernel32.dll);
_GetModuleFileNameA = (void (__stdcall *)(_DWORD, char *, signed int))GetProcAddress(v27, &GetModuleFileNameA);
_fopen = (int (__cdecl *)(char *, char *))GetProcAddress(v78, &fopen);
_fseek = (void (__cdecl *)(int, signed int, _DWORD))GetProcAddress(v78, &fseek);
_fread = (void (__cdecl *)(char *, signed int, signed int, int))GetProcAddress(v78, &fread);
_fclose = (int (__cdecl *)(int))GetProcAddress(v78, &fclose);
这里加载了两个dll,之后获取了5个函数的内存实际地址。
_GetModuleFileNameA(0, &v11, 260); // v11=当前exe执行的绝对路径
ptr_file = _fopen(&v11, &rb);
_fseek(ptr_file, 78, 0); // 移动文件指针至离文件开始位置78个字节处
_fread(v79, 38, 1, ptr_file); // 读取38个字节放入v79数组中
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-7-15 11:19
被jishuzhain编辑
,原因: 附件添加