/ go / stringrecovery / stringrecovery.go
stringrecovery.go
  1  package stringrecovery
  2  
  3  import (
  4  	"debug/macho"
  5  	"debug/pe"
  6  	"encoding/binary"
  7  	"errors"
  8  	"fmt"
  9  	"io"
 10  	"os"
 11  	"strings"
 12  )
 13  
 14  var (
 15  	charset_english      = ` !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~` + "`"
 16  	lookup_table_english [256]bool
 17  )
 18  
 19  type (
 20  	Callback func(segment_name string, address uint64, token string)
 21  )
 22  
 23  func init() {
 24  	for _, c := range charset_english {
 25  		lookup_table_english[c] = true
 26  	}
 27  }
 28  
 29  func recover_section(try_align bool, word_size uint64, minimum_length int, section_name string, virtual_address uint64, section_reader io.ReaderAt, callback Callback) (err error) {
 30  	var (
 31  		offset int64
 32  	)
 33  
 34  	var (
 35  		current_token        strings.Builder
 36  		current_token_offset int64
 37  	)
 38  
 39  	for {
 40  		var b [1]byte
 41  		if _, err = section_reader.ReadAt(b[:], offset); err != nil {
 42  			if err == io.EOF {
 43  				err = nil
 44  				break
 45  			}
 46  		}
 47  		if b[0] == 0 {
 48  			// if current_token != "", this is a 0-terminator
 49  			// emit the token
 50  			if current_token.Len() > 0 {
 51  				if current_token.Len() < minimum_length {
 52  					current_token.Reset()
 53  					offset++
 54  					continue
 55  				}
 56  
 57  				align_offset := 0
 58  
 59  				if try_align {
 60  					for i := uint64(current_token_offset); (i % word_size) != 0; i++ {
 61  						current_token_offset++
 62  						align_offset++
 63  					}
 64  				}
 65  
 66  				current_token_string := current_token.String()
 67  				current_token_string = current_token_string[align_offset:]
 68  
 69  				callback(section_name, virtual_address+uint64(current_token_offset), current_token_string)
 70  				current_token.Reset()
 71  			}
 72  			offset++
 73  			continue
 74  		}
 75  		if lookup_table_english[b[0]] {
 76  			if current_token.Len() == 0 {
 77  				current_token_offset = offset
 78  			}
 79  			current_token.WriteByte(b[0])
 80  		} else {
 81  			// discard everything leaing up to this
 82  			current_token.Reset()
 83  		}
 84  		offset++
 85  	}
 86  
 87  	return
 88  }
 89  
 90  func recover_file_macho(word_size uint64, file *os.File, callback Callback) (err error) {
 91  	var (
 92  		macho_file *macho.File
 93  	)
 94  	macho_file, err = macho.NewFile(file)
 95  	if err != nil {
 96  		return
 97  	}
 98  	for _, section := range macho_file.Sections {
 99  		fmt.Fprintln(os.Stderr, "recovering", section.Name)
100  		switch section.Name {
101  		case "__cstring":
102  			if err = recover_section(false, word_size, 1, section.Name, section.Addr, section, callback); err != nil {
103  				return
104  			}
105  		case "__const":
106  			if err = recover_section(false, word_size, 4, section.Name, section.Addr, section, callback); err != nil {
107  				return
108  			}
109  		}
110  	}
111  	return
112  }
113  
114  func recover_file_pe(file *os.File, callback Callback) (err error) {
115  	var (
116  		pe_file *pe.File
117  	)
118  	pe_file, err = pe.NewFile(file)
119  	if err != nil {
120  		return
121  	}
122  	image_base := uint64(0x400000)
123  	var word_size uint64
124  
125  	switch h := pe_file.OptionalHeader.(type) {
126  	case *pe.OptionalHeader32:
127  		word_size = 4
128  		image_base = uint64(h.ImageBase)
129  	case *pe.OptionalHeader64:
130  		word_size = 8
131  		image_base = h.ImageBase
132  	}
133  
134  	for _, section := range pe_file.Sections {
135  		fmt.Fprintln(os.Stderr, "recovering", section.Name)
136  		switch section.Name {
137  		case ".data":
138  			if err = recover_section(true, word_size, 4, section.Name, image_base+uint64(section.VirtualAddress), section, callback); err != nil {
139  				return
140  			}
141  		case ".rdata":
142  			if err = recover_section(true, word_size, 4, section.Name, image_base+uint64(section.VirtualAddress), section, callback); err != nil {
143  				return
144  			}
145  		}
146  	}
147  
148  	return
149  }
150  
151  func RecoverFile(filename string, callback Callback) (err error) {
152  	var file *os.File
153  	file, err = os.Open(filename)
154  	if err != nil {
155  		return
156  	}
157  	var magic [4]byte
158  	if _, err = file.ReadAt(magic[:], 0); err != nil {
159  		return
160  	}
161  	magic_number := binary.LittleEndian.Uint32(magic[:])
162  	if magic[0] == 'M' && magic[1] == 'Z' {
163  		err = recover_file_pe(file, callback)
164  	} else if magic_number == 0xfeedface {
165  		err = recover_file_macho(4, file, callback)
166  	} else if magic_number == 0xfeedfacf {
167  		err = recover_file_macho(8, file, callback)
168  	} else if magic_number == 0xcefaedfe {
169  		err = recover_file_macho(4, file, callback)
170  	} else {
171  		err = errors.New("unknown file magic: " + filename)
172  	}
173  	return
174  }