Пример #1
0
func (loader *PELoader) loadPESection(
	ws *workspace.Workspace,
	mod *workspace.LoadedModule,
	section *pe.Section) error {

	h := section.SectionHeader

	logrus.Infof("section: %s", section.SectionHeader.Name)
	logrus.Infof("  virtual address: 0x%x", section.SectionHeader.VirtualAddress)
	logrus.Infof("  virtual size: 0x%x", section.SectionHeader.VirtualSize)
	logrus.Infof("  file offset: 0x%x", section.SectionHeader.Offset)
	logrus.Infof("  file size: 0x%x", section.SectionHeader.Size)

	rvaSecStart := AS.RVA(h.VirtualAddress)
	secStart := mod.VA(rvaSecStart)
	secLength := roundUpToPage(uint64(h.VirtualSize))
	e := ws.MemMap(secStart, secLength, fmt.Sprintf("%s/%s", mod.Name, section.SectionHeader.Name))
	check(e)

	d, e := section.Data()
	check(e)

	e = mod.MemWrite(ws, rvaSecStart, d)
	check(e)

	// TODO: apply permissions

	return nil
}
Пример #2
0
// findPrologues locates all instances of common x86 function
//   prologues in the given byteslice.
func findPrologues(d []byte) ([]AS.RVA, error) {
	ret := make([]AS.RVA, 0, 100)
	bare := make(map[AS.RVA]bool)

	// first, find prologues with hotpatch region
	hits, e := findAll(d, []byte{0x8B, 0xFF, 0x55, 0x8B, 0xEC}) // mov edi, edi; push ebp; mov ebp, esp
	check(e)

	// index the "bare" prologue start for future overlap query
	ret = append(ret, hits...)
	for _, hit := range hits {
		bare[AS.RVA(uint64(hit)+0x2)] = true
	}

	// now, find prologues without hotpatch region
	hits, e = findAll(d, []byte{0x55, 0x8B, 0xEC}) // push ebp; mov ebp, esp
	check(e)

	// and ensure they don't overlap with the hotpatchable prologues
	for _, hit := range hits {
		if _, ok := bare[hit]; ok {
			continue
		}
		ret = append(ret, hit)
	}

	return ret, nil
}
Пример #3
0
func (loader *PELoader) resolveImports(
	ws *workspace.Workspace,
	mod *workspace.LoadedModule,
	dataDirectory [16]pe.DataDirectory) error {

	// since we always map at ImageBase, we don't need to apply (32bit) relocs
	// TODO: check 64bit reloc types

	importDirectory := dataDirectory[1]
	importRva := AS.RVA(importDirectory.VirtualAddress)
	importSize := importDirectory.Size

	logrus.Infof("import rva: %s", importRva)
	logrus.Infof("import size: 0x%x", importSize)

	d, e := mod.MemRead(ws, AS.RVA(importDirectory.VirtualAddress), uint64(importDirectory.Size))
	check(e)

	p := bytes.NewBuffer(d)
	for {
		var dir ImageImportDirectory
		binary.Read(p, binary.LittleEndian, &dir.rvaOriginalThunkTable)
		logrus.Infof("rva import lookup table: 0x%x", dir.rvaOriginalThunkTable)
		if dir.rvaOriginalThunkTable == 0 {
			break
		}
		binary.Read(p, binary.LittleEndian, &dir.TimeDateStamp)
		logrus.Infof("time date stamp: 0x%x", dir.TimeDateStamp)

		binary.Read(p, binary.LittleEndian, &dir.ForwarderChain)
		logrus.Infof("forwarder chain: 0x%x", dir.ForwarderChain)

		binary.Read(p, binary.LittleEndian, &dir.rvaModuleName)

		moduleNameBuf, e := mod.MemRead(ws, AS.RVA(dir.rvaModuleName), 0x100)
		check(e)
		moduleName, e := readAscii(moduleNameBuf)
		check(e)

		logrus.Infof("module name: %s", string(moduleName))

		binary.Read(p, binary.LittleEndian, &dir.rvaThunkTable)
		loader.resolveThunkTable(ws, mod, moduleName, AS.RVA(dir.rvaThunkTable))
	}

	return nil
}
Пример #4
0
// note: rva is relative to the module
func (m LoadedModule) MemReadRva(ws *Workspace, rva AS.RVA) (AS.RVA, error) {
	// AS.RVAs are 32bits even on x64
	var data uint32
	d, e := m.MemRead(ws, rva, 0x4)
	if e != nil {
		return 0, e
	}

	p := bytes.NewBuffer(d)
	binary.Read(p, binary.LittleEndian, &data)
	return AS.RVA(uint64(data)), nil
}
Пример #5
0
// perhaps this should be moved into artifacts
func (ws *Workspace) ResolveAddressToSymbol(va AS.VA) (*LinkedSymbol, error) {
	for _, mod := range ws.LoadedModules {
		if va < mod.BaseAddress {
			continue
		}
		rva := AS.RVA(uint64(va) - uint64(mod.BaseAddress))
		sym, ok := mod.Imports[rva]
		if !ok {
			continue
		}
		return &sym, nil
	}

	return nil, ErrFailedToResolveImport
}
Пример #6
0
// findAll locates all instances of the given separator in
//  the given byteslice and returns the RVAs relative to the
//  start of the slice.
func findAll(d []byte, sep []byte) ([]AS.RVA, error) {
	var offset uint64
	ret := make([]AS.RVA, 0, 100)
	for {
		i := bytes.Index(d, sep)
		if i == -1 {
			break
		}

		ret = append(ret, AS.RVA(uint64(i)+offset))

		if i+len(sep) > len(d) {
			break
		}
		d = d[i+len(sep):]
		offset += uint64(i + len(sep))
	}
	return ret, nil
}
Пример #7
0
func (loader *PELoader) Load(ws *workspace.Workspace) (*workspace.LoadedModule, error) {
	var imageBase AS.VA
	var addressOfEntryPoint AS.RVA
	var dataDirectory [16]pe.DataDirectory

	if optionalHeader, ok := loader.file.OptionalHeader.(*pe.OptionalHeader32); ok {
		imageBase = AS.VA(optionalHeader.ImageBase)
		addressOfEntryPoint = AS.RVA(optionalHeader.AddressOfEntryPoint)
		dataDirectory = optionalHeader.DataDirectory
	} else {
		return nil, workspace.InvalidModeError
	}

	mod := &workspace.LoadedModule{
		Name:             loader.name,
		BaseAddress:      imageBase,
		EntryPoint:       addressOfEntryPoint.VA(imageBase),
		Imports:          map[AS.RVA]workspace.LinkedSymbol{},
		ExportsByName:    map[string]workspace.ExportedSymbol{},
		ExportsByOrdinal: map[uint16]workspace.ExportedSymbol{},
	}

	for _, section := range loader.file.Sections {
		e := loader.loadPESection(ws, mod, section)
		check(e)
	}

	e := loader.resolveImports(ws, mod, dataDirectory)
	check(e)

	e = loader.resolveExports(ws, mod, dataDirectory)
	check(e)

	e = ws.AddLoadedModule(mod)
	check(e)

	return mod, nil
}
Пример #8
0
func (loader *PELoader) resolveExports(
	ws *workspace.Workspace,
	mod *workspace.LoadedModule,
	dataDirectory [16]pe.DataDirectory) error {
	exportDirectory := dataDirectory[0]
	exportRva := AS.RVA(exportDirectory.VirtualAddress)
	exportSize := exportDirectory.Size

	logrus.Infof("export rva: %s", exportRva)
	logrus.Infof("export size: 0x%x", exportSize)

	d, e := mod.MemRead(ws, AS.RVA(exportDirectory.VirtualAddress), uint64(exportDirectory.Size))
	check(e)

	p := bytes.NewBuffer(d)
	var dir ImageExportDirectory

	binary.Read(p, binary.LittleEndian, &dir.Characteristics)
	binary.Read(p, binary.LittleEndian, &dir.TimeDateStamp)
	binary.Read(p, binary.LittleEndian, &dir.MajorVersion)
	binary.Read(p, binary.LittleEndian, &dir.MinorVersion)
	binary.Read(p, binary.LittleEndian, &dir.rvaName)
	binary.Read(p, binary.LittleEndian, &dir.Base)
	binary.Read(p, binary.LittleEndian, &dir.NumberOfFunctions)
	binary.Read(p, binary.LittleEndian, &dir.NumberOfNames)
	binary.Read(p, binary.LittleEndian, &dir.rvaAddressOfFunctions)
	binary.Read(p, binary.LittleEndian, &dir.rvaAddressOfNames)
	binary.Read(p, binary.LittleEndian, &dir.rvaAddressOfNameOrdinals)

	if dir.rvaAddressOfFunctions == 0 {
		panic("address of functions is NULL")
	}

	exportModuleNameBuf, e := mod.MemRead(ws, AS.RVA(dir.rvaName), 0x100)
	check(e)
	exportModuleName, e := readAscii(exportModuleNameBuf)
	logrus.Infof("export name: %s", string(exportModuleName))
	check(e)

	logrus.Infof("time date stamp: 0x%x", dir.TimeDateStamp)

	// note closure over dir, mod, ws
	readFunctionRva := func(i uint32) (AS.RVA, error) {
		if i > dir.NumberOfFunctions {
			panic("function index too large")
		}
		// sizeof(RVA) is always 4 bytes, even on x64
		return mod.MemReadRva(ws, AS.RVA(dir.rvaAddressOfFunctions+4*i))
	}

	// isForwardedExport returns true when the provided RVA falls within the
	//  export directory table, which is used to signify that an export is
	//  fowarded to another module.
	// implementation: note closure over loader
	isForwardedExport := func(rvaFn AS.RVA) bool {
		if uint32(rvaFn) < exportDirectory.VirtualAddress {
			return false
		}
		if uint32(rvaFn) >= exportDirectory.VirtualAddress+exportDirectory.Size {
			return false
		}
		return true
	}

	// implementation: node closure over mod, ws
	readForwardedSymbol := func(rvaFn AS.RVA) (workspace.LinkedSymbol, error) {
		var forwardedSymbol workspace.LinkedSymbol
		forwardedNameBuf, e := mod.MemRead(ws, AS.RVA(rvaFn), 0x100)
		check(e)
		forwardedName, e := readAscii(forwardedNameBuf)
		check(e)

		i := strings.LastIndex(forwardedName, ".")
		if i == -1 {
			panic("expected to find a '.' in the module name")
		}
		if i >= len(forwardedName) {
			panic("module name ends in period")
		}

		forwardedSymbol.ModuleName = forwardedName[:i]
		forwardedSymbol.SymbolName = forwardedName[i+1:]
		return forwardedSymbol, nil
	}

	// resolve exports by ordinals first
	for i := uint32(0); i < dir.NumberOfFunctions; i++ {
		ordinal := uint16(i + dir.Base)
		rvaFn, e := readFunctionRva(i)
		check(e)

		isForwarded := isForwardedExport(rvaFn)

		sym := workspace.ExportedSymbol{
			RVA:         rvaFn,
			IsForwarded: isForwarded,
		}

		if isForwarded {
			fsym, e := readForwardedSymbol(rvaFn)
			check(e)
			sym.ForwardedSymbol = fsym
		}
		mod.ExportsByOrdinal[ordinal] = sym

		if isForwarded {
			logrus.Infof(" export: (ordinal) %x: %s -> %s.%s",
				ordinal, rvaFn, sym.ForwardedSymbol.ModuleName, sym.ForwardedSymbol.SymbolName)
		} else {
			logrus.Infof(" export: (ordinal) %x: %s", ordinal, rvaFn)
		}
	}

	// resolve exports by name
	for i := uint32(0); i < dir.NumberOfNames; i++ {
		// sizeof(RVA) is always 4 bytes, even on x64
		rvaName, e := mod.MemReadRva(ws, AS.RVA(dir.rvaAddressOfNames+4*i))
		check(e)
		// sizeof(ordinal) is always 2 bytes
		nameOrdinal, e := mod.MemReadShort(ws, AS.RVA(dir.rvaAddressOfNameOrdinals+2*i))
		check(e)
		rvaFn, e := readFunctionRva(uint32(nameOrdinal))
		check(e)

		nameBuf, e := mod.MemRead(ws, AS.RVA(rvaName), 0x100)
		check(e)
		name, e := readAscii(nameBuf)
		check(e)

		isForwarded := isForwardedExport(rvaFn)

		sym := workspace.ExportedSymbol{
			RVA:         rvaFn,
			IsForwarded: isForwarded,
		}

		if isForwarded {
			fsym, e := readForwardedSymbol(rvaFn)
			check(e)
			sym.ForwardedSymbol = fsym
		}
		mod.ExportsByName[name] = sym

		if isForwarded {
			logrus.Infof(" export: %s: %s -> %s.%s",
				name, rvaFn, sym.ForwardedSymbol.ModuleName, sym.ForwardedSymbol.SymbolName)
		} else {
			logrus.Infof(" export: %s: %s", name, rvaFn)
		}
	}

	return nil
}