metasm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,228 @@
|
|
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/main'
|
8
|
+
require 'metasm/parse'
|
9
|
+
require 'metasm/encode'
|
10
|
+
require 'metasm/decode'
|
11
|
+
require 'metasm/exe_format/serialstruct'
|
12
|
+
require 'metasm/os/main' # VirtualFile
|
13
|
+
|
14
|
+
module Metasm
|
15
|
+
class ExeFormat
|
16
|
+
# creates a new instance, populates self.encoded with the supplied string
|
17
|
+
def self.load(str, *a, &b)
|
18
|
+
e = new(*a, &b)
|
19
|
+
if str.kind_of? EncodedData; e.encoded = str
|
20
|
+
else e.encoded << str
|
21
|
+
end
|
22
|
+
e
|
23
|
+
end
|
24
|
+
|
25
|
+
# same as load, used by AutoExe
|
26
|
+
def self.autoexe_load(*x, &b)
|
27
|
+
load(*x, &b)
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :filename
|
31
|
+
|
32
|
+
# same as +load+, but from a file
|
33
|
+
# uses VirtualFile if available
|
34
|
+
def self.load_file(path, *a, &b)
|
35
|
+
e = load(VirtualFile.read(path), *a, &b)
|
36
|
+
e.filename ||= path
|
37
|
+
e
|
38
|
+
end
|
39
|
+
|
40
|
+
# +load_file+ then decode
|
41
|
+
def self.decode_file(path, *a, &b)
|
42
|
+
e = load_file(path, *a, &b)
|
43
|
+
e.decode if not e.instance_variables.map { |iv| iv.to_s }.include?("@disassembler")
|
44
|
+
e
|
45
|
+
end
|
46
|
+
|
47
|
+
# +load_file+ then decode header
|
48
|
+
def self.decode_file_header(path, *a, &b)
|
49
|
+
e = load_file(path, *a, &b)
|
50
|
+
e.decode_header
|
51
|
+
e
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.decode(raw, *a, &b)
|
55
|
+
e = load(raw, *a, &b)
|
56
|
+
e.decode
|
57
|
+
e
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.decode_header(raw, *a, &b)
|
61
|
+
e = load(raw, *a, &b)
|
62
|
+
e.decode_header
|
63
|
+
e
|
64
|
+
end
|
65
|
+
|
66
|
+
# creates a new object using the specified cpu, parses the asm source, and assemble
|
67
|
+
def self.assemble(cpu, source, file='<unk>', lineno=1)
|
68
|
+
source, cpu = cpu, source if source.kind_of? CPU
|
69
|
+
e = new(cpu)
|
70
|
+
e.assemble(source, file, lineno)
|
71
|
+
e
|
72
|
+
end
|
73
|
+
|
74
|
+
# same as #assemble, reads asm source from the specified file
|
75
|
+
def self.assemble_file(cpu, filename)
|
76
|
+
filename, cpu = cpu, filename if filename.kind_of? CPU
|
77
|
+
assemble(cpu, File.read(filename), filename, 1)
|
78
|
+
end
|
79
|
+
|
80
|
+
# parses a bunch of standalone C code, compile and assemble it
|
81
|
+
def compile_c(source, file='<unk>', lineno=1)
|
82
|
+
cp = @cpu.new_cparser
|
83
|
+
tune_cparser(cp)
|
84
|
+
cp.parse(source, file, lineno)
|
85
|
+
read_c_attrs cp if respond_to? :read_c_attrs
|
86
|
+
asm_source = @cpu.new_ccompiler(cp, self).compile
|
87
|
+
puts asm_source if $DEBUG
|
88
|
+
assemble(asm_source, 'C compiler output', 1)
|
89
|
+
c_set_default_entrypoint
|
90
|
+
end
|
91
|
+
|
92
|
+
# creates a new object using the specified cpu, parse/compile/assemble the C source
|
93
|
+
def self.compile_c(cpu, source, file='<unk>', lineno=1)
|
94
|
+
source, cpu = cpu, source if source.kind_of? CPU
|
95
|
+
e = new(cpu)
|
96
|
+
e.compile_c(source, file, lineno)
|
97
|
+
e
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.compile_c_file(cpu, filename)
|
101
|
+
filename, cpu = cpu, filename if filename.kind_of? CPU
|
102
|
+
compile_c(cpu, File.read(filename), filename, 1)
|
103
|
+
end
|
104
|
+
|
105
|
+
# add directive to change the current assembler section to the assembler source +src+
|
106
|
+
def compile_setsection(src, section)
|
107
|
+
src << section
|
108
|
+
end
|
109
|
+
|
110
|
+
# prepare a preprocessor before it reads any source, should define macros to identify the fileformat
|
111
|
+
def tune_prepro(l)
|
112
|
+
end
|
113
|
+
|
114
|
+
# prepare a cparser
|
115
|
+
def tune_cparser(cp)
|
116
|
+
tune_prepro(cp.lexer)
|
117
|
+
end
|
118
|
+
|
119
|
+
# this is called once C code is parsed, to handle C attributes like export/import/init etc
|
120
|
+
def read_c_attrs(cp)
|
121
|
+
end
|
122
|
+
|
123
|
+
# should setup a default entrypoint for C code, including preparing args for main() etc
|
124
|
+
def c_set_default_entrypoint
|
125
|
+
end
|
126
|
+
|
127
|
+
attr_writer :disassembler # custom reader
|
128
|
+
def disassembler
|
129
|
+
@disassembler ||= init_disassembler
|
130
|
+
end
|
131
|
+
|
132
|
+
# returns the exe disassembler
|
133
|
+
# if it does not exist, creates one, and feeds it with the exe sections
|
134
|
+
def init_disassembler
|
135
|
+
@disassembler ||= Disassembler.new(self)
|
136
|
+
@disassembler.cpu ||= cpu
|
137
|
+
each_section { |edata, base|
|
138
|
+
edata ||= EncodedData.new
|
139
|
+
@disassembler.add_section edata, base
|
140
|
+
}
|
141
|
+
@disassembler
|
142
|
+
end
|
143
|
+
|
144
|
+
# disassembles the specified entrypoints
|
145
|
+
# initializes the disassembler if needed
|
146
|
+
# uses get_default_entrypoints if the argument list is empty
|
147
|
+
# returns the disassembler
|
148
|
+
def disassemble(*entrypoints)
|
149
|
+
entrypoints = get_default_entrypoints if entrypoints.empty?
|
150
|
+
disassembler.disassemble(*entrypoints)
|
151
|
+
@disassembler
|
152
|
+
end
|
153
|
+
|
154
|
+
# disassembles the specified entrypoints without backtracking
|
155
|
+
# initializes the disassembler if needed
|
156
|
+
# uses get_default_entrypoints if the argument list is empty
|
157
|
+
# returns the disassembler
|
158
|
+
def disassemble_fast_deep(*entrypoints)
|
159
|
+
entrypoints = get_default_entrypoints if entrypoints.empty?
|
160
|
+
disassembler.disassemble_fast_deep(*entrypoints)
|
161
|
+
@disassembler
|
162
|
+
end
|
163
|
+
|
164
|
+
# returns a list of entrypoints to disassemble (program entrypoint, exported functions...)
|
165
|
+
def get_default_entrypoints
|
166
|
+
[]
|
167
|
+
end
|
168
|
+
|
169
|
+
# encodes the executable as a string, checks that all relocations are
|
170
|
+
# resolved, and returns the raw string version
|
171
|
+
def encode_string(*a)
|
172
|
+
encode(*a)
|
173
|
+
raise ["Unresolved relocations:", @encoded.reloc.map { |o, r| "#{r.target} " + (Backtrace.backtrace_str(r.backtrace) if r.backtrace).to_s }].join("\n") if not @encoded.reloc.empty?
|
174
|
+
@encoded.data
|
175
|
+
end
|
176
|
+
|
177
|
+
# saves the result of +encode_string+ in the specified file
|
178
|
+
# fails if the file already exists
|
179
|
+
def encode_file(path, *a)
|
180
|
+
#raise Errno::EEXIST, path if File.exist? path # race, but cannot use O_EXCL, as O_BINARY is not defined in ruby
|
181
|
+
encode_string(*a)
|
182
|
+
File.open(path, 'wb') { |fd| fd.write(@encoded.data) }
|
183
|
+
end
|
184
|
+
|
185
|
+
# returns the address at which a given file offset would be mapped
|
186
|
+
def addr_to_fileoff(addr)
|
187
|
+
addr
|
188
|
+
end
|
189
|
+
|
190
|
+
# returns the file offset where a mapped byte comes from
|
191
|
+
def fileoff_to_addr(foff)
|
192
|
+
foff
|
193
|
+
end
|
194
|
+
|
195
|
+
def shortname; self.class.name.split('::').last.downcase; end
|
196
|
+
|
197
|
+
module IntToHash
|
198
|
+
# converts a constant name to its numeric value using the hash
|
199
|
+
# {1 => 'toto', 2 => 'tata'}: 'toto' => 1, 42 => 42, 'tutu' => raise
|
200
|
+
def int_from_hash(val, hash)
|
201
|
+
val.kind_of?(Integer) ? hash.index(val) || val : hash.index(val) or raise "unknown constant #{val.inspect}"
|
202
|
+
end
|
203
|
+
|
204
|
+
# converts an array of flag constants to its numeric value using the hash
|
205
|
+
# {1 => 'toto', 2 => 'tata'}: ['toto', 'tata'] => 3, 'toto' => 2, 42 => 42
|
206
|
+
def bits_from_hash(val, hash)
|
207
|
+
val.kind_of?(Array) ? val.inject(0) { |val_, bitname| val_ | int_from_hash(bitname, hash) } : int_from_hash(val, hash)
|
208
|
+
end
|
209
|
+
|
210
|
+
# converts a numeric value to the corresponding constant name using the hash
|
211
|
+
# {1 => 'toto', 2 => 'tata'}: 1 => 'toto', 42 => 42, 'tata' => 'tata', 'tutu' => raise
|
212
|
+
def int_to_hash(val, hash)
|
213
|
+
val.kind_of?(Integer) ? hash.fetch(val, val) : (hash.index(val) ? val : raise("unknown constant #{val.inspect}"))
|
214
|
+
end
|
215
|
+
|
216
|
+
# converts a numeric value to the corresponding array of constant flag names using the hash
|
217
|
+
# {1 => 'toto', 2 => 'tata'}: 5 => ['toto', 4]
|
218
|
+
def bits_to_hash(val, hash)
|
219
|
+
(val.kind_of?(Integer) ? (hash.find_all { |k, v| val & k == k and val &= ~k }.map { |k, v| v } << val) : val.kind_of?(Array) ? val.map { |e| int_to_hash(e, hash) } : [int_to_hash(val, hash)]) - [0]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
include IntToHash
|
223
|
+
end
|
224
|
+
|
225
|
+
class SerialStruct
|
226
|
+
include ExeFormat::IntToHash
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,164 @@
|
|
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/encode'
|
9
|
+
require 'metasm/decode'
|
10
|
+
|
11
|
+
module Metasm
|
12
|
+
class MZ < ExeFormat
|
13
|
+
MAGIC = 'MZ' # 0x4d5a
|
14
|
+
class Header < SerialStruct
|
15
|
+
mem :magic, 2, MAGIC
|
16
|
+
words :cblp, :cp, :crlc, :cparhdr, :minalloc, :maxalloc, :ss, :sp, :csum, :ip, :cs, :lfarlc, :ovno
|
17
|
+
mem :unk, 4
|
18
|
+
|
19
|
+
def encode(mz, relocs)
|
20
|
+
h = EncodedData.new
|
21
|
+
set_default_values mz, h, relocs
|
22
|
+
h << super(mz)
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_default_values(mz, h=nil, relocs=nil)
|
26
|
+
return if not h
|
27
|
+
@cblp ||= Expression[[mz.label_at(mz.body, mz.body.virtsize), :-, mz.label_at(h, 0)], :%, 512] # number of bytes used in last page
|
28
|
+
@cp ||= Expression[[mz.label_at(mz.body, mz.body.virtsize), :-, mz.label_at(h, 0)], :/, 512] # number of pages used
|
29
|
+
@crlc ||= relocs.virtsize/4
|
30
|
+
@cparhdr ||= Expression[[mz.label_at(relocs, 0), :-, mz.label_at(h, 0)], :/, 16] # header size in paragraphs (16o)
|
31
|
+
@minalloc ||= ((mz.body.virtsize - mz.body.rawsize) + 15) / 16
|
32
|
+
@maxalloc ||= @minalloc
|
33
|
+
@sp ||= 0 # ss:sp points at 1st byte of body => works if body does not reach end of segment (or maybe the overflow make the stack go to header space)
|
34
|
+
@lfarlc ||= Expression[mz.label_at(relocs, 0), :-, mz.label_at(h, 0)]
|
35
|
+
|
36
|
+
super(mz)
|
37
|
+
end
|
38
|
+
|
39
|
+
def decode(mz)
|
40
|
+
super(mz)
|
41
|
+
raise InvalidExeFormat, "Invalid MZ signature #{h.magic.inspect}" if @magic != MAGIC
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Relocation < SerialStruct
|
46
|
+
words :offset, :segment
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# encodes a word in 16 bits
|
51
|
+
def encode_word(val) Expression[val].encode(:u16, @endianness) end
|
52
|
+
# decodes a 16bits word from self.encoded
|
53
|
+
def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end
|
54
|
+
|
55
|
+
|
56
|
+
attr_accessor :endianness, :header, :source
|
57
|
+
# the EncodedData representing the content of the file
|
58
|
+
attr_accessor :body
|
59
|
+
# an array of Relocations - quite obscure
|
60
|
+
attr_accessor :relocs
|
61
|
+
|
62
|
+
def initialize(cpu=nil)
|
63
|
+
@endianness = cpu ? cpu.endianness : :little
|
64
|
+
@relocs = []
|
65
|
+
@header = Header.new
|
66
|
+
@body = EncodedData.new
|
67
|
+
@source = []
|
68
|
+
super(cpu)
|
69
|
+
end
|
70
|
+
|
71
|
+
# assembles the source in the body, clears the source
|
72
|
+
def assemble(*a)
|
73
|
+
parse(*a) if not a.empty?
|
74
|
+
@body << assemble_sequence(@source, @cpu)
|
75
|
+
@body.fixup @body.binding
|
76
|
+
# XXX should create @relocs here
|
77
|
+
@source.clear
|
78
|
+
end
|
79
|
+
|
80
|
+
# sets up @cursource
|
81
|
+
def parse_init
|
82
|
+
@cursource = @source
|
83
|
+
super()
|
84
|
+
end
|
85
|
+
|
86
|
+
# encodes the header and the relocation table, return them in an array, with the body.
|
87
|
+
def pre_encode
|
88
|
+
relocs = @relocs.inject(EncodedData.new) { |edata, r| edata << r.encode(self) }
|
89
|
+
header = @header.encode self, relocs
|
90
|
+
[header, relocs, @body]
|
91
|
+
end
|
92
|
+
|
93
|
+
# defines the exe-specific parser instructions:
|
94
|
+
# .entrypoint [<label>]: defines the program entrypoint to label (or create a new label at this location)
|
95
|
+
def parse_parser_instruction(instr)
|
96
|
+
case instr.raw.downcase
|
97
|
+
when '.entrypoint'
|
98
|
+
# ".entrypoint <somelabel/expression>" or ".entrypoint" (here)
|
99
|
+
@lexer.skip_space
|
100
|
+
if tok = @lexer.nexttok and tok.type == :string
|
101
|
+
raise instr, 'syntax error' if not entrypoint = Expression.parse(@lexer)
|
102
|
+
else
|
103
|
+
entrypoint = new_label('entrypoint')
|
104
|
+
@cursource << Label.new(entrypoint, instr.backtrace.dup)
|
105
|
+
end
|
106
|
+
@header.ip = Expression[entrypoint, :-, label_at(@body, 0, 'body')]
|
107
|
+
@lexer.skip_space
|
108
|
+
raise instr, 'eol expected' if t = @lexer.nexttok and t.type != :eol
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# concats the header, relocation table and body
|
114
|
+
def encode
|
115
|
+
pre_encode.inject(@encoded) { |edata, pe| edata << pe }
|
116
|
+
@encoded.fixup @encoded.binding
|
117
|
+
encode_fix_checksum
|
118
|
+
end
|
119
|
+
|
120
|
+
# sets the file checksum (untested)
|
121
|
+
def encode_fix_checksum
|
122
|
+
@encoded.ptr = 0
|
123
|
+
decode_header
|
124
|
+
mzlen = @header.cp * 512 + @header.cblp
|
125
|
+
@encoded.ptr = 0
|
126
|
+
csum = -@header.csum
|
127
|
+
(mzlen/2).times { csum += decode_word }
|
128
|
+
csum &= 0xffff
|
129
|
+
@header.csum = csum
|
130
|
+
hdr = @header.encode(self, nil)
|
131
|
+
@encoded[0, hdr.length] = hdr
|
132
|
+
end
|
133
|
+
|
134
|
+
# decodes the MZ header from the current offset in self.encoded
|
135
|
+
def decode_header
|
136
|
+
@header.decode self
|
137
|
+
end
|
138
|
+
|
139
|
+
# decodes the relocation table
|
140
|
+
def decode_relocs
|
141
|
+
@relocs.clear
|
142
|
+
@encoded.ptr = @header.lfarlc
|
143
|
+
@header.crlc.times { @relocs << Relocation.decode(self) }
|
144
|
+
end
|
145
|
+
|
146
|
+
# decodes the main part of the program
|
147
|
+
# mostly defines the 'start' export, to point to the MZ entrypoint
|
148
|
+
def decode_body
|
149
|
+
@body = @encoded[@header.cparhdr*16...@header.cp*512+@header.cblp]
|
150
|
+
@body.virtsize += @header.minalloc * 16
|
151
|
+
@body.add_export 'start', @header.cs * 16 + @header.ip
|
152
|
+
end
|
153
|
+
|
154
|
+
def decode
|
155
|
+
decode_header
|
156
|
+
decode_relocs
|
157
|
+
decode_body
|
158
|
+
end
|
159
|
+
|
160
|
+
def each_section
|
161
|
+
yield @body, 0
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,172 @@
|
|
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/encode'
|
9
|
+
require 'metasm/decode'
|
10
|
+
|
11
|
+
|
12
|
+
module Metasm
|
13
|
+
# Nintendo DS executable file format
|
14
|
+
class NDS < ExeFormat
|
15
|
+
class Header < SerialStruct
|
16
|
+
str :title, 12
|
17
|
+
str :code, 4
|
18
|
+
str :maker, 2
|
19
|
+
bytes :unitcode, :encryptionselect, :devicetype
|
20
|
+
mem :reserved1, 9
|
21
|
+
bytes :version, :autostart
|
22
|
+
words :arm9off, :arm9entry, :arm9addr, :arm9sz
|
23
|
+
words :arm7off, :arm7entry, :arm7addr, :arm7sz
|
24
|
+
words :fnameoff, :fnamesz, :fatoff, :fatsz
|
25
|
+
words :arm9oloff, :arm9olsz, :arm7oloff, :arm7olsz
|
26
|
+
words :romctrl1, :romtcrl2, :iconoff
|
27
|
+
half :secureCRC
|
28
|
+
half :romctrl3
|
29
|
+
words :a9autoloadlist, :a7autoloadlist
|
30
|
+
mem :secareadisable, 8
|
31
|
+
words :endoff, :headersz
|
32
|
+
mem :reserved4, 56
|
33
|
+
mem :ninlogo, 156
|
34
|
+
half :logoCRC, 0xcf56
|
35
|
+
half :headerCRC
|
36
|
+
end
|
37
|
+
|
38
|
+
class Icon < SerialStruct
|
39
|
+
halfs :version, :crc
|
40
|
+
mem :reserved, 0x1c
|
41
|
+
mem :bitmap, 0x200 # 32x32, 4x4 tiles, each 4x8 bytes, 4bit depth
|
42
|
+
mem :palette, 0x20 # 16 colocs 16bits 0..0x7fff, 0 transparent (ignored)
|
43
|
+
mem :title_jap, 0x100 # 16bit unicode
|
44
|
+
mem :title_eng, 0x100
|
45
|
+
mem :title_fre, 0x100
|
46
|
+
mem :title_ger, 0x100
|
47
|
+
mem :title_ita, 0x100
|
48
|
+
mem :title_spa, 0x100
|
49
|
+
mem :unused, 0x1c0
|
50
|
+
|
51
|
+
attr_accessor :title_jap_short, :title_eng_short, :title_fre_short, :title_ger_short, :title_ita_short, :title_spa_short
|
52
|
+
|
53
|
+
def decode(exe)
|
54
|
+
super(exe)
|
55
|
+
|
56
|
+
%w[jap eng fre ger ita spa].each { |lang|
|
57
|
+
str = instance_variable_get("@title_#{lang}")
|
58
|
+
uchrs = str.unpack('v*')
|
59
|
+
str = str[0, uchrs.index(?\0).to_i*2]
|
60
|
+
instance_variable_set("@title_#{lang}", str)
|
61
|
+
str = str.unpack('v*').pack('C*')
|
62
|
+
instance_variable_set("@title_#{lang}_short", str)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def encode_byte(val) Expression[val].encode(:u8, @endianness) end
|
68
|
+
def encode_half(val) Expression[val].encode(:u16, @endianness) end
|
69
|
+
def encode_word(val) Expression[val].encode(:u32, @endianness) end
|
70
|
+
def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end
|
71
|
+
def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end
|
72
|
+
def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end
|
73
|
+
|
74
|
+
|
75
|
+
attr_accessor :header, :icon, :arm9, :arm7
|
76
|
+
attr_accessor :files, :fat
|
77
|
+
|
78
|
+
def initialize(endianness=:little)
|
79
|
+
@endianness = endianness
|
80
|
+
@encoded = EncodedData.new
|
81
|
+
end
|
82
|
+
|
83
|
+
# decodes the header from the current offset in self.encoded
|
84
|
+
def decode_header
|
85
|
+
@header = Header.decode(self)
|
86
|
+
end
|
87
|
+
|
88
|
+
def decode_icon
|
89
|
+
@encoded.ptr = @header.iconoff
|
90
|
+
@icon = Icon.decode(self)
|
91
|
+
end
|
92
|
+
|
93
|
+
def decode
|
94
|
+
decode_header
|
95
|
+
decode_icon
|
96
|
+
@arm9 = @encoded[@header.arm9off, @header.arm9sz]
|
97
|
+
@arm7 = @encoded[@header.arm7off, @header.arm7sz]
|
98
|
+
@arm9.add_export('entrypoint', @header.arm9entry - @header.arm9addr)
|
99
|
+
@arm7.add_export('entrypoint_arm7', @header.arm7entry - @header.arm7addr)
|
100
|
+
end
|
101
|
+
|
102
|
+
def decode_fat
|
103
|
+
# decode the files section
|
104
|
+
# it is just the tree structure of a file hierarchy
|
105
|
+
# no indication whatsoever on where to find individual file content
|
106
|
+
f = @encoded[@fnameoff, @fnamesz]
|
107
|
+
f.ptr = 0
|
108
|
+
idx = []
|
109
|
+
# 1st word = size of index subsection
|
110
|
+
idxsz = decode_word(f)
|
111
|
+
f.ptr = 0
|
112
|
+
# index seems to be an array of word, half, half (offset of name, index of name of first file, index of name of first subdir)
|
113
|
+
(idxsz/8).times { idx << [decode_word(f), decode_half(f), decode_half(f)] }
|
114
|
+
# follows a serie of filenames : 1-byte length, name
|
115
|
+
# if length has high bit set, name is a directory, content = index[half following the name]
|
116
|
+
dat = []
|
117
|
+
idx.each { |off, idf, idd|
|
118
|
+
f.ptr = off
|
119
|
+
dat << []
|
120
|
+
while (l = decode_byte(f)) > 0
|
121
|
+
name = f.read(l&0x7f)
|
122
|
+
if l & 0x80 > 0
|
123
|
+
i = decode_half(f)
|
124
|
+
dat.last << { name => i.to_s(16) }
|
125
|
+
else
|
126
|
+
dat.last << name
|
127
|
+
end
|
128
|
+
end
|
129
|
+
}
|
130
|
+
|
131
|
+
# build the tree from the serialized data
|
132
|
+
# directory = array of [hash (subdirname => directory) or string (filename)]
|
133
|
+
tree = dat.map { |dt| dt.map { |d| d.dup } }
|
134
|
+
tree.each { |br|
|
135
|
+
br.grep(Hash).each { |b|
|
136
|
+
b.each { |k, v| b[k] = tree[v.to_i(16) & 0xfff] }
|
137
|
+
}
|
138
|
+
}
|
139
|
+
tree = tree.first
|
140
|
+
|
141
|
+
# flatten the tree to a list of fullpath
|
142
|
+
iter = lambda { |ar, cur|
|
143
|
+
ret = []
|
144
|
+
ar.each { |elem|
|
145
|
+
case elem
|
146
|
+
when Hash; ret.concat iter[elem.values.first, cur + elem.keys.first + '/']
|
147
|
+
else ret << (cur + elem)
|
148
|
+
end
|
149
|
+
}
|
150
|
+
ret
|
151
|
+
}
|
152
|
+
|
153
|
+
@files = tree #iter[tree, '/']
|
154
|
+
|
155
|
+
encoded.ptr = @fatoff
|
156
|
+
@fat = encoded.read(@fatsz)
|
157
|
+
end
|
158
|
+
|
159
|
+
def cpu_from_headers
|
160
|
+
ARM.new
|
161
|
+
end
|
162
|
+
|
163
|
+
def each_section
|
164
|
+
yield @arm9, @header.arm9addr
|
165
|
+
yield @arm7, @header.arm7addr
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_default_entrypoints
|
169
|
+
[@header.arm9entry, @header.arm7entry]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|