如何在Windows XP/7中查找内存中的字节序列?
问题:我正在做一个学校的项目(我自己选择的),这个项目是一个程序加载器/DLL注入器。这个想法最初是我在这里找到的,然后我根据自己的需求进行了修改,并把DLL中的汇编部分转换成了可以用GCC编译的扩展汇编,而不是Visual Studio。与其在控制台窗口中打印弹球得分,我加载了一个我自己写的程序,这个程序可以接收用户输入并将其写入文件。加载器会注入一个DLL,这个DLL里面有一个函数,可以把原本要写入文件的用户输入重定向到一个消息框,并且会把我自己的字符串写入文件。
在我的机器上这个程序是能工作的,但我对平台切换有些担心,因为我的教授需要在她的机器上编译这个项目,所以在她的机器上,地址0x004014A6可能不会包含写入文件的指令CALL <某个地址>
(实际代码是:ofile << user_input;
),而是仍然会有一个调用那个写字符串到文件的函数的指令。
我想做的是动态确定那个地址,而不是硬编码一个固定的地址。我觉得我可以通过使用GetProcAddress来获取被调用函数的地址,然后创建一个数组来保存表示CALL <那个函数>
的字节,并在我预期找到那个调用的内存附近逐字节搜索,找到地址后再进行后续操作。
不过,我不太清楚具体该怎么做。
主要问题:我该如何扫描一段内存地址,并将内容与数组中的元素进行比较?
换句话说,我想在我的DLL中包含一个函数,这个函数可以读取内存中任意地址的字节,并将其与预期的字节序列进行比较。我该如何在某个进程中任意读取内存地址的内容?
怀疑:我需要知道原始程序执行的起始和结束地址。我该如何获取起始和结束地址之间的范围?(这似乎是这里真正的难点。我可能只需要知道如何获取进程的起始和结束地址,就能解决其他问题。)
2 个回答
如果你在用Python,可能可以试试ptrace。它是跨平台的,可以通过pip安装。这里有一段我在网上找到的Unix系统的代码示例:http://sixserv.org/2010/07/26/memory-debugging-or-a-universal-game-trainer-with-python-and-ptrace/
def search_memory_locations(pid, max_memory, search_value):
child_pid = os.fork()
if child_pid == 0: # search within forked process:
locations = list()
prev_locations = read_locations()
dbg = PtraceDebugger()
process = dbg.addProcess(pid, False)
memory_mappings = readProcessMappings(process)
print "\x1B[?25l", # deactivate cursor (^_^)
for memory_mapping in memory_mappings:
# only search in read/writable memory areas within range...
if "rw" in memory_mapping.permissions and memory_mapping.end <= max_memory:
for loc in range(memory_mapping.start, memory_mapping.end):
value = process.readBytes(loc, 1)
if value[0] == search_value:
print "search memory area[0x%08X-0x%08X] address[0x%08X] value[0x%02X (%03d)] \r" % (memory_mapping.start, memory_mapping.end, loc, ord(value), ord(value)),
if prev_locations and len(prev_locations) > 0 and not loc in prev_locations:
continue # skip prev not found locations
locations.append(loc)
print "\x1B[?25h", # activate cursor
dbg.quit()
write_locations(locations)
sys.exit()
return child_pid # don't really need this
如果你在用C/C++,这里有一个我之前在ITH上用过的专门针对Windows的函数:http://code.google.com/p/interactive-text-hooker/
DWORD SearchPattern(DWORD base, DWORD base_length, LPVOID search, DWORD search_length) //KMP
{
__asm
{
mov eax,search_length
alloc:
push 0
sub eax,1
jnz alloc
mov edi,search
mov edx,search_length
mov ecx,1
xor esi,esi
build_table:
mov al,byte ptr [edi+esi]
cmp al,byte ptr [edi+ecx]
sete al
test esi,esi
jz pre
test al,al
jnz pre
mov esi,[esp+esi*4-4]
jmp build_table
pre:
test al,al
jz write_table
inc esi
write_table:
mov [esp+ecx*4],esi
inc ecx
cmp ecx,edx
jb build_table
mov esi,base
xor edx,edx
mov ecx,edx
matcher:
mov al,byte ptr [edi+ecx]
cmp al,byte ptr [esi+edx]
sete al
test ecx,ecx
jz match
test al,al
jnz match
mov ecx, [esp+ecx*4-4]
jmp matcher
match:
test al,al
jz pre2
inc ecx
cmp ecx,search_length
je finish
pre2:
inc edx
cmp edx,base_length //search_length
jb matcher
mov edx,search_length
dec edx
finish:
mov ecx,search_length
sub edx,ecx
lea eax,[edx+1]
lea ecx,[ecx*4]
add esp,ecx
}
}
如果你要使用的程序版本不久后会改变,你可以通过以下方式获取一个在不同系统下都能用的地址:虚拟地址(VA)等于相对虚拟地址(RVA)加上模块的基地址(这个可以通过 GetModuleHandle
获取)。如果版本会改变,那你就需要用到签名扫描器。不过,签名扫描器并不是万无一失的,因为你要扫描的模式可能会在不同版本之间大幅变化。你需要的任何范围数据都可以从目标应用的PE文件中获取,这样就可以临时给 .code
部分赋予读取权限,让你能高效地遍历它(不那么高效的方法是使用 ReadProcessMemory
)。这里有一个小的签名扫描器库,下面还有一个简单签名扫描器的源代码链接:http://www.blizzhackers.cc/viewtopic.php?f=182&t=478228&sid=55fc9a949aa0beb2ca2fb09e933210de