1package coff 2 3import ( 4 "debug/pe" 5 "encoding/binary" 6 "errors" 7 "io" 8 "reflect" 9 "regexp" 10 "sort" 11 "strconv" 12 "strings" 13 14 "github.com/akavel/rsrc/binutil" 15) 16 17type Dir struct { // struct IMAGE_RESOURCE_DIRECTORY 18 Characteristics uint32 19 TimeDateStamp uint32 20 MajorVersion uint16 21 MinorVersion uint16 22 NumberOfNamedEntries uint16 23 NumberOfIdEntries uint16 24 DirEntries 25 Dirs 26} 27 28type DirEntries []DirEntry 29type Dirs []Dir 30 31type DirEntry struct { // struct IMAGE_RESOURCE_DIRECTORY_ENTRY 32 NameOrId uint32 33 OffsetToData uint32 34} 35 36type DataEntry struct { // struct IMAGE_RESOURCE_DATA_ENTRY 37 OffsetToData uint32 38 Size1 uint32 39 CodePage uint32 //FIXME: what value here? for now just using 0 40 Reserved uint32 41} 42 43type RelocationEntry struct { 44 RVA uint32 // "offset within the Section's raw data where the address starts." 45 SymbolIndex uint32 // "(zero based) index in the Symbol table to which the reference refers." 46 Type uint16 47} 48 49// Values reverse-engineered from windres output; names from teh Internets. 50// Teh googlies Internets don't seem to have much to say about the AMD64 one, 51// unfortunately :/ but it works... 52const ( 53 _IMAGE_REL_AMD64_ADDR32NB = 0x03 54 _IMAGE_REL_I386_DIR32NB = 0x07 55) 56 57type Auxiliary [18]byte 58 59type Symbol struct { 60 Name [8]byte 61 Value uint32 62 SectionNumber uint16 63 Type uint16 64 StorageClass uint8 65 AuxiliaryCount uint8 66 Auxiliaries []Auxiliary 67} 68 69type StringsHeader struct { 70 Length uint32 71} 72 73const ( 74 MASK_SUBDIRECTORY = 1 << 31 75 76 RT_ICON = 3 77 RT_GROUP_ICON = 3 + 11 78 RT_MANIFEST = 24 79) 80 81// http://www.delorie.com/djgpp/doc/coff/symtab.html 82const ( 83 DT_PTR = 1 84 T_UCHAR = 12 85) 86 87var ( 88 STRING_RSRC = [8]byte{'.', 'r', 's', 'r', 'c', 0, 0, 0} 89 STRING_RDATA = [8]byte{'.', 'r', 'd', 'a', 't', 'a', 0, 0} 90 91 LANG_ENTRY = DirEntry{NameOrId: 0x0409} //FIXME: language; what value should be here? 92) 93 94type Sizer interface { 95 Size() int64 //NOTE: must not exceed limits of uint32, or behavior is undefined 96} 97 98type Coff struct { 99 pe.FileHeader 100 pe.SectionHeader32 101 102 *Dir 103 DataEntries []DataEntry 104 Data []Sizer 105 106 Relocations []RelocationEntry 107 Symbols []Symbol 108 StringsHeader 109 Strings []Sizer 110} 111 112func NewRDATA() *Coff { 113 return &Coff{ 114 pe.FileHeader{ 115 Machine: pe.IMAGE_FILE_MACHINE_I386, 116 NumberOfSections: 1, // .data 117 TimeDateStamp: 0, 118 NumberOfSymbols: 2, // starting only with '.rdata', will increase; must include auxiliaries, apparently 119 SizeOfOptionalHeader: 0, 120 Characteristics: 0x0105, //http://www.delorie.com/djgpp/doc/coff/filhdr.html 121 }, 122 pe.SectionHeader32{ 123 Name: STRING_RDATA, 124 Characteristics: 0x40000040, // "INITIALIZED_DATA MEM_READ" ? 125 }, 126 127 // "directory hierarchy" of .rsrc section; empty for .data function 128 nil, 129 []DataEntry{}, 130 131 []Sizer{}, 132 133 []RelocationEntry{}, 134 135 []Symbol{Symbol{ 136 Name: STRING_RDATA, 137 Value: 0, 138 SectionNumber: 1, 139 Type: 0, // FIXME: wtf? 140 StorageClass: 3, // FIXME: is it ok? and uint8? and what does the value mean? 141 AuxiliaryCount: 1, 142 Auxiliaries: []Auxiliary{{}}, //http://www6.cptec.inpe.br/sx4/sx4man2/g1af01e/chap5.html 143 }}, 144 145 StringsHeader{ 146 Length: uint32(binary.Size(StringsHeader{})), // empty strings table for now -- but we must still show size of the table's header... 147 }, 148 []Sizer{}, 149 } 150} 151 152// NOTE: must be called immediately after NewRSRC, before any other 153// functions. 154func (coff *Coff) Arch(arch string) error { 155 switch arch { 156 case "386": 157 coff.Machine = pe.IMAGE_FILE_MACHINE_I386 158 case "amd64": 159 // Sources: 160 // https://github.com/golang/go/blob/0e23ca41d99c82d301badf1b762888e2c69e6c57/src/debug/pe/pe.go#L116 161 // https://github.com/yasm/yasm/blob/7160679eee91323db98b0974596c7221eeff772c/modules/objfmts/coff/coff-objfmt.c#L38 162 // FIXME: currently experimental -- not sure if something more doesn't need to be changed 163 coff.Machine = pe.IMAGE_FILE_MACHINE_AMD64 164 default: 165 return errors.New("coff: unknown architecture: " + arch) 166 } 167 return nil 168} 169 170//NOTE: only usable for Coff created using NewRDATA 171//NOTE: symbol names must be probably >8 characters long 172//NOTE: symbol names should not contain embedded zeroes 173func (coff *Coff) AddData(symbol string, data Sizer) { 174 coff.addSymbol(symbol) 175 coff.Data = append(coff.Data, data) 176 coff.SectionHeader32.SizeOfRawData += uint32(data.Size()) 177} 178 179// addSymbol appends a symbol to Coff.Symbols and to Coff.Strings. 180//NOTE: symbol s must be probably >8 characters long 181//NOTE: symbol s should not contain embedded zeroes 182func (coff *Coff) addSymbol(s string) { 183 coff.FileHeader.NumberOfSymbols++ 184 185 buf := strings.NewReader(s + "\000") // ASCIIZ 186 r := io.NewSectionReader(buf, 0, int64(len(s)+1)) 187 coff.Strings = append(coff.Strings, r) 188 189 coff.StringsHeader.Length += uint32(r.Size()) 190 191 coff.Symbols = append(coff.Symbols, Symbol{ 192 //Name: // will be filled in Freeze 193 //Value: // as above 194 SectionNumber: 1, 195 Type: 0, // why 0??? // DT_PTR<<4 | T_UCHAR, // unsigned char* // (?) or use void* ? T_VOID=1 196 StorageClass: 2, // 2=C_EXT, or 5=C_EXTDEF ? 197 AuxiliaryCount: 0, 198 }) 199} 200 201func NewRSRC() *Coff { 202 return &Coff{ 203 pe.FileHeader{ 204 Machine: pe.IMAGE_FILE_MACHINE_I386, 205 NumberOfSections: 1, // .rsrc 206 TimeDateStamp: 0, // was also 0 in sample data from MinGW's windres.exe 207 NumberOfSymbols: 1, 208 SizeOfOptionalHeader: 0, 209 Characteristics: 0x0104, //FIXME: copied from windres.exe output, find out what should be here and why 210 }, 211 pe.SectionHeader32{ 212 Name: STRING_RSRC, 213 Characteristics: 0x40000040, // "INITIALIZED_DATA MEM_READ" ? 214 }, 215 216 // "directory hierarchy" of .rsrc section: top level goes resource type, then id/name, then language 217 &Dir{}, 218 219 []DataEntry{}, 220 []Sizer{}, 221 222 []RelocationEntry{}, 223 224 []Symbol{Symbol{ 225 Name: STRING_RSRC, 226 Value: 0, 227 SectionNumber: 1, 228 Type: 0, // FIXME: wtf? 229 StorageClass: 3, // FIXME: is it ok? and uint8? and what does the value mean? 230 AuxiliaryCount: 0, // FIXME: wtf? 231 }}, 232 233 StringsHeader{ 234 Length: uint32(binary.Size(StringsHeader{})), // empty strings table -- but we must still show size of the table's header... 235 }, 236 []Sizer{}, 237 } 238} 239 240//NOTE: function assumes that 'id' is increasing on each entry 241//NOTE: only usable for Coff created using NewRSRC 242func (coff *Coff) AddResource(kind uint32, id uint16, data Sizer) { 243 re := RelocationEntry{ 244 // "(zero based) index in the Symbol table to which the 245 // reference refers. Once you have loaded the COFF file into 246 // memory and know where each symbol is, you find the new 247 // updated address for the given symbol and update the 248 // reference accordingly." 249 SymbolIndex: 0, 250 } 251 switch coff.Machine { 252 case pe.IMAGE_FILE_MACHINE_I386: 253 re.Type = _IMAGE_REL_I386_DIR32NB 254 case pe.IMAGE_FILE_MACHINE_AMD64: 255 re.Type = _IMAGE_REL_AMD64_ADDR32NB 256 } 257 coff.Relocations = append(coff.Relocations, re) 258 coff.SectionHeader32.NumberOfRelocations++ 259 260 // find top level entry, inserting new if necessary at correct sorted position 261 entries0 := coff.Dir.DirEntries 262 dirs0 := coff.Dir.Dirs 263 i0 := sort.Search(len(entries0), func(i int) bool { 264 return entries0[i].NameOrId >= kind 265 }) 266 if i0 >= len(entries0) || entries0[i0].NameOrId != kind { 267 // inserting new entry & dir 268 entries0 = append(entries0[:i0], append([]DirEntry{{NameOrId: kind}}, entries0[i0:]...)...) 269 dirs0 = append(dirs0[:i0], append([]Dir{{}}, dirs0[i0:]...)...) 270 coff.Dir.NumberOfIdEntries++ 271 } 272 coff.Dir.DirEntries = entries0 273 coff.Dir.Dirs = dirs0 274 275 // for second level, assume ID is always increasing, so we don't have to sort 276 dirs0[i0].DirEntries = append(dirs0[i0].DirEntries, DirEntry{NameOrId: uint32(id)}) 277 dirs0[i0].Dirs = append(dirs0[i0].Dirs, Dir{ 278 NumberOfIdEntries: 1, 279 DirEntries: DirEntries{LANG_ENTRY}, 280 }) 281 dirs0[i0].NumberOfIdEntries++ 282 283 // calculate preceding DirEntry leaves, to find new index in Data & DataEntries 284 n := 0 285 for _, dir0 := range dirs0[:i0+1] { 286 n += len(dir0.DirEntries) //NOTE: assuming 1 language here; TODO: dwell deeper if more langs added 287 } 288 n-- 289 290 // insert new data in correct place 291 coff.DataEntries = append(coff.DataEntries[:n], append([]DataEntry{{Size1: uint32(data.Size())}}, coff.DataEntries[n:]...)...) 292 coff.Data = append(coff.Data[:n], append([]Sizer{data}, coff.Data[n:]...)...) 293} 294 295// Freeze fills in some important offsets in resulting file. 296func (coff *Coff) Freeze() { 297 switch coff.SectionHeader32.Name { 298 case STRING_RSRC: 299 coff.freezeRSRC() 300 case STRING_RDATA: 301 coff.freezeRDATA() 302 } 303} 304 305func (coff *Coff) freezeCommon1(path string, offset, diroff uint32) (newdiroff uint32) { 306 switch path { 307 case "/Dir": 308 coff.SectionHeader32.PointerToRawData = offset 309 diroff = offset 310 case "/Relocations": 311 coff.SectionHeader32.PointerToRelocations = offset 312 coff.SectionHeader32.SizeOfRawData = offset - diroff 313 case "/Symbols": 314 coff.FileHeader.PointerToSymbolTable = offset 315 } 316 return diroff 317} 318 319func freezeCommon2(v reflect.Value, offset *uint32) error { 320 if binutil.Plain(v.Kind()) { 321 *offset += uint32(binary.Size(v.Interface())) // TODO: change to v.Type().Size() ? 322 return nil 323 } 324 vv, ok := v.Interface().(Sizer) 325 if ok { 326 *offset += uint32(vv.Size()) 327 return binutil.WALK_SKIP 328 } 329 return nil 330} 331 332func (coff *Coff) freezeRDATA() { 333 var offset, diroff, stringsoff uint32 334 binutil.Walk(coff, func(v reflect.Value, path string) error { 335 diroff = coff.freezeCommon1(path, offset, diroff) 336 337 RE := regexp.MustCompile 338 const N = `\[(\d+)\]` 339 m := matcher{} 340 //TODO: adjust symbol pointers 341 //TODO: fill Symbols.Name, .Value 342 switch { 343 case m.Find(path, RE("^/Data"+N+"$")): 344 n := m[0] 345 coff.Symbols[1+n].Value = offset - diroff // FIXME: is it ok? 346 sz := uint64(coff.Data[n].Size()) 347 binary.LittleEndian.PutUint64(coff.Symbols[0].Auxiliaries[0][0:8], binary.LittleEndian.Uint64(coff.Symbols[0].Auxiliaries[0][0:8])+sz) 348 case path == "/StringsHeader": 349 stringsoff = offset 350 case m.Find(path, RE("^/Strings"+N+"$")): 351 binary.LittleEndian.PutUint32(coff.Symbols[m[0]+1].Name[4:8], offset-stringsoff) 352 } 353 354 return freezeCommon2(v, &offset) 355 }) 356 coff.SectionHeader32.PointerToRelocations = 0 357} 358 359func (coff *Coff) freezeRSRC() { 360 leafwalker := make(chan *DirEntry) 361 go func() { 362 for _, dir1 := range coff.Dir.Dirs { // resource type 363 for _, dir2 := range dir1.Dirs { // resource ID 364 for i := range dir2.DirEntries { // resource lang 365 leafwalker <- &dir2.DirEntries[i] 366 } 367 } 368 } 369 }() 370 371 var offset, diroff uint32 372 binutil.Walk(coff, func(v reflect.Value, path string) error { 373 diroff = coff.freezeCommon1(path, offset, diroff) 374 375 RE := regexp.MustCompile 376 const N = `\[(\d+)\]` 377 m := matcher{} 378 switch { 379 case m.Find(path, RE("^/Dir/Dirs"+N+"$")): 380 coff.Dir.DirEntries[m[0]].OffsetToData = MASK_SUBDIRECTORY | (offset - diroff) 381 case m.Find(path, RE("^/Dir/Dirs"+N+"/Dirs"+N+"$")): 382 coff.Dir.Dirs[m[0]].DirEntries[m[1]].OffsetToData = MASK_SUBDIRECTORY | (offset - diroff) 383 case m.Find(path, RE("^/DataEntries"+N+"$")): 384 direntry := <-leafwalker 385 direntry.OffsetToData = offset - diroff 386 case m.Find(path, RE("^/DataEntries"+N+"/OffsetToData$")): 387 coff.Relocations[m[0]].RVA = offset - diroff 388 case m.Find(path, RE("^/Data"+N+"$")): 389 coff.DataEntries[m[0]].OffsetToData = offset - diroff 390 } 391 392 return freezeCommon2(v, &offset) 393 }) 394} 395 396func mustAtoi(s string) int { 397 i, err := strconv.Atoi(s) 398 if err != nil { 399 panic(err) 400 } 401 return i 402} 403 404type matcher []int 405 406func (m *matcher) Find(s string, re *regexp.Regexp) bool { 407 subs := re.FindStringSubmatch(s) 408 if subs == nil { 409 return false 410 } 411 412 *m = (*m)[:0] 413 for i := 1; i < len(subs); i++ { 414 *m = append(*m, mustAtoi(subs[i])) 415 } 416 return true 417} 418