Robert Sloan | 8ff0355 | 2017-06-14 12:40:58 -0700 | [diff] [blame] | 1 | // Copyright (c) 2017, Google Inc. |
| 2 | // |
| 3 | // Permission to use, copy, modify, and/or distribute this software for any |
| 4 | // purpose with or without fee is hereby granted, provided that the above |
| 5 | // copyright notice and this permission notice appear in all copies. |
| 6 | // |
| 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| 10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| 12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| 13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| 14 | |
| 15 | // break-hash parses an ELF binary containing the FIPS module and corrupts the |
| 16 | // first byte of the module. This should cause the integrity check to fail. |
| 17 | package main |
| 18 | |
| 19 | import ( |
| 20 | "bytes" |
| 21 | "crypto/hmac" |
| 22 | "crypto/sha512" |
| 23 | "debug/elf" |
| 24 | "encoding/hex" |
| 25 | "errors" |
| 26 | "fmt" |
| 27 | "io/ioutil" |
| 28 | "os" |
| 29 | ) |
| 30 | |
| 31 | func do(outPath, inPath string) error { |
| 32 | objectBytes, err := ioutil.ReadFile(inPath) |
| 33 | if err != nil { |
| 34 | return err |
| 35 | } |
| 36 | |
| 37 | object, err := elf.NewFile(bytes.NewReader(objectBytes)) |
| 38 | if err != nil { |
| 39 | return errors.New("failed to parse object: " + err.Error()) |
| 40 | } |
| 41 | |
| 42 | // Find the .text section. |
| 43 | var textSection *elf.Section |
| 44 | var textSectionIndex elf.SectionIndex |
| 45 | for i, section := range object.Sections { |
| 46 | if section.Name == ".text" { |
| 47 | textSectionIndex = elf.SectionIndex(i) |
| 48 | textSection = section |
| 49 | break |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | if textSection == nil { |
| 54 | return errors.New("failed to find .text section in object") |
| 55 | } |
| 56 | |
| 57 | symbols, err := object.Symbols() |
| 58 | if err != nil { |
| 59 | return errors.New("failed to parse symbols: " + err.Error()) |
| 60 | } |
| 61 | |
| 62 | // Find the start and end markers of the module. |
| 63 | var startSeen, endSeen bool |
| 64 | var start, end uint64 |
| 65 | |
| 66 | for _, symbol := range symbols { |
| 67 | if symbol.Section != textSectionIndex { |
| 68 | continue |
| 69 | } |
| 70 | |
| 71 | switch symbol.Name { |
| 72 | case "BORINGSSL_bcm_text_start": |
| 73 | if startSeen { |
| 74 | return errors.New("duplicate start symbol found") |
| 75 | } |
| 76 | startSeen = true |
| 77 | start = symbol.Value |
| 78 | case "BORINGSSL_bcm_text_end": |
| 79 | if endSeen { |
| 80 | return errors.New("duplicate end symbol found") |
| 81 | } |
| 82 | endSeen = true |
| 83 | end = symbol.Value |
| 84 | default: |
| 85 | continue |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | if !startSeen || !endSeen { |
| 90 | return errors.New("could not find module in object") |
| 91 | } |
| 92 | |
| 93 | moduleText := make([]byte, end-start) |
| 94 | if n, err := textSection.ReadAt(moduleText, int64(start-textSection.Addr)); err != nil { |
| 95 | return fmt.Errorf("failed to read from module start (at %d of %d) in .text: %s", start, textSection.Size, err) |
| 96 | } else if n != len(moduleText) { |
| 97 | return fmt.Errorf("short read from .text: wanted %d, got %d", len(moduleText), n) |
| 98 | } |
| 99 | |
| 100 | // In order to match up the module start with the raw ELF contents, |
| 101 | // search for the first 256 bytes and assume that will be unique. |
| 102 | offset := bytes.Index(objectBytes, moduleText[:256]) |
| 103 | if offset < 0 { |
| 104 | return errors.New("did not find module prefix in object file") |
| 105 | } |
| 106 | |
| 107 | if bytes.Index(objectBytes[offset+1:], moduleText[:256]) >= 0 { |
| 108 | return errors.New("found two occurrences of prefix in object file") |
| 109 | } |
| 110 | |
| 111 | // Corrupt the module in the ELF. |
| 112 | objectBytes[offset] ^= 1 |
| 113 | |
| 114 | // Calculate the before and after hash of the module. |
| 115 | var zeroKey [64]byte |
| 116 | mac := hmac.New(sha512.New, zeroKey[:]) |
| 117 | mac.Write(moduleText) |
| 118 | hashWas := mac.Sum(nil) |
| 119 | |
| 120 | moduleText[0] ^= 1 |
| 121 | mac.Reset() |
| 122 | mac.Write(moduleText) |
| 123 | newHash := mac.Sum(nil) |
| 124 | |
| 125 | fmt.Printf("Found start of module at offset 0x%x (VMA 0x%x):\n", start-textSection.Addr, start) |
| 126 | fmt.Printf(hex.Dump(moduleText[:128])) |
| 127 | fmt.Printf("\nHash of module was: %x\n", hashWas) |
| 128 | fmt.Printf("Hash of corrupted module is: %x\n", newHash) |
| 129 | |
| 130 | return ioutil.WriteFile(outPath, objectBytes, 0755) |
| 131 | } |
| 132 | |
| 133 | func main() { |
| 134 | if len(os.Args) != 3 { |
| 135 | usage() |
| 136 | os.Exit(1) |
| 137 | } |
| 138 | |
| 139 | if err := do(os.Args[2], os.Args[1]); err != nil { |
| 140 | fmt.Fprintf(os.Stderr, "%s\n", err) |
| 141 | os.Exit(1) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | func usage() { |
| 146 | fmt.Fprintf(os.Stderr, "Usage: %s <input binary> <output path>\n", os.Args[0]) |
| 147 | } |