metasm 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/BUGS +11 -0
- data/CREDITS +17 -0
- data/README +270 -0
- data/TODO +114 -0
- data/doc/code_organisation.txt +146 -0
- data/doc/const_missing.txt +16 -0
- data/doc/core_classes.txt +75 -0
- data/doc/feature_list.txt +53 -0
- data/doc/index.txt +59 -0
- data/doc/install_notes.txt +170 -0
- data/doc/style.css +3 -0
- data/doc/use_cases.txt +18 -0
- data/lib/metasm.rb +80 -0
- data/lib/metasm/arm.rb +12 -0
- data/lib/metasm/arm/debug.rb +39 -0
- data/lib/metasm/arm/decode.rb +167 -0
- data/lib/metasm/arm/encode.rb +77 -0
- data/lib/metasm/arm/main.rb +75 -0
- data/lib/metasm/arm/opcodes.rb +177 -0
- data/lib/metasm/arm/parse.rb +130 -0
- data/lib/metasm/arm/render.rb +55 -0
- data/lib/metasm/compile_c.rb +1457 -0
- data/lib/metasm/dalvik.rb +8 -0
- data/lib/metasm/dalvik/decode.rb +196 -0
- data/lib/metasm/dalvik/main.rb +60 -0
- data/lib/metasm/dalvik/opcodes.rb +366 -0
- data/lib/metasm/decode.rb +213 -0
- data/lib/metasm/decompile.rb +2659 -0
- data/lib/metasm/disassemble.rb +2068 -0
- data/lib/metasm/disassemble_api.rb +1280 -0
- data/lib/metasm/dynldr.rb +1329 -0
- data/lib/metasm/encode.rb +333 -0
- data/lib/metasm/exe_format/a_out.rb +194 -0
- data/lib/metasm/exe_format/autoexe.rb +82 -0
- data/lib/metasm/exe_format/bflt.rb +189 -0
- data/lib/metasm/exe_format/coff.rb +455 -0
- data/lib/metasm/exe_format/coff_decode.rb +901 -0
- data/lib/metasm/exe_format/coff_encode.rb +1078 -0
- data/lib/metasm/exe_format/dex.rb +457 -0
- data/lib/metasm/exe_format/dol.rb +145 -0
- data/lib/metasm/exe_format/elf.rb +923 -0
- data/lib/metasm/exe_format/elf_decode.rb +979 -0
- data/lib/metasm/exe_format/elf_encode.rb +1375 -0
- data/lib/metasm/exe_format/macho.rb +827 -0
- data/lib/metasm/exe_format/main.rb +228 -0
- data/lib/metasm/exe_format/mz.rb +164 -0
- data/lib/metasm/exe_format/nds.rb +172 -0
- data/lib/metasm/exe_format/pe.rb +437 -0
- data/lib/metasm/exe_format/serialstruct.rb +246 -0
- data/lib/metasm/exe_format/shellcode.rb +114 -0
- data/lib/metasm/exe_format/xcoff.rb +167 -0
- data/lib/metasm/gui.rb +23 -0
- data/lib/metasm/gui/cstruct.rb +373 -0
- data/lib/metasm/gui/dasm_coverage.rb +199 -0
- data/lib/metasm/gui/dasm_decomp.rb +369 -0
- data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
- data/lib/metasm/gui/dasm_graph.rb +1354 -0
- data/lib/metasm/gui/dasm_hex.rb +543 -0
- data/lib/metasm/gui/dasm_listing.rb +599 -0
- data/lib/metasm/gui/dasm_main.rb +906 -0
- data/lib/metasm/gui/dasm_opcodes.rb +291 -0
- data/lib/metasm/gui/debug.rb +1228 -0
- data/lib/metasm/gui/gtk.rb +884 -0
- data/lib/metasm/gui/qt.rb +495 -0
- data/lib/metasm/gui/win32.rb +3004 -0
- data/lib/metasm/gui/x11.rb +621 -0
- data/lib/metasm/ia32.rb +14 -0
- data/lib/metasm/ia32/compile_c.rb +1523 -0
- data/lib/metasm/ia32/debug.rb +193 -0
- data/lib/metasm/ia32/decode.rb +1167 -0
- data/lib/metasm/ia32/decompile.rb +564 -0
- data/lib/metasm/ia32/encode.rb +314 -0
- data/lib/metasm/ia32/main.rb +233 -0
- data/lib/metasm/ia32/opcodes.rb +872 -0
- data/lib/metasm/ia32/parse.rb +327 -0
- data/lib/metasm/ia32/render.rb +91 -0
- data/lib/metasm/main.rb +1193 -0
- data/lib/metasm/mips.rb +11 -0
- data/lib/metasm/mips/compile_c.rb +7 -0
- data/lib/metasm/mips/decode.rb +253 -0
- data/lib/metasm/mips/encode.rb +51 -0
- data/lib/metasm/mips/main.rb +72 -0
- data/lib/metasm/mips/opcodes.rb +443 -0
- data/lib/metasm/mips/parse.rb +51 -0
- data/lib/metasm/mips/render.rb +43 -0
- data/lib/metasm/os/gnu_exports.rb +270 -0
- data/lib/metasm/os/linux.rb +1112 -0
- data/lib/metasm/os/main.rb +1686 -0
- data/lib/metasm/os/remote.rb +527 -0
- data/lib/metasm/os/windows.rb +2027 -0
- data/lib/metasm/os/windows_exports.rb +745 -0
- data/lib/metasm/parse.rb +876 -0
- data/lib/metasm/parse_c.rb +3938 -0
- data/lib/metasm/pic16c/decode.rb +42 -0
- data/lib/metasm/pic16c/main.rb +17 -0
- data/lib/metasm/pic16c/opcodes.rb +68 -0
- data/lib/metasm/ppc.rb +11 -0
- data/lib/metasm/ppc/decode.rb +264 -0
- data/lib/metasm/ppc/decompile.rb +251 -0
- data/lib/metasm/ppc/encode.rb +51 -0
- data/lib/metasm/ppc/main.rb +129 -0
- data/lib/metasm/ppc/opcodes.rb +410 -0
- data/lib/metasm/ppc/parse.rb +52 -0
- data/lib/metasm/preprocessor.rb +1277 -0
- data/lib/metasm/render.rb +130 -0
- data/lib/metasm/sh4.rb +8 -0
- data/lib/metasm/sh4/decode.rb +336 -0
- data/lib/metasm/sh4/main.rb +292 -0
- data/lib/metasm/sh4/opcodes.rb +381 -0
- data/lib/metasm/x86_64.rb +12 -0
- data/lib/metasm/x86_64/compile_c.rb +1025 -0
- data/lib/metasm/x86_64/debug.rb +59 -0
- data/lib/metasm/x86_64/decode.rb +268 -0
- data/lib/metasm/x86_64/encode.rb +264 -0
- data/lib/metasm/x86_64/main.rb +135 -0
- data/lib/metasm/x86_64/opcodes.rb +118 -0
- data/lib/metasm/x86_64/parse.rb +68 -0
- data/misc/bottleneck.rb +61 -0
- data/misc/cheader-findpppath.rb +58 -0
- data/misc/hexdiff.rb +74 -0
- data/misc/hexdump.rb +55 -0
- data/misc/metasm-all.rb +13 -0
- data/misc/objdiff.rb +47 -0
- data/misc/objscan.rb +40 -0
- data/misc/pdfparse.rb +661 -0
- data/misc/ppc_pdf2oplist.rb +192 -0
- data/misc/tcp_proxy_hex.rb +84 -0
- data/misc/txt2html.rb +440 -0
- data/samples/a.out.rb +31 -0
- data/samples/asmsyntax.rb +77 -0
- data/samples/bindiff.rb +555 -0
- data/samples/compilation-steps.rb +49 -0
- data/samples/cparser_makestackoffset.rb +55 -0
- data/samples/dasm-backtrack.rb +38 -0
- data/samples/dasmnavig.rb +318 -0
- data/samples/dbg-apihook.rb +228 -0
- data/samples/dbghelp.rb +143 -0
- data/samples/disassemble-gui.rb +102 -0
- data/samples/disassemble.rb +133 -0
- data/samples/dump_upx.rb +95 -0
- data/samples/dynamic_ruby.rb +1929 -0
- data/samples/elf_list_needed.rb +46 -0
- data/samples/elf_listexports.rb +33 -0
- data/samples/elfencode.rb +25 -0
- data/samples/exeencode.rb +128 -0
- data/samples/factorize-headers-elfimports.rb +77 -0
- data/samples/factorize-headers-peimports.rb +109 -0
- data/samples/factorize-headers.rb +43 -0
- data/samples/gdbclient.rb +583 -0
- data/samples/generate_libsigs.rb +102 -0
- data/samples/hotfix_gtk_dbg.rb +59 -0
- data/samples/install_win_env.rb +78 -0
- data/samples/lindebug.rb +924 -0
- data/samples/linux_injectsyscall.rb +95 -0
- data/samples/machoencode.rb +31 -0
- data/samples/metasm-shell.rb +91 -0
- data/samples/pe-hook.rb +69 -0
- data/samples/pe-ia32-cpuid.rb +203 -0
- data/samples/pe-mips.rb +35 -0
- data/samples/pe-shutdown.rb +78 -0
- data/samples/pe-testrelocs.rb +51 -0
- data/samples/pe-testrsrc.rb +24 -0
- data/samples/pe_listexports.rb +31 -0
- data/samples/peencode.rb +19 -0
- data/samples/peldr.rb +494 -0
- data/samples/preprocess-flatten.rb +19 -0
- data/samples/r0trace.rb +308 -0
- data/samples/rubstop.rb +399 -0
- data/samples/scan_pt_gnu_stack.rb +54 -0
- data/samples/scanpeexports.rb +62 -0
- data/samples/shellcode-c.rb +40 -0
- data/samples/shellcode-dynlink.rb +146 -0
- data/samples/source.asm +34 -0
- data/samples/struct_offset.rb +47 -0
- data/samples/testpe.rb +32 -0
- data/samples/testraw.rb +45 -0
- data/samples/win32genloader.rb +132 -0
- data/samples/win32hooker-advanced.rb +169 -0
- data/samples/win32hooker.rb +96 -0
- data/samples/win32livedasm.rb +33 -0
- data/samples/win32remotescan.rb +133 -0
- data/samples/wintrace.rb +92 -0
- data/tests/all.rb +8 -0
- data/tests/dasm.rb +39 -0
- data/tests/dynldr.rb +35 -0
- data/tests/encodeddata.rb +132 -0
- data/tests/ia32.rb +82 -0
- data/tests/mips.rb +116 -0
- data/tests/parse_c.rb +239 -0
- data/tests/preprocessor.rb +269 -0
- data/tests/x86_64.rb +62 -0
- metadata +255 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
require 'metasm/exe_format/main'
|
|
8
|
+
require 'metasm/exe_format/mz'
|
|
9
|
+
require 'metasm/exe_format/coff'
|
|
10
|
+
|
|
11
|
+
module Metasm
|
|
12
|
+
class PE < COFF
|
|
13
|
+
MAGIC = "PE\0\0" # 0x50450000
|
|
14
|
+
|
|
15
|
+
attr_accessor :coff_offset, :signature, :mz
|
|
16
|
+
|
|
17
|
+
def initialize(*a)
|
|
18
|
+
super(*a)
|
|
19
|
+
cpu = a.grep(CPU).first
|
|
20
|
+
@mz = MZ.new(cpu).share_namespace(self)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# overrides COFF#decode_header
|
|
24
|
+
# simply sets the offset to the PE pointer before decoding the COFF header
|
|
25
|
+
# also checks the PE signature
|
|
26
|
+
def decode_header
|
|
27
|
+
@cursection ||= self
|
|
28
|
+
@encoded.ptr = 0x3c
|
|
29
|
+
@encoded.ptr = decode_word(@encoded)
|
|
30
|
+
@signature = @encoded.read(4)
|
|
31
|
+
raise InvalidExeFormat, "Invalid PE signature #{@signature.inspect}" if @signature != MAGIC
|
|
32
|
+
@coff_offset = @encoded.ptr
|
|
33
|
+
if @mz.encoded.empty?
|
|
34
|
+
@mz.encoded << @encoded[0, @coff_offset-4]
|
|
35
|
+
@mz.encoded.ptr = 0
|
|
36
|
+
@mz.decode_header
|
|
37
|
+
end
|
|
38
|
+
super()
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# creates a default MZ file to be used in the PE header
|
|
42
|
+
# this one is specially crafted to fit in the 0x3c bytes before the signature
|
|
43
|
+
def encode_default_mz_header
|
|
44
|
+
# XXX use single-quoted source, to avoid ruby interpretation of \r\n
|
|
45
|
+
@mz.cpu = Ia32.new(386, 16)
|
|
46
|
+
@mz.assemble <<'EOMZSTUB'
|
|
47
|
+
db "Needs Win32!\r\n$"
|
|
48
|
+
.entrypoint
|
|
49
|
+
push cs
|
|
50
|
+
pop ds
|
|
51
|
+
xor dx, dx ; ds:dx = addr of $-terminated string
|
|
52
|
+
mov ah, 9 ; output string
|
|
53
|
+
int 21h
|
|
54
|
+
mov ax, 4c01h ; exit with code in al
|
|
55
|
+
int 21h
|
|
56
|
+
EOMZSTUB
|
|
57
|
+
|
|
58
|
+
mzparts = @mz.pre_encode
|
|
59
|
+
|
|
60
|
+
# put stuff before 0x3c
|
|
61
|
+
@mz.encoded << mzparts.shift
|
|
62
|
+
raise 'OH NOES !!1!!!1!' if @mz.encoded.virtsize > 0x3c # MZ header is too long, cannot happen
|
|
63
|
+
until mzparts.empty?
|
|
64
|
+
break if mzparts.first.virtsize + @mz.encoded.virtsize > 0x3c
|
|
65
|
+
@mz.encoded << mzparts.shift
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# set PE signature pointer
|
|
69
|
+
@mz.encoded.align 0x3c
|
|
70
|
+
@mz.encoded << encode_word('pesigptr')
|
|
71
|
+
|
|
72
|
+
# put last parts of the MZ program
|
|
73
|
+
until mzparts.empty?
|
|
74
|
+
@mz.encoded << mzparts.shift
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ensure the sig will be 8bytes-aligned
|
|
78
|
+
@mz.encoded.align 8
|
|
79
|
+
|
|
80
|
+
@mz.encoded.fixup 'pesigptr' => @mz.encoded.virtsize
|
|
81
|
+
@mz.encoded.fixup @mz.encoded.binding
|
|
82
|
+
@mz.encoded.fill
|
|
83
|
+
@mz.encode_fix_checksum
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# encodes the PE header before the COFF header, uses a default mz header if none defined
|
|
87
|
+
# the MZ header must have 0x3c pointing just past its last byte which should be 8bytes aligned
|
|
88
|
+
# the 2 1st bytes of the MZ header should be 'MZ'
|
|
89
|
+
def encode_header(*a)
|
|
90
|
+
encode_default_mz_header if @mz.encoded.empty?
|
|
91
|
+
|
|
92
|
+
@encoded << @mz.encoded.dup
|
|
93
|
+
|
|
94
|
+
# append the PE signature
|
|
95
|
+
@signature ||= MAGIC
|
|
96
|
+
@encoded << @signature
|
|
97
|
+
|
|
98
|
+
super(*a)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# a returns a new PE with only minimal information copied:
|
|
102
|
+
# section name/perm/addr/content
|
|
103
|
+
# exports
|
|
104
|
+
# imports (with boundimport cleared)
|
|
105
|
+
# resources
|
|
106
|
+
def mini_copy(share_ns=true)
|
|
107
|
+
ret = self.class.new(@cpu)
|
|
108
|
+
ret.share_namespace(self) if share_ns
|
|
109
|
+
ret.header.machine = @header.machine
|
|
110
|
+
ret.header.characteristics = @header.characteristics
|
|
111
|
+
ret.optheader.entrypoint = @optheader.entrypoint
|
|
112
|
+
ret.optheader.image_base = @optheader.image_base
|
|
113
|
+
ret.optheader.subsystem = @optheader.subsystem
|
|
114
|
+
ret.optheader.dll_characts = @optheader.dll_characts
|
|
115
|
+
@sections.each { |s|
|
|
116
|
+
rs = Section.new
|
|
117
|
+
rs.name = s.name
|
|
118
|
+
rs.virtaddr = s.virtaddr
|
|
119
|
+
rs.characteristics = s.characteristics
|
|
120
|
+
rs.encoded = s.encoded
|
|
121
|
+
ret.sections << s
|
|
122
|
+
}
|
|
123
|
+
ret.resource = resource
|
|
124
|
+
ret.tls = tls
|
|
125
|
+
if imports
|
|
126
|
+
ret.imports = @imports.map { |id| id.dup }
|
|
127
|
+
ret.imports.each { |id|
|
|
128
|
+
id.timestamp = id.firstforwarder =
|
|
129
|
+
id.ilt_p = id.libname_p = nil
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
ret.export = export
|
|
133
|
+
ret
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def c_set_default_entrypoint
|
|
137
|
+
return if @optheader.entrypoint
|
|
138
|
+
if @sections.find { |s| s.encoded.export['main'] }
|
|
139
|
+
@optheader.entrypoint = 'main'
|
|
140
|
+
elsif @sections.find { |s| s.encoded.export['DllEntryPoint'] }
|
|
141
|
+
@optheader.entrypoint = 'DllEntryPoint'
|
|
142
|
+
elsif @sections.find { |s| s.encoded.export['DllMain'] }
|
|
143
|
+
case @cpu.shortname
|
|
144
|
+
when 'ia32'
|
|
145
|
+
@optheader.entrypoint = 'DllEntryPoint'
|
|
146
|
+
compile_c <<EOS
|
|
147
|
+
enum { DLL_PROCESS_DETACH, DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH, DLL_PROCESS_VERIFIER };
|
|
148
|
+
__stdcall int DllMain(void *handle, unsigned long reason, void *reserved);
|
|
149
|
+
__stdcall int DllEntryPoint(void *handle, unsigned long reason, void *reserved) {
|
|
150
|
+
int ret = DllMain(handle, reason, reserved);
|
|
151
|
+
if (ret == 0 && reason == DLL_PROCESS_ATTACH)
|
|
152
|
+
DllMain(handle, DLL_PROCESS_DETACH, reserved);
|
|
153
|
+
return ret;
|
|
154
|
+
}
|
|
155
|
+
EOS
|
|
156
|
+
else
|
|
157
|
+
@optheader.entrypoint = 'DllMain'
|
|
158
|
+
end
|
|
159
|
+
elsif @sections.find { |s| s.encoded.export['WinMain'] }
|
|
160
|
+
case @cpu.shortname
|
|
161
|
+
when 'ia32'
|
|
162
|
+
@optheader.entrypoint = 'main'
|
|
163
|
+
compile_c <<EOS
|
|
164
|
+
#define GetCommandLine GetCommandLineA
|
|
165
|
+
#define GetModuleHandle GetModuleHandleA
|
|
166
|
+
#define GetStartupInfo GetStartupInfoA
|
|
167
|
+
#define STARTF_USESHOWWINDOW 0x00000001
|
|
168
|
+
#define SW_SHOWDEFAULT 10
|
|
169
|
+
|
|
170
|
+
typedef unsigned long DWORD;
|
|
171
|
+
typedef unsigned short WORD;
|
|
172
|
+
typedef struct {
|
|
173
|
+
DWORD cb; char *lpReserved, *lpDesktop, *lpTitle;
|
|
174
|
+
DWORD dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
|
|
175
|
+
WORD wShowWindow, cbReserved2; char *lpReserved2;
|
|
176
|
+
void *hStdInput, *hStdOutput, *hStdError;
|
|
177
|
+
} STARTUPINFO;
|
|
178
|
+
|
|
179
|
+
__stdcall void *GetModuleHandleA(const char *lpModuleName);
|
|
180
|
+
__stdcall void GetStartupInfoA(STARTUPINFO *lpStartupInfo);
|
|
181
|
+
__stdcall void ExitProcess(unsigned int uExitCode);
|
|
182
|
+
__stdcall char *GetCommandLineA(void);
|
|
183
|
+
__stdcall int WinMain(void *hInstance, void *hPrevInstance, char *lpCmdLine, int nShowCmd);
|
|
184
|
+
|
|
185
|
+
int main(void) {
|
|
186
|
+
STARTUPINFO startupinfo;
|
|
187
|
+
startupinfo.cb = sizeof(STARTUPINFO);
|
|
188
|
+
char *cmd = GetCommandLine();
|
|
189
|
+
int ret;
|
|
190
|
+
|
|
191
|
+
if (*cmd == '"') {
|
|
192
|
+
cmd++;
|
|
193
|
+
while (*cmd && *cmd != '"') {
|
|
194
|
+
if (*cmd == '\\\\') cmd++;
|
|
195
|
+
cmd++;
|
|
196
|
+
}
|
|
197
|
+
if (*cmd == '"') cmd++;
|
|
198
|
+
} else
|
|
199
|
+
while (*cmd && *cmd != ' ') cmd++;
|
|
200
|
+
while (*cmd == ' ') cmd++;
|
|
201
|
+
|
|
202
|
+
GetStartupInfo(&startupinfo);
|
|
203
|
+
ret = WinMain(GetModuleHandle(0), 0, cmd, (startupinfo.dwFlags & STARTF_USESHOWWINDOW) ? (int)startupinfo.wShowWindow : (int)SW_SHOWDEFAULT);
|
|
204
|
+
ExitProcess((DWORD)ret);
|
|
205
|
+
return ret;
|
|
206
|
+
}
|
|
207
|
+
EOS
|
|
208
|
+
else
|
|
209
|
+
@optheader.entrypoint = 'WinMain'
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# handles writes to fs:[0] -> dasm SEH handler (first only, does not follow the chain)
|
|
215
|
+
# TODO seh prototype (args => context)
|
|
216
|
+
# TODO hook on (non)resolution of :w xref
|
|
217
|
+
def get_xrefs_x(dasm, di)
|
|
218
|
+
if @cpu.shortname =~ /ia32|x64/ and a = di.instruction.args.first and a.kind_of? Ia32::ModRM and a.seg and a.seg.val == 4 and
|
|
219
|
+
w = get_xrefs_rw(dasm, di).find { |type, ptr, len| type == :w and ptr.externals.include? 'segment_base_fs' } and
|
|
220
|
+
dasm.backtrace(Expression[w[1], :-, 'segment_base_fs'], di.address).to_a.include?(Expression[0])
|
|
221
|
+
sehptr = w[1]
|
|
222
|
+
sz = @cpu.size/8
|
|
223
|
+
sehptr = Indirection.new(Expression[Indirection.new(sehptr, sz, di.address), :+, sz], sz, di.address)
|
|
224
|
+
a = dasm.backtrace(sehptr, di.address, :include_start => true, :origin => di.address, :type => :x, :detached => true)
|
|
225
|
+
puts "backtrace seh from #{di} => #{a.map { |addr| Expression[addr] }.join(', ')}" if $VERBOSE
|
|
226
|
+
a.each { |aa|
|
|
227
|
+
next if aa == Expression::Unknown
|
|
228
|
+
l = dasm.auto_label_at(aa, 'seh', 'loc', 'sub')
|
|
229
|
+
dasm.addrs_todo << [aa]
|
|
230
|
+
}
|
|
231
|
+
super(dasm, di)
|
|
232
|
+
else
|
|
233
|
+
super(dasm, di)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# returns a disassembler with a special decodedfunction for GetProcAddress (i386 only), and the default func
|
|
238
|
+
def init_disassembler
|
|
239
|
+
d = super()
|
|
240
|
+
d.backtrace_maxblocks_data = 4
|
|
241
|
+
case @cpu.shortname
|
|
242
|
+
when 'ia32', 'x64'
|
|
243
|
+
old_cp = d.c_parser
|
|
244
|
+
d.c_parser = nil
|
|
245
|
+
d.parse_c '__stdcall void *GetProcAddress(int, char *);'
|
|
246
|
+
d.c_parser.lexer.define_weak('__MS_X86_64_ABI__') if @cpu.kind_of? X86_64
|
|
247
|
+
gpa = @cpu.decode_c_function_prototype(d.c_parser, 'GetProcAddress')
|
|
248
|
+
d.c_parser = old_cp
|
|
249
|
+
d.parse_c ''
|
|
250
|
+
d.c_parser.lexer.define_weak('__MS_X86_64_ABI__') if @cpu.kind_of? X86_64
|
|
251
|
+
@getprocaddr_unknown = []
|
|
252
|
+
gpa.btbind_callback = lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth|
|
|
253
|
+
break bind if @getprocaddr_unknown.include? [dasm, calladdr] or not Expression[expr].externals.include? :eax
|
|
254
|
+
sz = @cpu.size/8
|
|
255
|
+
break bind if not dasm.decoded[calladdr]
|
|
256
|
+
if @cpu.kind_of? X86_64
|
|
257
|
+
arg2 = :rdx
|
|
258
|
+
else
|
|
259
|
+
arg2 = Indirection[[:esp, :+, 2*sz], sz, calladdr]
|
|
260
|
+
end
|
|
261
|
+
fnaddr = dasm.backtrace(arg2, calladdr, :include_start => true, :maxdepth => maxdepth)
|
|
262
|
+
if fnaddr.kind_of? ::Array and fnaddr.length == 1 and s = dasm.get_section_at(fnaddr.first) and fn = s[0].read(64) and i = fn.index(?\0) and i > sz # try to avoid ordinals
|
|
263
|
+
bind = bind.merge @cpu.register_symbols[0] => Expression[fn[0, i]]
|
|
264
|
+
else
|
|
265
|
+
@getprocaddr_unknown << [dasm, calladdr]
|
|
266
|
+
puts "unknown func name for getprocaddress from #{Expression[calladdr]}" if $VERBOSE
|
|
267
|
+
end
|
|
268
|
+
bind
|
|
269
|
+
}
|
|
270
|
+
d.function[Expression['GetProcAddress']] = gpa
|
|
271
|
+
d.function[:default] = @cpu.disassembler_default_func
|
|
272
|
+
end
|
|
273
|
+
d
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def module_name
|
|
277
|
+
export and @export.libname
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def module_address
|
|
281
|
+
@optheader.image_base
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def module_size
|
|
285
|
+
@sections.map { |s_| s_.virtaddr + s_.virtsize }.max || 0
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def module_symbols
|
|
289
|
+
syms = [['entrypoint', @optheader.entrypoint]]
|
|
290
|
+
@export.exports.to_a.each { |e|
|
|
291
|
+
next if not e.target
|
|
292
|
+
name = e.name || "ord_#{e.ordinal}"
|
|
293
|
+
syms << [name, label_rva(e.target)]
|
|
294
|
+
} if export
|
|
295
|
+
syms
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# an instance of a PE file, loaded in memory
|
|
300
|
+
# just change the rva_to_off and the section content decoding methods
|
|
301
|
+
class LoadedPE < PE
|
|
302
|
+
attr_accessor :load_address
|
|
303
|
+
|
|
304
|
+
# use the virtualaddr/virtualsize fields of the section header
|
|
305
|
+
def decode_section_body(s)
|
|
306
|
+
s.encoded = @encoded[s.virtaddr, s.virtsize] || EncodedData.new
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# no need to decode relocations on an already mapped image
|
|
310
|
+
def decode_relocs
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# reads a loaded PE from memory, returns a PE object
|
|
314
|
+
# dumps the header, optheader and all sections ; try to rebuild IAT (#memdump_imports)
|
|
315
|
+
def self.memdump(memory, baseaddr, entrypoint = nil, iat_p=nil)
|
|
316
|
+
loaded = LoadedPE.load memory[baseaddr, 0x1000_0000]
|
|
317
|
+
loaded.load_address = baseaddr
|
|
318
|
+
loaded.decode
|
|
319
|
+
|
|
320
|
+
dump = PE.new(loaded.cpu_from_headers)
|
|
321
|
+
dump.share_namespace loaded
|
|
322
|
+
dump.optheader.image_base = baseaddr
|
|
323
|
+
dump.optheader.entrypoint = (entrypoint || loaded.optheader.entrypoint + baseaddr) - baseaddr
|
|
324
|
+
dump.directory['resource_table'] = loaded.directory['resource_table']
|
|
325
|
+
|
|
326
|
+
loaded.sections.each { |s|
|
|
327
|
+
ss = Section.new
|
|
328
|
+
ss.name = s.name
|
|
329
|
+
ss.virtaddr = s.virtaddr
|
|
330
|
+
ss.encoded = s.encoded
|
|
331
|
+
ss.characteristics = s.characteristics
|
|
332
|
+
dump.sections << ss
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
loaded.memdump_imports(memory, dump, iat_p)
|
|
336
|
+
|
|
337
|
+
dump
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# rebuilds an IAT from the loaded pe and the memory
|
|
341
|
+
# for each loaded iat, find the matching dll in memory
|
|
342
|
+
# for each loaded iat entry, retrieve the exported name from the loaded dll
|
|
343
|
+
# OR
|
|
344
|
+
# from a base iat address in memory (unk_iat_p, rva), retrieve the 1st dll, find
|
|
345
|
+
# all iat pointers/forwarders to this dll, on failure try to find another dll
|
|
346
|
+
# allows gaps of 5 invalid pointers between libraries
|
|
347
|
+
# dll found by scanning pages 16 by 16 backward from the first iat address (XXX the 1st must not be forwarded)
|
|
348
|
+
# TODO bound imports
|
|
349
|
+
def memdump_imports(memory, dump, unk_iat_p=nil)
|
|
350
|
+
puts 'rebuilding imports...' if $VERBOSE
|
|
351
|
+
if unk_iat_p
|
|
352
|
+
# read iat data from unk_iat_p
|
|
353
|
+
iat_p = unk_iat_p
|
|
354
|
+
else
|
|
355
|
+
return if not imports
|
|
356
|
+
# read iat data from @imports
|
|
357
|
+
imports = @imports.dup
|
|
358
|
+
imports.each { |id| id.iat = id.iat.dup }
|
|
359
|
+
iat_p = imports.first.iat_p # used for iat_p
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
failcnt = 0 # bad pointers in iat table (unk_ only)
|
|
363
|
+
dump.imports ||= []
|
|
364
|
+
loaded_dll = nil # the dll from who we're importing the current importdirectory
|
|
365
|
+
ptrsz = (@optheader.signature == 'PE+' ? 8 : 4)
|
|
366
|
+
cache = [] # optimize forwarder target search
|
|
367
|
+
loop do
|
|
368
|
+
if unk_iat_p
|
|
369
|
+
# read imported pointer from the table
|
|
370
|
+
ptr = decode_xword(EncodedData.new(memory[@load_address + iat_p, ptrsz]))
|
|
371
|
+
iat_p += ptrsz
|
|
372
|
+
else
|
|
373
|
+
# read imported pointer from the import structure
|
|
374
|
+
while not ptr = imports.first.iat.shift
|
|
375
|
+
load_dll = nil
|
|
376
|
+
imports.shift
|
|
377
|
+
break if imports.empty?
|
|
378
|
+
iat_p = imports.first.iat_p
|
|
379
|
+
end
|
|
380
|
+
break if imports.empty?
|
|
381
|
+
iat_p += ptrsz
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
if not loaded_dll or not e = loaded_dll.export.exports.find { |e_| loaded_dll.label_rva(e_.target) == ptr - loaded_dll.load_address }
|
|
385
|
+
# points to unknown space
|
|
386
|
+
# find pointed module start
|
|
387
|
+
if not dll = cache.find { |dll_| ptr >= dll_.load_address and ptr < dll_.load_address + dll_.optheader.image_size }
|
|
388
|
+
addr = ptr & ~0xffff
|
|
389
|
+
256.times { break if memory[addr, 2] == MZ::MAGIC or addr < 0x10000 ; addr -= 0x10000 }
|
|
390
|
+
if memory[addr, 2] == MZ::MAGIC
|
|
391
|
+
dll = LoadedPE.load memory[addr, 0x1000_0000]
|
|
392
|
+
dll.load_address = addr
|
|
393
|
+
dll.decode_header
|
|
394
|
+
dll.decode_exports
|
|
395
|
+
cache << dll
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
if dll and dll.export and e = dll.export.exports.find { |e_| dll.label_rva(e_.target) == ptr - dll.load_address }
|
|
399
|
+
if loaded_dll and ee = loaded_dll.export.exports.find { |ee_| ee_.forwarder_name == e.name }
|
|
400
|
+
# it's a forwarder from the current loaded_dll
|
|
401
|
+
puts "forwarder #{ee.name} -> #{dll.export.libname}!#{e.name}" if $DEBUG
|
|
402
|
+
e = ee
|
|
403
|
+
else
|
|
404
|
+
# new library, start a new importdirectory
|
|
405
|
+
# XXX if 1st import is forwarded, loaded_dll will points to the bad module...
|
|
406
|
+
loaded_dll = dll
|
|
407
|
+
id = ImportDirectory.new
|
|
408
|
+
id.libname = loaded_dll.export.libname
|
|
409
|
+
puts "lib #{id.libname}" if $VERBOSE
|
|
410
|
+
id.imports = []
|
|
411
|
+
id.iat_p = iat_p - ptrsz
|
|
412
|
+
dump.imports << id
|
|
413
|
+
end
|
|
414
|
+
else
|
|
415
|
+
puts 'unknown ptr %x' % ptr if $DEBUG
|
|
416
|
+
# allow holes in the unk_iat_p table
|
|
417
|
+
break if not unk_iat_p or failcnt > 4
|
|
418
|
+
failcnt += 1
|
|
419
|
+
next
|
|
420
|
+
end
|
|
421
|
+
failcnt = 0
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# dumped last importdirectory is correct, append the import field
|
|
425
|
+
i = ImportDirectory::Import.new
|
|
426
|
+
if e.name
|
|
427
|
+
puts e.name if $DEBUG
|
|
428
|
+
i.name = e.name
|
|
429
|
+
else
|
|
430
|
+
puts "##{e.ordinal}" if $DEBUG
|
|
431
|
+
i.ordinal = e.ordinal
|
|
432
|
+
end
|
|
433
|
+
dump.imports.last.imports << i
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# This file is part of Metasm, the Ruby assembly manipulation suite
|
|
2
|
+
# Copyright (C) 2006-2009 Yoann GUILLOT
|
|
3
|
+
#
|
|
4
|
+
# Licence is LGPL, see LICENCE in the top-level directory
|
|
5
|
+
|
|
6
|
+
module Metasm
|
|
7
|
+
# a class representing a simple structure serialized in a binary
|
|
8
|
+
class SerialStruct
|
|
9
|
+
# hash shared by all classes
|
|
10
|
+
# key = class, value = array of fields
|
|
11
|
+
# field = array [name, decode...]
|
|
12
|
+
@@fields = {}
|
|
13
|
+
NAME=0
|
|
14
|
+
DECODE=1
|
|
15
|
+
ENCODE=2
|
|
16
|
+
DEFVAL=3
|
|
17
|
+
ENUM=4
|
|
18
|
+
BITS=5
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
# defines a new field
|
|
22
|
+
# adds an accessor
|
|
23
|
+
def new_field(name, decode, encode, defval, enum=nil, bits=nil)
|
|
24
|
+
if name
|
|
25
|
+
attr_accessor name
|
|
26
|
+
name = "@#{name}".to_sym
|
|
27
|
+
end
|
|
28
|
+
(@@fields[self] ||= []) << [name, decode, encode, defval, enum, bits]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# creates a field constructor for a simple integer
|
|
32
|
+
# relies on exe implementing (en,de)code_#{type}
|
|
33
|
+
def new_int_field(*types)
|
|
34
|
+
recv = class << self ; self ; end
|
|
35
|
+
types.each { |type|
|
|
36
|
+
recv.send(:define_method, type) { |name, *args|
|
|
37
|
+
new_field(name, "decode_#{type}".to_sym, "encode_#{type}".to_sym, args[0] || 0, args[1])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# shortcut to define multiple fields of this type with default values
|
|
41
|
+
recv.send(:define_method, "#{type}s") { |*names|
|
|
42
|
+
names.each { |name| send type, name }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# standard fields:
|
|
48
|
+
|
|
49
|
+
# a fixed-size memory chunk
|
|
50
|
+
def mem(name, len, defval='')
|
|
51
|
+
new_field(name, lambda { |exe, me| exe.curencoded.read(len) }, lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }, defval)
|
|
52
|
+
end
|
|
53
|
+
# a fixed-size string, 0-padded
|
|
54
|
+
def str(name, len, defval='')
|
|
55
|
+
e = lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }
|
|
56
|
+
d = lambda { |exe, me| v = exe.curencoded.read(len) ; v = v[0, v.index(?\0)] if v.index(?\0) ; v }
|
|
57
|
+
new_field(name, d, e, defval)
|
|
58
|
+
end
|
|
59
|
+
# 0-terminated string
|
|
60
|
+
def strz(name, defval='')
|
|
61
|
+
d = lambda { |exe, me|
|
|
62
|
+
ed = exe.curencoded
|
|
63
|
+
ed.read(ed.data.index(?\0, ed.ptr)-ed.ptr+1).chop
|
|
64
|
+
}
|
|
65
|
+
e = lambda { |exe, me, val| val + 0.chr }
|
|
66
|
+
new_field(name, d, e, defval)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# field access
|
|
70
|
+
def fld_get(name)
|
|
71
|
+
name = "@#{name}".to_sym
|
|
72
|
+
@@fields[self].find { |f| f[NAME] == name }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# change the default for a field
|
|
76
|
+
def fld_default(name, default=nil, &b)
|
|
77
|
+
default ||= b
|
|
78
|
+
fld_get(name)[DEFVAL] = default
|
|
79
|
+
end
|
|
80
|
+
def fld_enum(name, enum=nil, &b) fld_get(name)[ENUM] = enum||b end
|
|
81
|
+
def fld_bits(name, bits=nil, &b) fld_get(name)[BITS] = bits||b end
|
|
82
|
+
|
|
83
|
+
# define a bitfield: many fields inside a single word/byte/whatever
|
|
84
|
+
# usage: bitfield :word, 0 => :lala, 1 => nil, 4 => :lolo, 8 => :foo
|
|
85
|
+
# => a bitfield read using exe.decode_word, containing 3 subfields:
|
|
86
|
+
# :lala (bits 0...1), (discard 3 bits), :lolo (bits 4...8), and :foo (bits 8..-1)
|
|
87
|
+
# fields default to 0
|
|
88
|
+
def bitfield(inttype, h)
|
|
89
|
+
# XXX encode/decode very not threadsafe ! this is a Georges Foreman Guarantee.
|
|
90
|
+
# could use a me.instance_variable..
|
|
91
|
+
|
|
92
|
+
# decode the value in a temp var
|
|
93
|
+
d = lambda { |exe, me| @bitfield_val = exe.send("decode_#{inttype}") }
|
|
94
|
+
# reset a temp var
|
|
95
|
+
e = lambda { |exe, me, val| @bitfield_val = 0 ; nil }
|
|
96
|
+
new_field(nil, d, e, nil)
|
|
97
|
+
|
|
98
|
+
h = h.sort
|
|
99
|
+
h.length.times { |i|
|
|
100
|
+
# yay closure !
|
|
101
|
+
# get field parameters
|
|
102
|
+
next if not name = h[i][1]
|
|
103
|
+
off = h[i][0]
|
|
104
|
+
nxt = h[i+1]
|
|
105
|
+
mask = (nxt ? (1 << (nxt[0]-off))-1 : -1)
|
|
106
|
+
# read the field value from the temp var
|
|
107
|
+
d = lambda { |exe, me| (@bitfield_val >> off) & mask }
|
|
108
|
+
# update the temp var with the field value, return nil
|
|
109
|
+
e = lambda { |exe, me, val| @bitfield_val |= (val & mask) << off ; nil }
|
|
110
|
+
new_field(name, d, e, 0)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# free the temp var
|
|
114
|
+
d = lambda { |exe, me| @bitfield_val = nil }
|
|
115
|
+
# return encoded temp var
|
|
116
|
+
e = lambda { |exe, me, val|
|
|
117
|
+
val = @bitfield_val
|
|
118
|
+
@bitfield_val = nil
|
|
119
|
+
exe.send("encode_#{inttype}", val)
|
|
120
|
+
}
|
|
121
|
+
new_field(nil, d, e, nil)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# inject a hook to be run during the decoding process
|
|
125
|
+
def decode_hook(before=nil, &b)
|
|
126
|
+
idx = (before ? @@fields[self].index(fld_get(before)) : -1)
|
|
127
|
+
@@fields[self].insert(idx, [nil, b])
|
|
128
|
+
end
|
|
129
|
+
end # class methods
|
|
130
|
+
|
|
131
|
+
# standard int fields
|
|
132
|
+
new_int_field :byte, :half, :word
|
|
133
|
+
|
|
134
|
+
# set value of fields from argument list, runs int_to_hash if needed
|
|
135
|
+
def initialize(*a)
|
|
136
|
+
if not a.empty?
|
|
137
|
+
a.zip(struct_fields.reject { |f| not f[NAME] }).each { |v, f|
|
|
138
|
+
v = int_to_hash(v, f[ENUM]) if f[ENUM]
|
|
139
|
+
v = bits_to_hash(v, f[BITS]) if f[BITS]
|
|
140
|
+
instance_variable_set f[NAME], v
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# returns this classes' field array
|
|
146
|
+
# uses struct_specialized if defined (a method that returns another
|
|
147
|
+
# SerialStruct class whose fields should be used)
|
|
148
|
+
def struct_fields(exe=nil)
|
|
149
|
+
klass = self.class
|
|
150
|
+
klass = struct_specialized(exe) if respond_to? :struct_specialized
|
|
151
|
+
raise "SerialStruct: no fields for #{klass}" if $DEBUG and not @@fields[klass]
|
|
152
|
+
@@fields[klass]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# decodes the fields from the exe
|
|
156
|
+
def decode(exe, *args)
|
|
157
|
+
struct_fields(exe).each { |f|
|
|
158
|
+
case d = f[DECODE]
|
|
159
|
+
when Symbol; val = exe.send(d, *args)
|
|
160
|
+
when Array; val = exe.send(*d)
|
|
161
|
+
when Proc; val = d[exe, self]
|
|
162
|
+
when nil; next
|
|
163
|
+
end
|
|
164
|
+
next if not f[NAME]
|
|
165
|
+
if h = f[ENUM]; h = h[exe, self] if h.kind_of? Proc; val = int_to_hash( val, h) end
|
|
166
|
+
if h = f[BITS]; h = h[exe, self] if h.kind_of? Proc; val = bits_to_hash(val, h) end
|
|
167
|
+
instance_variable_set(f[NAME], val)
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# initialize uninitialized fields
|
|
172
|
+
def set_default_values(exe)
|
|
173
|
+
struct_fields(exe).each { |f|
|
|
174
|
+
if not f[NAME]
|
|
175
|
+
f[DEFVAL][exe, self] if f[DEFVAL]
|
|
176
|
+
next
|
|
177
|
+
end
|
|
178
|
+
# check existence to avoid a "warning: ivar @bla not initialized"
|
|
179
|
+
next if instance_variables.map { |ivn| ivn.to_sym }.include?(f[NAME]) and instance_variable_get(f[NAME])
|
|
180
|
+
val = f[DEFVAL]
|
|
181
|
+
val = val[exe, self] if val.kind_of? Proc
|
|
182
|
+
if val.kind_of? Integer and h = f[ENUM]; h = h[exe, self] if h.kind_of? Proc; val = int_to_hash( val, h) end
|
|
183
|
+
if val.kind_of? Integer and h = f[BITS]; h = h[exe, self] if h.kind_of? Proc; val = bits_to_hash(val, h) end
|
|
184
|
+
instance_variable_set(f[NAME], val)
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# sets default values, then encodes the fields, returns an EData
|
|
189
|
+
def encode(exe, *a)
|
|
190
|
+
set_default_values(exe, *a)
|
|
191
|
+
|
|
192
|
+
ed = EncodedData.new
|
|
193
|
+
struct_fields(exe).each { |f|
|
|
194
|
+
if not f[NAME]
|
|
195
|
+
ed << f[ENCODE][exe, self, nil] if f[ENCODE]
|
|
196
|
+
next
|
|
197
|
+
end
|
|
198
|
+
val = instance_variable_get(f[NAME])
|
|
199
|
+
if h = f[ENUM]; h = h[exe, self] if h.kind_of? Proc; val = int_from_hash( val, h) end
|
|
200
|
+
if h = f[BITS]; h = h[exe, self] if h.kind_of? Proc; val = bits_from_hash(val, h) end
|
|
201
|
+
case e = f[ENCODE]
|
|
202
|
+
when Symbol; val = exe.send(e, val)
|
|
203
|
+
when Array; val = exe.send(e, *val)
|
|
204
|
+
when Proc; val = e[exe, self, val]
|
|
205
|
+
when nil; next
|
|
206
|
+
end
|
|
207
|
+
ed << val
|
|
208
|
+
}
|
|
209
|
+
ed
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# shortcut to create a new instance and decode it
|
|
213
|
+
def self.decode(*a)
|
|
214
|
+
s = new
|
|
215
|
+
s.decode(*a)
|
|
216
|
+
s
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def dump(e, a)
|
|
220
|
+
case e
|
|
221
|
+
when Integer; e >= 0x100 ? '0x%X'%e : e
|
|
222
|
+
when String; e.length > 64 ? e[0, 62].inspect+'...' : e.inspect
|
|
223
|
+
when Array; '[' + e.map { |i| dump(i, a) }.join(', ') + ']'
|
|
224
|
+
when SerialStruct; a.include?(e) ? '...' : e.to_s(a)
|
|
225
|
+
else e.inspect
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# displays the struct content, ordered by fields
|
|
230
|
+
def to_s(a=[])
|
|
231
|
+
ivs = instance_variables.map { |iv| iv.to_sym }
|
|
232
|
+
ivs = (struct_fields.to_a.map { |f| f[NAME] }.compact & ivs) | ivs
|
|
233
|
+
"<#{self.class} " + ivs.map { |iv| "#{iv}=#{dump(instance_variable_get(iv), a+[self])}" }.join(' ') + ">"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
class ExeFormat
|
|
238
|
+
def curencoded; encoded; end
|
|
239
|
+
def decode_strz(ed = curencoded)
|
|
240
|
+
if stop = ed.data.index(?\0, ed.ptr)
|
|
241
|
+
ed.read(stop - ed.ptr + 1).chop
|
|
242
|
+
else ''
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|