├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── common ├── common.go ├── common_linux.go └── common_linux_test.go ├── cresponse ├── README.md ├── cresponse.go └── response.h ├── examples ├── listlibs ├── listlibs.go ├── memsearch ├── memsearch.go ├── pgrep └── pgrep.go ├── listlibs ├── listlibs.go ├── listlibs_darwin.c ├── listlibs_darwin.go ├── listlibs_darwin.h ├── listlibs_linux.go ├── listlibs_windows.c ├── listlibs_windows.go └── listlibs_windows.h ├── memaccess ├── memaccess.go ├── memaccess.h ├── memaccess_c_wrapper.go ├── memaccess_darwin.c ├── memaccess_linux.go ├── memaccess_test.go └── memaccess_windows.c ├── memsearch ├── memsearch.go └── memsearch_test.go ├── process ├── process.go ├── process.h ├── process_c_wrapper.go ├── process_darwin.c ├── process_darwin.go ├── process_linux.go ├── process_test.go ├── process_windows.c ├── process_windows.go └── process_windows.h └── test ├── test.go └── tools ├── Makefile └── known_byte_sequences.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | #test cases 23 | /test/tools/* 24 | !/test/tools/known_byte_sequence.c 25 | 26 | #ctags 27 | /tags 28 | 29 | #vagrant 30 | Vagrantfile 31 | .vagrant 32 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTBINDIR=test/tools 2 | TESTS=./memaccess ./memsearch ./process ./common 3 | 4 | all: get run_tests64 run_tests32 5 | 6 | get: 7 | go get -u github.com/mozilla/masche/process 8 | go get -u github.com/mozilla/masche/memsearch 9 | go get -u github.com/mozilla/masche/memaccess 10 | go get -u github.com/mozilla/masche/listlibs 11 | 12 | lint: 13 | golint github.com/mozilla/masche/... 14 | 15 | vet: 16 | go vet github.com/mozilla/masche/... 17 | 18 | run_tests64: testbin64 19 | go test $(TESTS) 20 | 21 | testbin64: 22 | $(MAKE) -C $(TESTBINDIR) test64 23 | 24 | run_tests32: testbin32 25 | go test $(TESTS) 26 | 27 | testbin32: 28 | $(MAKE) -C $(TESTBINDIR) test32 29 | 30 | clean: 31 | go clean $(TESTS) 32 | $(MAKE) -C $(TESTBINDIR) clean 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MASCHE 2 | ====== 3 | ![MASCHE image (Javier Mascherano)](http://i.imgur.com/V3EMjswm.jpg) 4 | **MIG Memory Forensic library** 5 | 6 | ⚠️ Deprecation Notice ⚠️ 7 | ------------------------- 8 | 9 | Mozilla is no longer maintaining the Mozilla InvestiGator (MIG) project. 10 | 11 | Mozilla is also no longer making use of this code internally. 12 | 13 | You are welcome to use this code as is with no warranty. Please fork it to continue development. 14 | 15 | **MASCHE** stands for **Memory Analysis Suite for Checking the Harmony of Endpoints**. It is being developed as a project for the *Mozilla Winter of Security program*. 16 | 17 | It works on **Linux**, **Mac OS** and **Windows**. 18 | 19 | These are the current features: 20 | 21 | * listlibs: Searches for processes that have loaded a certain library. 22 | * pgrep: Has the same functionallity as pgrep on linux. 23 | * memaccess/memsearch: Allows access and search into a given process memory. 24 | 25 | You can find examples under the examples folder. 26 | 27 | ## Compiling 28 | 29 | You need `golang` installed. 30 | 31 | ### Linux 32 | You need glibc for 64 and 32 bits installed. On Fedora, the packages are: 33 | * glibc-devel.i686 34 | * glibc-devel.x86_64 35 | * glibc-headers.i686 36 | * glibc-headers.x86_64 37 | * glibc.i686 38 | * glibc.x86_64 39 | 40 | ### Windows 41 | 42 | In order to compile and run masche in windows you will need a gcc compiler. You can use mingw if you are running a 32 bits version of Windows or mingw-64 if you are running a 64 bits one. 43 | Just run `go build` on the package/example that you want. 44 | 45 | It's possible to cross-compile from linux. And this is the recommended way. 46 | * Install a cross compiler (for example, `mingw-w64`) 47 | * Enable cross compiling in your go toolchain (run `GOOS=windows ./all.bash` inside your `$GOROOT/src` folder) 48 | 49 | After that you should be able to cross compile masche without problems, just make sure to export the correct global variables: `GOOS=windows` `CGO_ENABLED=1` `CC=` (for example: `CC=x86_64-w64-ming32-gcc` ) 50 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | -------------------------------------------------------------------------------- /common/common_linux.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // MapsFilePathFromPid returns the memory maps file path for a given process id. 11 | func MapsFilePathFromPid(pid uint) string { 12 | return filepath.Join("/proc", fmt.Sprintf("%d", pid), "maps") 13 | } 14 | 15 | // MemFilePathFromPid method returns the path of the process' memory file. 16 | func MemFilePathFromPid(pid uint) string { 17 | return filepath.Join("/proc", fmt.Sprintf("%d", pid), "mem") 18 | } 19 | 20 | //ParseMapsFileMemoryLimits parses the memory limits of a mapping as found in /proc/PID/maps 21 | func ParseMapsFileMemoryLimits(limits string) (start uintptr, end uintptr, err error) { 22 | fields := strings.Split(limits, "-") 23 | if len(fields) != 2 { 24 | return 0, 0, fmt.Errorf("Invalid memory limits, it must have two hexa numbers separeted by a single -") 25 | } 26 | 27 | start64, err := strconv.ParseUint(fields[0], 16, 64) 28 | if err != nil { 29 | return 0, 0, err 30 | } 31 | start = uintptr(start64) 32 | 33 | end64, err := strconv.ParseUint(fields[1], 16, 64) 34 | if err != nil { 35 | return 0, 0, err 36 | } 37 | end = uintptr(end64) 38 | 39 | return 40 | } 41 | 42 | // SplitMapsFileEntry method splits a line of the maps files returning a slice with an element for each of its parts. 43 | func SplitMapsFileEntry(entry string) []string { 44 | res := make([]string, 0, 6) 45 | for i := 0; i < 5; i++ { 46 | if strings.Index(entry, " ") != -1 { 47 | res = append(res, entry[0:strings.Index(entry, " ")]) 48 | entry = entry[strings.Index(entry, " ")+1:] 49 | } else { 50 | res = append(res, entry, "") 51 | return res 52 | } 53 | } 54 | res = append(res, strings.TrimLeft(entry, " ")) 55 | return res 56 | } 57 | -------------------------------------------------------------------------------- /common/common_linux_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSplitMapsFileEntry(t *testing.T) { 8 | var entries = []string{ 9 | "7fb8faf65000-7fb8faf66000 rw-p 00023000 08:01 922969 /lib/x86_64-linux-gnu/ld-2.19.so", 10 | "7fb8faf65000-7fb8faf66000 rw-p 00023000 08:01 922969 /lib/x86_64-linux-gnu/with spaces.so", 11 | "7fb8faf66000-7fb8faf67000 rw-p 00000000 00:00 0", 12 | "7fff231a6000-7fff231c7000 rw-p 00000000 00:00 0 [stack]", 13 | "7fff231a6000-7fff231c7000 rw-p 00000000 00:00 0 [stack]", 14 | } 15 | 16 | var results = [][]string{ 17 | []string{"7fb8faf65000-7fb8faf66000", "rw-p", "00023000", "08:01", "922969", 18 | "/lib/x86_64-linux-gnu/ld-2.19.so"}, 19 | []string{"7fb8faf65000-7fb8faf66000", "rw-p", "00023000", "08:01", "922969", 20 | "/lib/x86_64-linux-gnu/with spaces.so"}, 21 | []string{"7fb8faf66000-7fb8faf67000", "rw-p", "00000000", "00:00", "0", ""}, 22 | []string{"7fff231a6000-7fff231c7000", "rw-p", "00000000", "00:00", "0", "[stack]"}, 23 | []string{"7fff231a6000-7fff231c7000", "rw-p", "00000000", "00:00", "0", "[stack]"}, 24 | } 25 | 26 | for i, entry := range entries { 27 | splitted := SplitMapsFileEntry(entry) 28 | if !compareStringSlices(results[i], splitted) { 29 | t.Error("Error splitting map entry", entry, " - Expected:", results[i], " - Got: ", splitted) 30 | } 31 | } 32 | } 33 | 34 | func TestParseMapsFileMemoryLimits(t *testing.T) { 35 | var memLimits = []string{ 36 | "7fb8faf65000-7fb8faf66000", 37 | "7fff231a6000-7fff231c7000", 38 | } 39 | 40 | var results = [][]uintptr{ 41 | []uintptr{0x7fb8faf65000, 0x7fb8faf66000}, 42 | []uintptr{0x7fff231a6000, 0x7fff231c7000}, 43 | } 44 | 45 | for i, limits := range memLimits { 46 | start, end, err := ParseMapsFileMemoryLimits(limits) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if results[i][0] != start { 52 | t.Error("expected ", results[i][0], " and got ", start) 53 | } 54 | 55 | if results[i][1] != end { 56 | t.Error("expected ", results[i][1], " and got ", end) 57 | } 58 | } 59 | 60 | var invalidMemoryLimits = []string{ 61 | "a", 62 | "aa-", 63 | "-a", 64 | "NonAlpha-1", 65 | "1-NonAlpha", 66 | "1-1-1", 67 | } 68 | 69 | for _, limits := range invalidMemoryLimits { 70 | _, _, err := ParseMapsFileMemoryLimits(limits) 71 | if err == nil { 72 | t.Error("an error should have been returned when parsing ", limits) 73 | } 74 | } 75 | 76 | } 77 | 78 | func compareStringSlices(a []string, b []string) bool { 79 | if len(a) != len(b) { 80 | return false 81 | } 82 | 83 | for i := range a { 84 | if a[i] != b[i] { 85 | return false 86 | } 87 | } 88 | 89 | return true 90 | } 91 | -------------------------------------------------------------------------------- /cresponse/README.md: -------------------------------------------------------------------------------- 1 | C Response module 2 | ================= 3 | 4 | This module defines a C response_t type and functions for working with it. 5 | 6 | It's intention is to be used as a way to communicate Go and C in a unified way 7 | across the project. 8 | -------------------------------------------------------------------------------- /cresponse/cresponse.go: -------------------------------------------------------------------------------- 1 | package cresponse 2 | 3 | // #include "response.h" 4 | // #cgo CFLAGS: -std=c99 5 | import "C" 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "unsafe" 11 | ) 12 | 13 | // CError is the Go represnentation of response.h's error_t. 14 | type CError struct { 15 | number int 16 | description string 17 | } 18 | 19 | func (err CError) Error() string { 20 | return fmt.Sprintf("System error number %d: %s", err.number, err.description) 21 | } 22 | 23 | // GetResponsesErrors returns the Go representation of the errors present in a C.response_t. 24 | // 25 | // NOTE: cgo types are private to each module, so exporting a function that expects a *C.response_t doesn't make sense, 26 | // so we export a function with an unsafe.Pointer and we cast it internally. 27 | func GetResponsesErrors(responsePointer unsafe.Pointer) (softerrors []error, harderror error) { 28 | response := (*C.response_t)(responsePointer) 29 | if response.fatal_error != nil && int(response.fatal_error.error_number) != 0 { 30 | harderror = cErrorFromErrorT(*response.fatal_error) 31 | } else { 32 | harderror = nil 33 | } 34 | 35 | softerrorsCount := int(response.soft_errors_count) 36 | softerrors = make([]error, 0, softerrorsCount) 37 | 38 | cSoftErrorsHeader := reflect.SliceHeader{ 39 | Data: uintptr(unsafe.Pointer(response.soft_errors)), 40 | Len: softerrorsCount, 41 | Cap: softerrorsCount, 42 | } 43 | cSoftErrors := *(*[]C.error_t)(unsafe.Pointer(&cSoftErrorsHeader)) 44 | 45 | for _, cErr := range cSoftErrors { 46 | softerrors = append(softerrors, cErrorFromErrorT(cErr)) 47 | } 48 | 49 | return 50 | } 51 | 52 | func cErrorFromErrorT(err C.error_t) CError { 53 | return CError{ 54 | number: int(err.error_number), 55 | description: C.GoString(err.description), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cresponse/response.h: -------------------------------------------------------------------------------- 1 | #ifndef RESPONSE_H 2 | #define RESPONSE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * This struct represents an error. 10 | * 11 | * error_number is the error as returned by the OS, 0 for no error. 12 | * description is a malloc'ed null-terminated string. 13 | **/ 14 | typedef struct { 15 | int error_number; 16 | char *description; 17 | } error_t; 18 | 19 | #ifdef _WIN32 20 | 21 | #include 22 | 23 | /** 24 | * error_create receives an Windows Error Code and returns an error_t with 25 | * that number and its description. 26 | * 27 | * A common usage for this function is error_t *err = error_create(GetLastError()); 28 | **/ 29 | static error_t *error_create(DWORD error_number) { 30 | error_t *err = calloc(1, sizeof * err); 31 | err->error_number = error_number; 32 | 33 | FormatMessage( 34 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 35 | FORMAT_MESSAGE_FROM_SYSTEM | 36 | FORMAT_MESSAGE_IGNORE_INSERTS, 37 | NULL, 38 | error_number, 39 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 40 | (LPTSTR) & (err->description), 41 | 0, NULL ); 42 | 43 | return err; 44 | } 45 | #endif /* _WIN32 */ 46 | 47 | /** 48 | * Frees an error_t. 49 | **/ 50 | static void error_free(error_t *error) { 51 | if (error == NULL) { 52 | return; 53 | } 54 | 55 | #ifdef _WIN32 56 | // In win32 errors are created with FormatMessage and 57 | // must be freed with LocalFree 58 | LocalFree(error->description); 59 | #else 60 | free(error->description); 61 | #endif 62 | free(error); 63 | } 64 | 65 | /** 66 | * This struct represents the error releated parts of a response to a function 67 | * call. 68 | * 69 | * fatal_error may point to an error_t that made the operation fail or be NULL. 70 | * soft_errors may be an array of non-fatal errors or be NULL. 71 | * soft_errors_count is the number errors in soft_errors (if no array, a 0). 72 | * soft_errors_capaciy is the syze of the soft_errors array (if no array, a 0). 73 | **/ 74 | typedef struct { 75 | error_t *fatal_error; 76 | error_t *soft_errors; 77 | size_t soft_errors_count; 78 | size_t soft_errors_capacity; 79 | } response_t; 80 | 81 | /** 82 | * IMPORTANT NOTE: This functions are implemented in a .h because of a bug or 83 | * unsuported feature in the OS X's version of cgo. There is no way to make 84 | * other modules to compile a C file from here and link themselves against it. 85 | **/ 86 | 87 | 88 | /** 89 | * Creates a new response without any error. 90 | **/ 91 | static response_t *response_create() { 92 | return calloc(1, sizeof(response_t)); 93 | } 94 | 95 | /** 96 | * Releases the resources used by an error response_t, including all error_t's 97 | * resources. 98 | **/ 99 | static void response_free(response_t *response) { 100 | if (response == NULL) { 101 | return; 102 | } 103 | 104 | error_free(response->fatal_error); 105 | if (response->soft_errors != NULL) { 106 | for (size_t i = 0; i < response->soft_errors_count; i++) { 107 | free(response->soft_errors[i].description); 108 | } 109 | free(response->soft_errors); 110 | } 111 | 112 | free(response); 113 | } 114 | 115 | /** 116 | * Sets a response's fatal error. 117 | * 118 | * description is a malloc'ed null-terminated string. 119 | * NOTE: The response MUST NOT have a fatal error already set. 120 | **/ 121 | static void response_set_fatal_error(response_t *response, int error_number, 122 | char *description) { 123 | assert(response->fatal_error == NULL); 124 | response->fatal_error = malloc(sizeof(*response->fatal_error)); 125 | response->fatal_error->error_number = error_number; 126 | response->fatal_error->description = description; 127 | } 128 | 129 | /** 130 | * Adds a soft error to a response. 131 | * 132 | * description is a malloc'ed null-terminated string. 133 | **/ 134 | static void response_add_soft_error(response_t *response, int error_number, 135 | char *description) { 136 | 137 | #define SOFT_ERRORS_INITIAL_CAPACITY 2 138 | #define SOFT_ERRORS_REALLOCATION_FACTOR 2 139 | 140 | if (response->soft_errors_capacity == 0) { 141 | response->soft_errors_count = 0; 142 | response->soft_errors_capacity = SOFT_ERRORS_INITIAL_CAPACITY; 143 | response->soft_errors = calloc(SOFT_ERRORS_INITIAL_CAPACITY, 144 | sizeof(*response->soft_errors)); 145 | } 146 | 147 | if (response->soft_errors_count == response->soft_errors_capacity) { 148 | response->soft_errors_capacity *= SOFT_ERRORS_REALLOCATION_FACTOR; 149 | response->soft_errors = realloc(response->soft_errors, 150 | response->soft_errors_capacity * 151 | sizeof(*response->soft_errors)); 152 | } 153 | 154 | response->soft_errors[response->soft_errors_count].error_number = 155 | error_number; 156 | response->soft_errors[response->soft_errors_count].description = 157 | description; 158 | response->soft_errors_count++; 159 | } 160 | 161 | #ifdef __MACH__ 162 | 163 | #include 164 | 165 | static void response_set_fatal_from_kret(response_t *response, 166 | kern_return_t error_number) { 167 | response_set_fatal_error(response, (int) error_number, 168 | strdup(mach_error_string(error_number))); 169 | } 170 | 171 | #endif /* __MACH__ */ 172 | 173 | #endif /* RESPONSE_H */ 174 | -------------------------------------------------------------------------------- /examples/listlibs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masche/2aa56d126480ee35164c66bcd43ced356f4d484e/examples/listlibs -------------------------------------------------------------------------------- /examples/listlibs.go: -------------------------------------------------------------------------------- 1 | // This program can be used to check if any process is running a given dynamic library. 2 | // The -r flag specifies a regexp over the filename of the library, for example: 3 | // ./prueba -r="libc" will match all programs that have the libc loaded as a dynamic library. 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "log" 10 | "regexp" 11 | 12 | "github.com/mozilla/masche/listlibs" 13 | "github.com/mozilla/masche/process" 14 | ) 15 | 16 | var rstr = flag.String("r", "", "library name regexp") 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | r, err := regexp.Compile(*rstr) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | ps, softs, hard := process.OpenAll() 27 | if hard != nil { 28 | log.Fatal(hard) 29 | } 30 | defer process.CloseAll(ps) 31 | for _, e := range softs { 32 | log.Println(e) 33 | } 34 | 35 | matches, softs, hard := findProcWithLib(r, ps) 36 | if hard != nil { 37 | log.Fatal(hard) 38 | } 39 | for _, e := range softs { 40 | fmt.Println(e) 41 | } 42 | 43 | fmt.Printf("Processes matching: %s\n", *rstr) 44 | for p, libs := range matches { 45 | n, hard, _ := p.Name() 46 | if hard != nil { 47 | log.Fatal(hard) 48 | } 49 | fmt.Printf("[%d] %s\n", p.Pid(), n) 50 | for _, l := range libs { 51 | fmt.Printf("\t%s\n", l) 52 | } 53 | } 54 | 55 | } 56 | 57 | func findProcWithLib(r *regexp.Regexp, ps []process.Process) (matches map[process.Process][]string, softerrors []error, harderror error) { 58 | matches = make(map[process.Process][]string) 59 | softerrors = make([]error, 0) 60 | for _, p := range ps { 61 | libs, softs, hard := listlibs.GetMatchingLoadedLibraries(p, r) 62 | if hard != nil { 63 | return nil, softerrors, hard 64 | } 65 | softerrors = append(softerrors, softs...) 66 | if len(libs) != 0 { 67 | matches[p] = libs 68 | } 69 | } 70 | return matches, softerrors, nil 71 | } 72 | -------------------------------------------------------------------------------- /examples/memsearch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masche/2aa56d126480ee35164c66bcd43ced356f4d484e/examples/memsearch -------------------------------------------------------------------------------- /examples/memsearch.go: -------------------------------------------------------------------------------- 1 | // This is an example program that shows the usage of the memsearch package. 2 | // 3 | // With this program you can: 4 | // - Search for a string in the memory of a process with a given PID 5 | // - Print an arbitrary amount of bytes from the process memory. 6 | package main 7 | 8 | import ( 9 | "encoding/hex" 10 | "flag" 11 | "io/ioutil" 12 | "log" 13 | "regexp" 14 | "strings" 15 | 16 | "github.com/mozilla/masche/memaccess" 17 | "github.com/mozilla/masche/memsearch" 18 | "github.com/mozilla/masche/process" 19 | ) 20 | 21 | var ( 22 | action = flag.String("action", "", "Action to perfom. One of: search, regexp-search, file-search, print") 23 | pid = flag.Int("pid", 0, "Process id to analyze") 24 | addr = flag.Int("addr", 0x0, "The initial address in the process address space to search/print") 25 | 26 | // print action flags 27 | size = flag.Int("n", 4, "Amount of bytes to print") 28 | 29 | // search action flags 30 | needle = flag.String("needle", "Find This!", "String to search for (interpreted as []byte)") 31 | 32 | // regexp-search action flags 33 | regexpString = flag.String("regexp", "regexp?", "Regexp to search for") 34 | 35 | // file-search action flags 36 | fileneedle = flag.String("fileneedle", "example.in", "Filename that contains hex-encoded needle (spaces are ignored)") 37 | ) 38 | 39 | func logErrors(softerrors []error, harderror error) { 40 | if harderror != nil { 41 | log.Fatal(harderror) 42 | } 43 | for _, soft := range softerrors { 44 | log.Print(soft) 45 | } 46 | } 47 | 48 | func main() { 49 | flag.Parse() 50 | 51 | proc, softerrors, harderror := process.OpenFromPid(uint(*pid)) 52 | logErrors(softerrors, harderror) 53 | 54 | switch *action { 55 | 56 | case "": 57 | log.Fatal("Missing action flag.") 58 | case "file-search": 59 | data, err := ioutil.ReadFile(*fileneedle) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | encoded := strings.Replace(strings.Replace(strings.TrimSpace(string(data)), " ", "", -1), "\n", "", -1) 64 | data, err = hex.DecodeString(encoded) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | found, address, softerrors, harderror := memsearch.FindBytesSequence(proc, uintptr(*addr), data) 69 | logErrors(softerrors, harderror) 70 | if found { 71 | log.Printf("Found in address: %x\n", address) 72 | } 73 | 74 | case "search": 75 | found, address, softerrors, harderror := memsearch.FindBytesSequence(proc, uintptr(*addr), []byte(*needle)) 76 | logErrors(softerrors, harderror) 77 | if found { 78 | log.Printf("Found in address: %x\n", address) 79 | } 80 | 81 | case "regexp-search": 82 | r, err := regexp.Compile(*regexpString) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | found, address, softerrors, harderror := memsearch.FindRegexpMatch(proc, uintptr(*addr), r) 88 | logErrors(softerrors, harderror) 89 | if found { 90 | log.Printf("Found in address: %x\n", address) 91 | } 92 | 93 | case "print": 94 | buf := make([]byte, *size) 95 | softerrors, harderror = memaccess.CopyMemory(proc, uintptr(*addr), buf) 96 | logErrors(softerrors, harderror) 97 | log.Println(string(buf)) 98 | 99 | default: 100 | log.Fatal("Unrecognized action ", *action) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/pgrep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/masche/2aa56d126480ee35164c66bcd43ced356f4d484e/examples/pgrep -------------------------------------------------------------------------------- /examples/pgrep.go: -------------------------------------------------------------------------------- 1 | // This program provides a functionality similar to what `pgrep` does un Linux: 2 | // it lists all the processes whose name matches a given regexp. 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "github.com/mozilla/masche/process" 9 | "log" 10 | "regexp" 11 | ) 12 | 13 | var reg = flag.String("r", ".*", "Regular Expression to use.") 14 | 15 | func main() { 16 | flag.Parse() 17 | 18 | r, err := regexp.Compile(*reg) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | ps, soft, hard := process.OpenByName(r) 24 | if hard != nil { 25 | log.Fatal(err) 26 | } 27 | if soft != nil { 28 | for _, err := range soft { 29 | log.Println(err) 30 | } 31 | } 32 | 33 | for _, p := range ps { 34 | name, soft, hard := p.Name() 35 | if hard != nil { 36 | log.Fatal(err) 37 | } 38 | if soft != nil { 39 | for _, err := range soft { 40 | log.Println(err) 41 | } 42 | } 43 | fmt.Printf("Process: %s\nPid: %d\n\n", name, p.Pid()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /listlibs/listlibs.go: -------------------------------------------------------------------------------- 1 | package listlibs 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/mozilla/masche/process" 7 | ) 8 | 9 | // ListLoadedLibraries lists all the libraries (their absolute paths) loaded by a process. 10 | func ListLoadedLibraries(p process.Process) (libraries []string, softerrors []error, harderror error) { 11 | return listLoadedLibraries(p) 12 | } 13 | 14 | // GetMatchingLoadedLibraries lists the libraries loaded by process p whose path matches r. 15 | func GetMatchingLoadedLibraries(p process.Process, r *regexp.Regexp) (libraries []string, softerrors []error, 16 | harderror error, 17 | ) { 18 | 19 | allLibraries, softerrors, harderror := ListLoadedLibraries(p) 20 | if harderror != nil { 21 | return 22 | } 23 | 24 | for _, lib := range allLibraries { 25 | if r.MatchString(lib) { 26 | libraries = append(libraries, lib) 27 | } 28 | } 29 | 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /listlibs/listlibs_darwin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "listlibs_darwin.h" 9 | 10 | static bool read_memory(process_handle_t handle, mach_vm_address_t from, 11 | size_t bytes, mach_vm_address_t to, response_t *response); 12 | 13 | static bool copy_string(process_handle_t handle, mach_vm_address_t from, 14 | char **to, response_t *response); 15 | 16 | response_t *list_loaded_libraries(process_handle_t handle, char ***libs, 17 | size_t *count) { 18 | 19 | response_t *response = response_create(); 20 | 21 | #define PATH_ARRAY_ALLOC_SIZE 64 22 | 23 | size_t path_array_size = PATH_ARRAY_ALLOC_SIZE; 24 | *libs = calloc(PATH_ARRAY_ALLOC_SIZE, sizeof(char *)); 25 | *count = 0; 26 | 27 | struct task_dyld_info dyld_info; 28 | mach_msg_type_number_t count_ret = TASK_DYLD_INFO_COUNT; 29 | kern_return_t kret = task_info( 30 | handle, 31 | TASK_DYLD_INFO, 32 | (task_info_t)&dyld_info, 33 | &count_ret 34 | ); 35 | 36 | if (kret != KERN_SUCCESS) { 37 | response_set_fatal_from_kret(response, kret); 38 | return response; 39 | } 40 | 41 | struct dyld_all_image_infos *all_info = 42 | (struct dyld_all_image_infos *) dyld_info.all_image_info_addr; 43 | uint64_t all_info_base_addr = (uint64_t) all_info; 44 | if (all_info == NULL) { 45 | char *msg = strdup("Can't find dyld_all_image_infos in the process."); 46 | response_set_fatal_error(response, -1, msg); 47 | return response; 48 | } 49 | 50 | /* If the other process is 64 bits its pointers are 8 bytes long, 4 if 32 */ 51 | size_t pointer_size = 8; 52 | if (dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32) { 53 | pointer_size = 4; 54 | } 55 | 56 | uint32_t info_array_count = 0; 57 | bool read_success = read_memory( 58 | handle, 59 | all_info_base_addr + 4, 60 | sizeof(info_array_count), 61 | (mach_vm_address_t) &info_array_count, 62 | response 63 | ); 64 | if (!read_success) { 65 | return response; 66 | } 67 | 68 | uint64_t info_array_start_addr = 0; 69 | read_success = read_memory( 70 | handle, 71 | all_info_base_addr + 8, 72 | pointer_size, 73 | (mach_vm_address_t) &info_array_start_addr, 74 | response 75 | ); 76 | if (!read_success) { 77 | return response; 78 | } 79 | 80 | size_t size_image_info; 81 | if (dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32) { 82 | /* two 32bit pointers and a 32 bit ulong */ 83 | size_image_info = 32 * 3 / 8; 84 | 85 | } else { 86 | /* two 64bit pointers and a 64 bit ulong */ 87 | size_image_info = 64 * 3 / 8; 88 | } 89 | 90 | for (uint32_t i = 0; i < info_array_count; i++) { 91 | 92 | mach_vm_address_t pathAddr = 0; 93 | read_success = read_memory( 94 | handle, 95 | /* current image_info + 1 pointer of offset */ 96 | info_array_start_addr + i * size_image_info + pointer_size, 97 | pointer_size, 98 | (mach_vm_address_t) &pathAddr, 99 | response 100 | ); 101 | if (!read_success) { 102 | return response; 103 | } 104 | 105 | char *path; 106 | read_success = copy_string( 107 | handle, 108 | pathAddr, 109 | &path, 110 | response 111 | ); 112 | if (!read_success) { 113 | return response; 114 | } 115 | 116 | if (*count == path_array_size) { 117 | path_array_size *= 2; 118 | *libs = realloc(*libs, path_array_size * sizeof(char *)); 119 | } 120 | 121 | (*libs)[*count] = path; 122 | (*count)++; 123 | } 124 | 125 | return response; 126 | } 127 | 128 | void free_loaded_libraries_list(char **list, size_t count) { 129 | for (size_t i = 0; i < count; i++) { 130 | if (list[i] != NULL) { 131 | free(list[i]); 132 | } 133 | } 134 | 135 | free(list); 136 | } 137 | 138 | static bool copy_string(process_handle_t handle, mach_vm_address_t from, 139 | char **to, response_t *response) { 140 | 141 | #define COPY_STRING_BUFFER_SIZE 128 142 | #define COPY_STRING_REALLOC_MULTIPLIER 2 143 | 144 | size_t allocated = COPY_STRING_BUFFER_SIZE; 145 | char *s = malloc(allocated); 146 | size_t read_chars = 0; 147 | char buffer[COPY_STRING_BUFFER_SIZE] = {0}; 148 | 149 | for (;;) { 150 | mach_vm_size_t read = 0; 151 | kern_return_t kret = mach_vm_read_overwrite( 152 | handle, 153 | from + read_chars, 154 | COPY_STRING_BUFFER_SIZE, 155 | (mach_vm_address_t) &buffer, 156 | &read 157 | ); 158 | 159 | if (kret != KERN_SUCCESS) { 160 | response_set_fatal_from_kret(response, kret); 161 | free(s); 162 | return false; 163 | } 164 | 165 | if (read_chars + read > allocated) { 166 | allocated *= COPY_STRING_REALLOC_MULTIPLIER; 167 | s = realloc(s, allocated); 168 | } 169 | 170 | for (size_t i = 0; i < read; i++) { 171 | s[read_chars++] = buffer[i]; 172 | if (buffer[i] == '\0') { 173 | *to = s; 174 | return true; 175 | } 176 | } 177 | 178 | if (read < COPY_STRING_BUFFER_SIZE) { 179 | /* We read less than the buffer, then, there is no more contiguous 180 | memory, and we hadn't find the end of the string, so we failed. */ 181 | char *description = NULL; 182 | char *format = "Couldn't read lib path from %" PRIxPTR; 183 | asprintf( 184 | &description, 185 | format, 186 | from 187 | ); 188 | response_set_fatal_error(response, -1, description); 189 | 190 | free(s); 191 | return false; 192 | } 193 | } 194 | 195 | return false; 196 | } 197 | 198 | static bool read_memory(process_handle_t handle, mach_vm_address_t from, 199 | size_t bytes, mach_vm_address_t to, response_t *response) { 200 | 201 | mach_vm_size_t read; 202 | kern_return_t kret = mach_vm_read_overwrite(handle, from, bytes, to, &read); 203 | 204 | if (kret != KERN_SUCCESS) { 205 | response_set_fatal_from_kret(response, kret); 206 | return false; 207 | } 208 | 209 | if (read != bytes) { 210 | char *description = NULL; 211 | char *format = "Couldn't read %d bytes from %" PRIxPTR " in listlibs"; 212 | asprintf( 213 | &description, 214 | format, 215 | bytes, 216 | from 217 | ); 218 | response_set_fatal_error(response, -1, description); 219 | return false; 220 | } 221 | 222 | return true; 223 | } 224 | -------------------------------------------------------------------------------- /listlibs/listlibs_darwin.go: -------------------------------------------------------------------------------- 1 | package listlibs 2 | 3 | // #cgo CFLAGS: -std=c99 4 | // #include "listlibs_darwin.h" 5 | import "C" 6 | 7 | import ( 8 | "github.com/mozilla/masche/cresponse" 9 | "github.com/mozilla/masche/process" 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | func listLoadedLibraries(p process.Process) (libraries []string, softerrors []error, harderror error) { 15 | var ptr uintptr 16 | var sizeT C.size_t 17 | clibs := (***C.char)(C.malloc(C.size_t(unsafe.Sizeof(ptr)))) 18 | count := (*C.size_t)(C.malloc(C.size_t(unsafe.Sizeof(sizeT)))) 19 | defer C.free(unsafe.Pointer(clibs)) 20 | defer C.free(unsafe.Pointer(count)) 21 | 22 | response := C.list_loaded_libraries((C.process_handle_t)(p.Handle()), clibs, count) 23 | defer C.free_loaded_libraries_list(*clibs, *count) 24 | softerrors, harderror = cresponse.GetResponsesErrors(unsafe.Pointer(response)) 25 | C.response_free(response) 26 | 27 | if harderror != nil { 28 | return 29 | } 30 | 31 | libraries = make([]string, 0, *count) 32 | clibsSlice := *(*[]*C.char)(unsafe.Pointer( 33 | &reflect.SliceHeader{ 34 | Data: uintptr(unsafe.Pointer(*clibs)), 35 | Len: int(*count), 36 | Cap: int(*count)})) 37 | 38 | processName, softs, harderror := p.Name() 39 | if harderror != nil { 40 | return 41 | } 42 | softerrors = append(softerrors, softs...) 43 | 44 | for i := range clibsSlice { 45 | if clibsSlice[i] == nil { 46 | continue 47 | } 48 | 49 | str := C.GoString(clibsSlice[i]) 50 | if str == processName { 51 | continue 52 | } 53 | libraries = append(libraries, str) 54 | } 55 | 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /listlibs/listlibs_darwin.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTLIBS_DARWIN_H 2 | 3 | #include "../cresponse/response.h" 4 | #include "../process/process.h" 5 | 6 | /** 7 | * Returns a dynamically allocated list of absolute paths (as null-terminated 8 | * strings) to the libraries loaded by the process. 9 | **/ 10 | response_t *list_loaded_libraries(process_handle_t handle, char ***libs, 11 | size_t *count); 12 | 13 | /** 14 | * Frees the list allocated by the previous function. 15 | **/ 16 | void free_loaded_libraries_list(char **list, size_t count); 17 | 18 | #endif /* LISTLIBS_DARWIN_H */ 19 | -------------------------------------------------------------------------------- /listlibs/listlibs_linux.go: -------------------------------------------------------------------------------- 1 | package listlibs 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/mozilla/masche/common" 7 | "github.com/mozilla/masche/process" 8 | "os" 9 | ) 10 | 11 | func listLoadedLibraries(p process.Process) (libraries []string, softerrors []error, harderror error) { 12 | 13 | mapsFile, harderror := os.Open(common.MapsFilePathFromPid(p.Pid())) 14 | if harderror != nil { 15 | return 16 | } 17 | defer mapsFile.Close() 18 | 19 | scanner := bufio.NewScanner(mapsFile) 20 | processName, softerrors, harderror := p.Name() 21 | if harderror != nil { 22 | return 23 | } 24 | 25 | libs := make([]string, 0, 10) 26 | for scanner.Scan() { 27 | line := scanner.Text() 28 | items := common.SplitMapsFileEntry(line) 29 | 30 | if len(items) != 6 { 31 | return libs, softerrors, fmt.Errorf("Unrecognised maps line: %s", line) 32 | } 33 | 34 | path := items[5] 35 | if path == processName { 36 | continue 37 | } 38 | 39 | if path == "/dev/zero" || path == "/dev/zero (deleted)" { 40 | continue 41 | } 42 | 43 | if path == "" { 44 | continue 45 | } 46 | 47 | if path[0] == '[' { 48 | continue 49 | } 50 | 51 | if inSlice(path, libs) { 52 | continue 53 | } 54 | 55 | libs = append(libs, path) 56 | } 57 | 58 | return libs, nil, nil 59 | } 60 | 61 | func inSlice(s string, slice []string) bool { 62 | for _, s2 := range slice { 63 | if s == s2 { 64 | return true 65 | } 66 | } 67 | 68 | return false 69 | } 70 | -------------------------------------------------------------------------------- /listlibs/listlibs_windows.c: -------------------------------------------------------------------------------- 1 | #include "listlibs_windows.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Ugly hack for mingw64 10 | // Some versions don't have LIST_MODULES_ALL defined in psapi.h 11 | #ifndef LIST_MODULES_ALL 12 | #define LIST_MODULES_ALL 0x03 13 | #endif 14 | 15 | // getModules retrieves all the modules for a process with their info. 16 | // it calls GetModuleFilenameEx and GetModuleInformation on the module. 17 | // Caller must call EnumProcessModulesResponse_Free even if there's an error. 18 | EnumProcessModulesResponse *getModules(process_handle_t process_handle) { 19 | HMODULE *aMods = NULL; 20 | ModuleInfo *modsInfo = NULL; 21 | DWORD size = 512 * sizeof(HMODULE); 22 | DWORD cbNeeded, mCount = 0; 23 | DWORD i; 24 | HANDLE hProcess = (HANDLE) process_handle; 25 | 26 | EnumProcessModulesResponse *res = calloc(1, sizeof * res); 27 | 28 | // Allocate a buffer large enough to carry all the modules, 29 | // there's no way to know the size beforehand, so if the array is full 30 | // (cbNeeded == size), we double its size and refill it again. 31 | do { 32 | size *= 2; 33 | aMods = realloc(aMods, size); 34 | 35 | BOOL success = EnumProcessModulesEx(hProcess, aMods, 36 | size, &cbNeeded, 37 | LIST_MODULES_ALL); 38 | if (!success) { 39 | res->error = GetLastError(); 40 | free(aMods); 41 | return res; 42 | } 43 | } while (cbNeeded == size); 44 | 45 | 46 | // Try to get module's filename and information for each of the 47 | // modules retrieved by EnumProcessModulesEx. If there's an error, 48 | // we abort and cleanup everything. 49 | mCount = cbNeeded / sizeof (HMODULE); 50 | modsInfo = calloc(mCount, sizeof * modsInfo); 51 | for (i = 0; i < mCount; i++) { 52 | TCHAR buf[MAX_PATH + 1]; 53 | DWORD len = GetModuleFileNameEx(hProcess, aMods[i], buf, 54 | sizeof(buf) / sizeof(TCHAR)); 55 | if (len == 0) { 56 | res->error = GetLastError(); 57 | goto cleanup; 58 | } 59 | buf[MAX_PATH] = '\0'; 60 | 61 | modsInfo[i].filename = (char *) _tcsdup(buf); 62 | // is there safer way to convert from TCHAR * to char *? 63 | 64 | MODULEINFO info; 65 | BOOL success = GetModuleInformation(hProcess, aMods[i], &info, sizeof(info)); 66 | if (!success) { 67 | res->error = GetLastError(); 68 | goto cleanup; 69 | } 70 | modsInfo[i].info = info; 71 | } 72 | 73 | res->modules = modsInfo; 74 | res->length = mCount; 75 | 76 | free(aMods); 77 | 78 | return res; 79 | 80 | cleanup: 81 | for (i = 0; i < mCount; i += 1) { 82 | free(modsInfo[i].filename); 83 | } 84 | 85 | free(modsInfo); 86 | res->modules = NULL; 87 | res->length = 0; 88 | 89 | free(aMods); 90 | 91 | return res; 92 | } 93 | 94 | void EnumProcessModulesResponse_Free(EnumProcessModulesResponse *r) { 95 | DWORD i; 96 | if (r == NULL) { 97 | return; 98 | } 99 | for (i = 0; i < r->length; i++) { 100 | free(r->modules[i].filename); 101 | } 102 | free(r->modules); 103 | free(r); 104 | } 105 | 106 | -------------------------------------------------------------------------------- /listlibs/listlibs_windows.go: -------------------------------------------------------------------------------- 1 | package listlibs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | 8 | "github.com/mozilla/masche/process" 9 | ) 10 | 11 | // #cgo CFLAGS: -std=c99 12 | // #cgo CFLAGS: -DPSAPI_VERSION=1 13 | // #cgo LDFLAGS: -lpsapi 14 | // #include "listlibs_windows.h" 15 | import "C" 16 | 17 | func listLoadedLibraries(p process.Process) (libraries []string, softerrors []error, 18 | harderror error) { 19 | r := C.getModules(C.process_handle_t(p.Handle())) 20 | defer C.EnumProcessModulesResponse_Free(r) 21 | if r.error != 0 { 22 | return nil, nil, fmt.Errorf("getModules failed with error: %d", r.error) 23 | } 24 | mods := make([]string, r.length) 25 | // We use this to access C arrays without doing manual pointer arithmetic. 26 | cmods := *(*[]C.ModuleInfo)(unsafe.Pointer( 27 | &reflect.SliceHeader{ 28 | Data: uintptr(unsafe.Pointer(r.modules)), 29 | Len: int(r.length), 30 | Cap: int(r.length)})) 31 | for i := range mods { 32 | mods[i] = C.GoString(cmods[i].filename) 33 | } 34 | return mods, nil, nil 35 | } 36 | -------------------------------------------------------------------------------- /listlibs/listlibs_windows.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIST_LIBS_WINDOWS_H_ 2 | #define _LIST_LIBS_WINDOWS_H_ 3 | 4 | #include 5 | #include 6 | #include "../process/process.h" 7 | 8 | typedef struct t_ModuleInfo { 9 | char *filename; 10 | MODULEINFO info; 11 | } ModuleInfo; 12 | 13 | typedef struct t_EnumProcessModulesResponse { 14 | DWORD error; 15 | DWORD length; 16 | ModuleInfo *modules; 17 | } EnumProcessModulesResponse; 18 | 19 | EnumProcessModulesResponse *getModules(process_handle_t handle); 20 | void EnumProcessModulesResponse_Free(EnumProcessModulesResponse *r); 21 | 22 | #endif // _LIST_LIBS_WINDOWS_H_ 23 | -------------------------------------------------------------------------------- /memaccess/memaccess.go: -------------------------------------------------------------------------------- 1 | // Package memaccess contains an interface for accessing other processes' memory. 2 | package memaccess 3 | 4 | import ( 5 | "fmt" 6 | "github.com/mozilla/masche/process" 7 | ) 8 | 9 | // MemoryRegion represents a region of readable contiguos memory of a process. 10 | // No readable memory can be available right next to this region, it's maximal in its upper bound. 11 | // 12 | // NOTE: This region is not necessary equivalent to the OS's region, if any. 13 | type MemoryRegion struct { 14 | Address uintptr 15 | Size uint 16 | } 17 | 18 | func (m MemoryRegion) String() string { 19 | return fmt.Sprintf("MemoryRegion[%x-%x)", m.Address, m.Address+uintptr(m.Size)) 20 | } 21 | 22 | // NoRegionAvailable is a centinel value indicating that there is no more regions available. 23 | var NoRegionAvailable MemoryRegion 24 | 25 | // NextReadableMemoryRegion returns a memory region containing address, or the next readable region after address in 26 | // case addresss is not in a readable region. If there aren't more regions available the special value NoRegionAvailable 27 | // is returned. 28 | func NextReadableMemoryRegion(p process.Process, address uintptr) (region MemoryRegion, softerrors []error, 29 | harderror error) { 30 | return nextReadableMemoryRegion(p, address) 31 | } 32 | 33 | // CopyMemory fills the entire buffer with memory from the process starting in address (in the process address space). 34 | // If there is not enough memory to read it returns a hard error. Note that this is not the only hard error it may 35 | // return though. 36 | func CopyMemory(p process.Process, address uintptr, buffer []byte) (softerrors []error, harderror error) { 37 | return copyMemory(p, address, buffer) 38 | } 39 | 40 | // WalkFunc type represents a function used for walking through the memory, see WalkMemory for more details. 41 | type WalkFunc func(address uintptr, buf []byte) (keepSearching bool) 42 | 43 | // WalkMemory reads all the memory of a process starting at a given address reading up to bufSize bytes into a buffer, 44 | // and calling walkFn with the buffer and the start address of the memory in the buffer. If walkFn returns false 45 | // WalkMemory stop reading the memory. 46 | // 47 | // NOTE: It can call to walkFn with a smaller buffer when reading the last part of a memory region. 48 | func WalkMemory(p process.Process, startAddress uintptr, bufSize uint, walkFn WalkFunc) (softerrors []error, harderror error) { 49 | 50 | var region MemoryRegion 51 | region, softerrors, harderror = NextReadableMemoryRegion(p, startAddress) 52 | if harderror != nil { 53 | return 54 | } 55 | 56 | // The first region can start befor startAddress. If that happens, it must contain it. In that case, we set the 57 | // region's Adrress to startAddress to behave as documented. 58 | if region.Address < startAddress { 59 | if region.Address+uintptr(region.Size) <= startAddress { 60 | harderror = fmt.Errorf("First memory region doesn't contain the startAddress. This is a bug") 61 | return 62 | } 63 | 64 | region.Size -= uint(startAddress - region.Address) 65 | region.Address = startAddress 66 | } 67 | 68 | const maxRetries int = 5 69 | 70 | buf := make([]byte, bufSize) 71 | retries := maxRetries 72 | 73 | for region != NoRegionAvailable { 74 | 75 | keepWalking, addr, serrs, err := walkRegion(p, region, buf, walkFn) 76 | softerrors = append(softerrors, serrs...) 77 | 78 | if err != nil && retries > 0 { 79 | // An error occurred: retry using the nearest region to the address that failed. 80 | retries-- 81 | region, serrs, harderror = NextReadableMemoryRegion(p, addr) 82 | softerrors = append(softerrors, serrs...) 83 | if harderror != nil { 84 | return 85 | } 86 | 87 | // if some chunk of this new region was already read we don't want to read it again. 88 | if region.Address < addr { 89 | region.Address = addr 90 | } 91 | 92 | continue 93 | } else if err != nil { 94 | // we have exceeded our retries, mark the error as soft error and keep going. 95 | softerrors = append(softerrors, fmt.Errorf("Retries exceeded on reading %d bytes starting at %x: %s", 96 | len(buf), addr, err.Error())) 97 | } else if !keepWalking { 98 | return 99 | } 100 | 101 | region, serrs, harderror = NextReadableMemoryRegion(p, region.Address+uintptr(region.Size)) 102 | softerrors = append(softerrors, serrs...) 103 | if harderror != nil { 104 | return 105 | } 106 | retries = maxRetries 107 | } 108 | return 109 | } 110 | 111 | // This function walks through a single memory region calling walkFunc with a given buffer. It always fills as much of 112 | // the buffer as possible before calling walkFunc, but it never calls it with overlaped memory sections. 113 | // 114 | // If the buffer cannot be filled a hard error is returned with the starting address of the chunk of memory that could 115 | // not be read. If no harderror is returned errorAddress must be ignored. 116 | // 117 | // If any of the calls to walkFn returns false, this function inmediatly returns, with keepWalking set to false and no 118 | // hard error. 119 | func walkRegion(p process.Process, region MemoryRegion, buf []byte, walkFn WalkFunc) (keepWalking bool, 120 | errorAddress uintptr, softerrors []error, harderror error) { 121 | softerrors = make([]error, 0) 122 | keepWalking = true 123 | remainingBytes := uintptr(region.Size) 124 | for addr := region.Address; remainingBytes > 0; { 125 | if remainingBytes < uintptr(len(buf)) { 126 | buf = buf[:remainingBytes] 127 | } 128 | 129 | serrs, err := CopyMemory(p, addr, buf) 130 | softerrors = append(softerrors, serrs...) 131 | 132 | if err != nil { 133 | harderror = err 134 | errorAddress = addr 135 | return 136 | } 137 | 138 | keepWalking = walkFn(addr, buf) 139 | if !keepWalking { 140 | return 141 | } 142 | 143 | addr += uintptr(len(buf)) 144 | remainingBytes -= uintptr(len(buf)) 145 | } 146 | 147 | return 148 | } 149 | 150 | // SlidingWalkMemory function works as WalkMemory, except that it reads overlapped bytes. It first calls walkFn with a full buffer, 151 | // then advances just half of the buffer size, and calls it again. 152 | // As with WalkRegion, the buffer can be smaller at the end of a region. 153 | // NOTE: It doesn't work with odd bufSize. 154 | func SlidingWalkMemory(p process.Process, startAddress uintptr, bufSize uint, walkFn WalkFunc) ( 155 | softerrors []error, harderror error) { 156 | 157 | if bufSize%2 != 0 { 158 | return softerrors, fmt.Errorf("SlidingWalkMemory doesn't support odd bufferSizes") 159 | } 160 | 161 | buffer := make([]byte, bufSize) 162 | halfBufferSize := bufSize / 2 163 | currentBufferStartsAt := uintptr(0) 164 | bufferedBytes := uint(0) 165 | softerrors, harderror = WalkMemory(p, startAddress, halfBufferSize, 166 | func(address uintptr, currentBuffer []byte) (keepSearching bool) { 167 | 168 | fromAnotherRegion := currentBufferStartsAt+uintptr(bufferedBytes) < address && currentBufferStartsAt != 0 169 | 170 | if fromAnotherRegion { 171 | if bufferedBytes > 0 && bufferedBytes < bufSize { 172 | // Call walkFn with buffer because if was starting a region and as it's not complete it hasn't been 173 | // sent to walkFn 174 | if !walkFn(currentBufferStartsAt, buffer[:bufferedBytes]) { 175 | return false 176 | } 177 | } 178 | 179 | bufferedBytes = 0 180 | } 181 | 182 | if bufferedBytes == 0 { 183 | copy(buffer, currentBuffer) 184 | currentBufferStartsAt = address 185 | 186 | // If the currentBuffer is smaller the region has finished 187 | if uint(len(currentBuffer)) != halfBufferSize { 188 | if !walkFn(currentBufferStartsAt, buffer[:len(currentBuffer)]) { 189 | return false 190 | } 191 | } else { 192 | bufferedBytes = halfBufferSize 193 | } 194 | 195 | return true 196 | } 197 | 198 | if bufferedBytes == bufSize { 199 | copy(buffer, buffer[:halfBufferSize]) 200 | currentBufferStartsAt += uintptr(halfBufferSize) 201 | } 202 | 203 | copy(buffer[halfBufferSize:], currentBuffer) 204 | if !walkFn(currentBufferStartsAt, buffer[:halfBufferSize+uint(len(currentBuffer))]) { 205 | return false 206 | } 207 | 208 | // If the currentBuffer is smaller the region has finished 209 | if uint(len(currentBuffer)) != halfBufferSize { 210 | bufferedBytes = 0 211 | } else { 212 | bufferedBytes = bufSize 213 | } 214 | 215 | return true 216 | }) 217 | 218 | // If we only have half buffer filled we haven't called walkFn yet with it 219 | if bufferedBytes == halfBufferSize { 220 | walkFn(currentBufferStartsAt, buffer[:halfBufferSize]) 221 | } 222 | 223 | return 224 | } 225 | -------------------------------------------------------------------------------- /memaccess/memaccess.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMACCES_H 2 | #define MEMACCES_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../cresponse/response.h" 8 | #include "../process/process.h" 9 | 10 | /** 11 | * A type representing a memory address used to represent addresses in the 12 | * inspected process. 13 | * 14 | * NOTE: This is necessary because Go doesn't allow us to have an unsafe.Pointer 15 | * with an address that is not mapped in the current process. 16 | **/ 17 | typedef uintptr_t memory_address_t; 18 | 19 | /** 20 | * This struct represents a region of readable contiguos memory of a process. 21 | * 22 | * No readable memory can be available right next to this region, it's maximal 23 | * in its upper bound. 24 | * 25 | * Note that this region is not necessary equivalent to the OS's region, if any. 26 | **/ 27 | typedef struct { 28 | memory_address_t start_address; 29 | size_t length; 30 | } memory_region_t; 31 | 32 | /** 33 | * Returns a memory region containing address, or the next readable region 34 | * after address in case addresss is not in a readable region. 35 | * 36 | * If there is no region to return region_available will be false. Otherwise 37 | * it will be true, and the region will be returned in memory_region. 38 | **/ 39 | response_t *get_next_readable_memory_region(process_handle_t handle, 40 | memory_address_t address, bool *region_available, 41 | memory_region_t *memory_region); 42 | 43 | /** 44 | * Copies a chunk of memory from the process' address space to the buffer. 45 | * 46 | * Note that start_address is the address as seen by the process. 47 | * If no fatal error ocurred the buffer will be populated with bytes_read bytes. 48 | * It's caller's responsibility to provide a big enough buffer. 49 | **/ 50 | response_t *copy_process_memory(process_handle_t handle, 51 | memory_address_t start_address, size_t bytes_to_read, void *buffer, 52 | size_t *bytes_read); 53 | 54 | #endif /* MEMACCES_H */ 55 | 56 | -------------------------------------------------------------------------------- /memaccess/memaccess_c_wrapper.go: -------------------------------------------------------------------------------- 1 | // +build windows darwin 2 | 3 | package memaccess 4 | 5 | // #include "memaccess.h" 6 | // #cgo CFLAGS: -std=c99 7 | import "C" 8 | 9 | import ( 10 | "fmt" 11 | "github.com/mozilla/masche/cresponse" 12 | "github.com/mozilla/masche/process" 13 | "unsafe" 14 | ) 15 | 16 | func nextReadableMemoryRegion(p process.Process, address uintptr) (region MemoryRegion, softerrors []error, harderror error) { 17 | var isAvailable C.bool 18 | var cRegion C.memory_region_t 19 | 20 | response := C.get_next_readable_memory_region( 21 | (C.process_handle_t)(p.Handle()), 22 | C.memory_address_t(address), 23 | &isAvailable, 24 | &cRegion) 25 | softerrors, harderror = cresponse.GetResponsesErrors(unsafe.Pointer(response)) 26 | C.response_free(response) 27 | 28 | if harderror != nil || isAvailable == false { 29 | return NoRegionAvailable, softerrors, harderror 30 | } 31 | 32 | return MemoryRegion{uintptr(cRegion.start_address), uint(cRegion.length)}, softerrors, harderror 33 | } 34 | 35 | func copyMemory(p process.Process, address uintptr, buffer []byte) (softerrors []error, harderror error) { 36 | buf := unsafe.Pointer(&buffer[0]) 37 | 38 | n := len(buffer) 39 | var bytesRead C.size_t 40 | resp := C.copy_process_memory( 41 | (C.process_handle_t)(p.Handle()), 42 | C.memory_address_t(address), 43 | C.size_t(n), 44 | buf, 45 | &bytesRead, 46 | ) 47 | 48 | softerrors, harderror = cresponse.GetResponsesErrors(unsafe.Pointer(resp)) 49 | C.response_free(resp) 50 | 51 | if harderror != nil { 52 | harderror = fmt.Errorf("Error while copying %d bytes starting at %x: %s", n, address, harderror.Error()) 53 | return 54 | } 55 | 56 | if len(buffer) != int(bytesRead) { 57 | harderror = fmt.Errorf("Could not copy %d bytes starting at %x, copyed %d", len(buffer), address, bytesRead) 58 | } 59 | 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /memaccess/memaccess_darwin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "memaccess.h" 7 | 8 | response_t *get_next_readable_memory_region(process_handle_t handle, 9 | memory_address_t address, bool *region_available, 10 | memory_region_t *memory_region) { 11 | response_t *response = response_create(); 12 | 13 | kern_return_t kret; 14 | struct vm_region_submap_info_64 info; 15 | mach_msg_type_number_t info_count = 0; 16 | mach_vm_address_t addr = address; 17 | mach_vm_size_t size = 0; 18 | uint32_t depth = 0; 19 | *region_available = false; 20 | 21 | for (;;) { 22 | info_count = VM_REGION_SUBMAP_INFO_COUNT_64; 23 | kret = mach_vm_region_recurse(handle, &addr, &size, &depth, 24 | (vm_region_recurse_info_t)&info, &info_count); 25 | 26 | if (kret == KERN_INVALID_ADDRESS) { 27 | break; 28 | } 29 | 30 | if (kret != KERN_SUCCESS) { 31 | response_set_fatal_from_kret(response, kret); 32 | return response; 33 | } 34 | 35 | if(info.is_submap) { 36 | depth += 1; 37 | continue; 38 | } 39 | 40 | if ((info.protection & VM_PROT_READ) != VM_PROT_READ) { 41 | if (*region_available) { 42 | return response; 43 | } 44 | 45 | char *description = NULL; 46 | asprintf( 47 | &description, 48 | "memory unreadable: %llx-%llx", 49 | addr, 50 | addr + size - 1 51 | ); 52 | response_add_soft_error(response, -1, description); 53 | } else { 54 | if (!(*region_available)) { 55 | 56 | // Sometimes a previous region is returned that doesn't contain, 57 | // address. This would lead to an infinite loop while using 58 | // the regions, getting every time the same one. To avoid this 59 | // we ask for the region 1 byte after address. 60 | if (addr + size <= address) { 61 | char *description = NULL; 62 | char *format = "wrong region obtained, expected it to " 63 | "contain %" PRIxPTR ", but got: %" PRIxPTR "-%" 64 | PRIxPTR; 65 | asprintf( 66 | &description, 67 | format, 68 | address, 69 | addr, 70 | addr + size - 1 71 | ); 72 | response_add_soft_error(response, -1, description); 73 | 74 | addr = address + 1; 75 | continue; 76 | } 77 | 78 | *region_available = true; 79 | memory_region->start_address = addr; 80 | memory_region->length = size; 81 | } else { 82 | memory_address_t limit_address = memory_region->start_address + 83 | memory_region->length; 84 | 85 | if (limit_address < addr) { 86 | return response; 87 | } 88 | 89 | mach_vm_size_t overlaped_bytes = limit_address - addr; 90 | memory_region->length += size - overlaped_bytes; 91 | } 92 | } 93 | 94 | addr += size; 95 | } 96 | 97 | return response; 98 | } 99 | 100 | response_t *copy_process_memory(process_handle_t handle, 101 | memory_address_t start_address, size_t bytes_to_read, void *buffer, 102 | size_t *bytes_read) { 103 | 104 | response_t *response = response_create(); 105 | 106 | mach_vm_size_t read; 107 | kern_return_t kret = mach_vm_read_overwrite(handle, start_address, 108 | bytes_to_read, (mach_vm_address_t) buffer, &read); 109 | 110 | if (kret != KERN_SUCCESS) { 111 | response_set_fatal_from_kret(response, kret); 112 | return response; 113 | } 114 | 115 | *bytes_read = read; 116 | return response; 117 | } 118 | 119 | -------------------------------------------------------------------------------- /memaccess/memaccess_linux.go: -------------------------------------------------------------------------------- 1 | package memaccess 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/mozilla/masche/common" 7 | "github.com/mozilla/masche/process" 8 | "os" 9 | ) 10 | 11 | func nextReadableMemoryRegion(p process.Process, address uintptr) (region MemoryRegion, softerrors []error, 12 | harderror error) { 13 | 14 | mapsFile, harderror := os.Open(common.MapsFilePathFromPid(p.Pid())) 15 | if harderror != nil { 16 | return 17 | } 18 | defer mapsFile.Close() 19 | 20 | region = MemoryRegion{} 21 | scanner := bufio.NewScanner(mapsFile) 22 | 23 | for scanner.Scan() { 24 | line := scanner.Text() 25 | items := common.SplitMapsFileEntry(line) 26 | 27 | if len(items) != 6 { 28 | return region, softerrors, fmt.Errorf("Unrecognised maps line: %s", line) 29 | } 30 | 31 | start, end, err := common.ParseMapsFileMemoryLimits(items[0]) 32 | if err != nil { 33 | return region, softerrors, err 34 | } 35 | 36 | if end <= address { 37 | continue 38 | } 39 | 40 | // Skip vsyscall as it can't be read. It's a special page mapped by the kernel to accelerate some syscalls. 41 | if items[5] == "[vsyscall]" { 42 | continue 43 | } 44 | 45 | // Check if memory is unreadable 46 | if items[1][0] == '-' { 47 | 48 | // If we were already reading a region this will just finish it. We only report the softerror when we 49 | // were actually trying to read it. 50 | if region.Address != 0 { 51 | return region, softerrors, nil 52 | } 53 | 54 | softerrors = append(softerrors, fmt.Errorf("Unreadable memory %s", items[0])) 55 | continue 56 | } 57 | 58 | size := uint(end - start) 59 | 60 | // Begenning of a region 61 | if region.Address == 0 { 62 | region = MemoryRegion{Address: start, Size: size} 63 | continue 64 | } 65 | 66 | // Continuation of a region 67 | if region.Address+uintptr(region.Size) == start { 68 | region.Size += size 69 | continue 70 | } 71 | 72 | // This map is outside the current region, so we are ready 73 | return region, softerrors, nil 74 | } 75 | 76 | // No region left 77 | if err := scanner.Err(); err != nil { 78 | return NoRegionAvailable, softerrors, err 79 | } 80 | 81 | // The last map was a valid region, so it was not closed by an invalid/non-contiguous one and we have to return it 82 | if region.Address > 0 { 83 | return region, softerrors, harderror 84 | } 85 | 86 | return NoRegionAvailable, softerrors, nil 87 | } 88 | 89 | func copyMemory(p process.Process, address uintptr, buffer []byte) (softerrors []error, harderror error) { 90 | mem, harderror := os.Open(common.MemFilePathFromPid(p.Pid())) 91 | 92 | if harderror != nil { 93 | harderror := fmt.Errorf("Error while reading %d bytes starting at %x: %s", len(buffer), address, harderror) 94 | return softerrors, harderror 95 | } 96 | defer mem.Close() 97 | 98 | bytesRead, harderror := mem.ReadAt(buffer, int64(address)) 99 | if harderror != nil { 100 | harderror := fmt.Errorf("Error while reading %d bytes starting at %x: %s", len(buffer), address, harderror) 101 | return softerrors, harderror 102 | } 103 | 104 | if bytesRead != len(buffer) { 105 | return softerrors, fmt.Errorf("Could not read the entire buffer") 106 | } 107 | 108 | return softerrors, nil 109 | } 110 | -------------------------------------------------------------------------------- /memaccess/memaccess_test.go: -------------------------------------------------------------------------------- 1 | package memaccess 2 | 3 | import ( 4 | "github.com/mozilla/masche/process" 5 | "github.com/mozilla/masche/test" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestManuallyWalk(t *testing.T) { 11 | cmd, err := test.LaunchTestCase() 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | defer cmd.Process.Kill() 16 | 17 | pid := uint(cmd.Process.Pid) 18 | proc, softerrors, err := process.OpenFromPid(pid) 19 | test.PrintSoftErrors(softerrors) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | var region MemoryRegion 25 | region, softerrors, err = NextReadableMemoryRegion(proc, 0) 26 | test.PrintSoftErrors(softerrors) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | if region == NoRegionAvailable { 32 | t.Error("No starting region returned") 33 | } 34 | 35 | previousRegion := region 36 | for region != NoRegionAvailable { 37 | region, softerrors, err = NextReadableMemoryRegion(proc, region.Address+uintptr(region.Size)) 38 | test.PrintSoftErrors(softerrors) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | if region != NoRegionAvailable && region.Address < previousRegion.Address+uintptr(previousRegion.Size) { 44 | t.Error("Returned region is not after the previous one.") 45 | } 46 | 47 | previousRegion = region 48 | } 49 | } 50 | 51 | func TestCopyMemory(t *testing.T) { 52 | cmd, err := test.LaunchTestCaseAndWaitForInitialization() 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | defer cmd.Process.Kill() 57 | 58 | pid := uint(cmd.Process.Pid) 59 | proc, softerrors, err := process.OpenFromPid(pid) 60 | test.PrintSoftErrors(softerrors) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | var region MemoryRegion 66 | region, softerrors, err = NextReadableMemoryRegion(proc, 0) 67 | test.PrintSoftErrors(softerrors) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | if region == NoRegionAvailable { 73 | t.Error("No starting region returned") 74 | } 75 | 76 | minRegionSize := uint(os.Getpagesize() + 100) // one page plus something 77 | 78 | for region.Size < minRegionSize { 79 | if region == NoRegionAvailable { 80 | t.Fatalf("We couldn't find a region of %d bytes", minRegionSize) 81 | } 82 | 83 | region, softerrors, err = NextReadableMemoryRegion(proc, region.Address+uintptr(region.Size)) 84 | test.PrintSoftErrors(softerrors) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | } 89 | 90 | buffers := [][]byte{ 91 | make([]byte, 2), 92 | make([]byte, os.Getpagesize()), 93 | make([]byte, minRegionSize), 94 | } 95 | 96 | for _, buffer := range buffers { 97 | // Valid read 98 | softerrors, err = CopyMemory(proc, region.Address, buffer) 99 | test.PrintSoftErrors(softerrors) 100 | if err != nil { 101 | t.Errorf("Couldn't read %d bytes from region", len(buffer)) 102 | } 103 | 104 | // Crossing boundaries 105 | softerrors, err = CopyMemory(proc, region.Address+uintptr(region.Size)-uintptr(len(buffer)/2), buffer) 106 | test.PrintSoftErrors(softerrors) 107 | if err == nil { 108 | t.Errorf("Read %d bytes inbetween regions", len(buffer)) 109 | } 110 | 111 | // Entirely outside region 112 | softerrors, err = CopyMemory(proc, region.Address+uintptr(region.Size), buffer) 113 | test.PrintSoftErrors(softerrors) 114 | if err == nil { 115 | t.Errorf("Read %d bytes after the region", len(buffer)) 116 | } 117 | } 118 | 119 | } 120 | 121 | func memoryRegionsOverlap(region1 MemoryRegion, region2 MemoryRegion) bool { 122 | region1End := region1.Address + uintptr(region1.Size) 123 | region2End := region2.Address + uintptr(region2.Size) 124 | 125 | if region2.Address >= region1.Address { 126 | if region2.Address < region1End { 127 | return true 128 | } 129 | } else { 130 | if region2End <= region1End { 131 | return true 132 | } 133 | } 134 | 135 | return false 136 | } 137 | 138 | func TestWalkMemoryDoesntOverlapTheBuffer(t *testing.T) { 139 | cmd, err := test.LaunchTestCaseAndWaitForInitialization() 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | defer cmd.Process.Kill() 144 | 145 | pid := uint(cmd.Process.Pid) 146 | proc, softerrors, err := process.OpenFromPid(pid) 147 | test.PrintSoftErrors(softerrors) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | pageSize := uint(os.Getpagesize()) 153 | bufferSizes := []uint{1024, pageSize, pageSize + 100, pageSize * 2, pageSize*2 + 123} 154 | for _, size := range bufferSizes { 155 | 156 | lastRegion := MemoryRegion{} 157 | softerrors, err = WalkMemory(proc, 0, size, func(address uintptr, buffer []byte) (keepSearching bool) { 158 | currentRegion := MemoryRegion{Address: address, Size: uint(len(buffer))} 159 | if memoryRegionsOverlap(lastRegion, currentRegion) { 160 | t.Errorf("Regions overlap while reading %d at a time: %v %v", size, lastRegion, currentRegion) 161 | return false 162 | } 163 | 164 | lastRegion = currentRegion 165 | return true 166 | }) 167 | test.PrintSoftErrors(softerrors) 168 | if err != nil { 169 | t.Fatal(err) 170 | } 171 | } 172 | } 173 | 174 | func TestWalkRegionReadsEntireRegion(t *testing.T) { 175 | cmd, err := test.LaunchTestCaseAndWaitForInitialization() 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | defer cmd.Process.Kill() 180 | 181 | pid := uint(cmd.Process.Pid) 182 | proc, softerrors, err := process.OpenFromPid(pid) 183 | test.PrintSoftErrors(softerrors) 184 | if err != nil { 185 | t.Fatal(err) 186 | } 187 | 188 | pageSize := uint(os.Getpagesize()) 189 | bufferSizes := []uint{1024, pageSize, pageSize + 100, pageSize * 2, pageSize*2 + 123} 190 | 191 | var region MemoryRegion 192 | region, softerrors, err = NextReadableMemoryRegion(proc, 0) 193 | test.PrintSoftErrors(softerrors) 194 | if err != nil { 195 | t.Fatal(err) 196 | } 197 | 198 | if region == NoRegionAvailable { 199 | t.Error("No starting region returned") 200 | } 201 | 202 | minRegionSize := bufferSizes[len(bufferSizes)-1] 203 | for region.Size < minRegionSize { 204 | if region == NoRegionAvailable { 205 | t.Fatalf("We couldn't find a region of %d bytes", minRegionSize) 206 | } 207 | 208 | region, softerrors, err = NextReadableMemoryRegion(proc, region.Address+uintptr(region.Size)) 209 | test.PrintSoftErrors(softerrors) 210 | if err != nil { 211 | t.Fatal(err) 212 | } 213 | } 214 | 215 | for _, size := range bufferSizes { 216 | buf := make([]byte, size) 217 | readRegion := MemoryRegion{} 218 | 219 | _, _, softerrors, err := walkRegion(proc, region, buf, 220 | func(address uintptr, buffer []byte) (keepSearching bool) { 221 | if readRegion.Address == 0 { 222 | readRegion.Address = address 223 | readRegion.Size = uint(len(buffer)) 224 | return true 225 | } 226 | 227 | readRegionLimit := readRegion.Address + uintptr(readRegion.Size) 228 | if readRegionLimit != address { 229 | t.Errorf("walkRegion skept %d bytes starting at %x", address-readRegionLimit, 230 | readRegionLimit) 231 | return false 232 | } 233 | 234 | readRegion.Size += uint(len(buffer)) 235 | return true 236 | }) 237 | test.PrintSoftErrors(softerrors) 238 | if err != nil { 239 | t.Fatal(err) 240 | } 241 | 242 | if region != readRegion { 243 | t.Errorf("%v not entirely read", region) 244 | } 245 | } 246 | } 247 | 248 | func TestSlidingWalkMemory(t *testing.T) { 249 | cmd, err := test.LaunchTestCaseAndWaitForInitialization() 250 | if err != nil { 251 | t.Fatal(err) 252 | } 253 | defer cmd.Process.Kill() 254 | 255 | pid := uint(cmd.Process.Pid) 256 | proc, softerrors, err := process.OpenFromPid(pid) 257 | test.PrintSoftErrors(softerrors) 258 | if err != nil { 259 | t.Fatal(err) 260 | } 261 | 262 | pageSize := uint(os.Getpagesize()) 263 | bufferSizes := []uint{1024, pageSize, pageSize + 100, pageSize * 2, pageSize*2 + 124} 264 | for _, size := range bufferSizes { 265 | lastRegion := MemoryRegion{} 266 | softerrors, err = SlidingWalkMemory(proc, 0, size, func(address uintptr, buffer []byte) (keepSearching bool) { 267 | currentRegion := MemoryRegion{Address: address, Size: uint(len(buffer))} 268 | 269 | if lastRegion.Address == 0 { 270 | lastRegion = currentRegion 271 | return true 272 | } 273 | 274 | lastRegionLimit := lastRegion.Address + uintptr(lastRegion.Size) 275 | overlappedBytes := uintptr(0) 276 | regionIsContigous := false 277 | 278 | if lastRegionLimit > currentRegion.Address { 279 | overlappedBytes = lastRegionLimit - currentRegion.Address 280 | } else if lastRegionLimit == currentRegion.Address { 281 | regionIsContigous = true 282 | } 283 | 284 | if regionIsContigous { 285 | if regionIsContigous { 286 | t.Errorf("Contigous buffer while we are expecting overlapped ones."+ 287 | "buffer size %d - lastRegion %v - currentRegion %v", size, lastRegion, currentRegion) 288 | } 289 | return false 290 | } 291 | 292 | // If the last buffer wasn't read complete the current one can't be overlapped 293 | if lastRegion.Size != size && overlappedBytes > 0 { 294 | t.Errorf("Overlapped buffer after non-complete one. "+ 295 | "buffer size %d - lastRegion %v - currentRegion %v", size, lastRegion, currentRegion) 296 | return false 297 | } 298 | 299 | // Overlapped bytes should be half of the buffer, or the buffer must came from another region 300 | if overlappedBytes != uintptr(size/2) && overlappedBytes != 0 { 301 | t.Errorf("Overlapping buffer by %d bytes. "+ 302 | "buffer size %d - lastRegion %v - currentRegion %v", overlappedBytes, size, lastRegion, 303 | currentRegion) 304 | return false 305 | } 306 | 307 | lastRegion = currentRegion 308 | return true 309 | }) 310 | test.PrintSoftErrors(softerrors) 311 | if err != nil { 312 | t.Fatal(err) 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /memaccess/memaccess_windows.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "memaccess.h" 5 | 6 | inline static BOOL is_readable(MEMORY_BASIC_INFORMATION info); 7 | 8 | response_t *get_next_readable_memory_region(process_handle_t handle, 9 | memory_address_t address, bool *region_available, 10 | memory_region_t *memory_region) { 11 | response_t *response = response_create(); 12 | 13 | memory_region->start_address = 0x0; 14 | memory_region->length = 0; 15 | *region_available = false; 16 | 17 | // Get all the contiguous readable memory regions starting from address. 18 | MEMORY_BASIC_INFORMATION info; 19 | 20 | while (TRUE) { 21 | SIZE_T r = VirtualQueryEx((HANDLE) handle, 22 | (void *) address, 23 | &info, 24 | sizeof(info)); 25 | if (r == 0) { 26 | DWORD err = GetLastError(); 27 | if (err == ERROR_INVALID_PARAMETER) { 28 | // This means that the address we are using is invalid, i.e: no more addresses left! 29 | break; 30 | } 31 | response->fatal_error = error_create(err); 32 | break; 33 | } 34 | 35 | 36 | if (!is_readable(info)) { 37 | if (*region_available) { 38 | break; 39 | } else { 40 | //TODO(mvanotti): Report a soft error here. See darwin version. 41 | address = (memory_address_t) info.BaseAddress + info.RegionSize; 42 | continue; 43 | } 44 | } 45 | 46 | if (!*region_available) { // first time setting it. 47 | *region_available = true; 48 | memory_region->start_address = (memory_address_t) info.BaseAddress; 49 | } else { 50 | //TODO(mvanotti): Check bounds. 51 | if (memory_region->start_address + memory_region->length != 52 | (memory_address_t) info.BaseAddress) { 53 | // This region isn't contiguous to the previous one. 54 | break; 55 | } 56 | } 57 | memory_region->length += info.RegionSize; 58 | address = (memory_address_t) info.BaseAddress + info.RegionSize; 59 | } 60 | return response; 61 | } 62 | 63 | inline static BOOL is_readable(MEMORY_BASIC_INFORMATION info) { 64 | if (info.State == MEM_FREE) { 65 | return FALSE; 66 | } 67 | 68 | switch (info.Protect) { 69 | case PAGE_EXECUTE_READ: 70 | case PAGE_EXECUTE_READWRITE: 71 | case PAGE_READONLY: 72 | case PAGE_READWRITE: 73 | return TRUE; 74 | default: 75 | return FALSE; 76 | } 77 | } 78 | 79 | response_t *copy_process_memory(process_handle_t handle, 80 | memory_address_t start_address, 81 | size_t bytes_to_read, void *buffer, size_t *bytes_read) { 82 | response_t *response = response_create(); 83 | BOOL success = ReadProcessMemory((HANDLE) handle, (void *) start_address, 84 | buffer, 85 | (SIZE_T) bytes_to_read, 86 | (SIZE_T *) bytes_read); 87 | if (!success) { 88 | response->fatal_error = error_create(GetLastError()); 89 | } 90 | 91 | return response; 92 | } 93 | -------------------------------------------------------------------------------- /memsearch/memsearch.go: -------------------------------------------------------------------------------- 1 | package memsearch 2 | 3 | import ( 4 | "bytes" 5 | "github.com/mozilla/masche/memaccess" 6 | "github.com/mozilla/masche/process" 7 | "regexp" 8 | ) 9 | 10 | // FindBytesSequence finds for the first occurrence of needle in the Process starting at a given address (in the 11 | // process address space). If the needle is found the first argument will be true and the second one will contain it's 12 | // address. 13 | func FindBytesSequence(p process.Process, address uintptr, needle []byte) (found bool, foundAddress uintptr, 14 | softerrors []error, harderror error) { 15 | 16 | const minBufferSize = uint(4096) 17 | bufferSize := minBufferSize 18 | if uint(len(needle)) > bufferSize { 19 | bufferSize = uint(len(needle)) 20 | } 21 | 22 | foundAddress = uintptr(0) 23 | found = false 24 | softerrors, harderror = memaccess.SlidingWalkMemory(p, address, bufferSize, 25 | func(address uintptr, buf []byte) (keepSearching bool) { 26 | i := bytes.Index(buf, needle) 27 | if i == -1 { 28 | return true 29 | } 30 | 31 | foundAddress = address + uintptr(i) 32 | found = true 33 | return false 34 | }) 35 | return 36 | } 37 | 38 | // FindRegexpMatch finds the first match of r in the process memory. This function works as FindFindBytesSequence 39 | // but instead of searching for a literal bytes sequence it uses a regexp. It tries to match the regexp in the memory 40 | // as is, not interpreting it as any charset in particular. 41 | func FindRegexpMatch(p process.Process, address uintptr, r *regexp.Regexp) (found bool, foundAddress uintptr, 42 | softerrors []error, harderror error) { 43 | 44 | const bufferSize = uint(4096) 45 | 46 | foundAddress = uintptr(0) 47 | found = false 48 | softerrors, harderror = memaccess.SlidingWalkMemory(p, address, bufferSize, 49 | func(address uintptr, buf []byte) (keepSearching bool) { 50 | loc := r.FindIndex(buf) 51 | if loc == nil { 52 | return true 53 | } 54 | 55 | foundAddress = address + uintptr(loc[0]) 56 | found = true 57 | return false 58 | }) 59 | 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /memsearch/memsearch_test.go: -------------------------------------------------------------------------------- 1 | package memsearch 2 | 3 | import ( 4 | "github.com/mozilla/masche/process" 5 | "github.com/mozilla/masche/test" 6 | "regexp" 7 | "testing" 8 | ) 9 | 10 | var needle = []byte("Find This!") 11 | 12 | var buffersToFind = [][]byte{ 13 | []byte{0xc, 0xa, 0xf, 0xe}, 14 | []byte{0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf}, 15 | []byte{0xb, 0xe, 0xb, 0xe, 0xf, 0xe, 0x0}, 16 | } 17 | 18 | var notPresent = []byte("this string should generate a list of bytes not present in the process") 19 | 20 | var regexpToMatch = []string{ 21 | "Un dia vi una vaca vestida de uniforme", 22 | "Un dia vi.*", 23 | "Un.*vestida de uniforme", 24 | "Un[a-z\\ ]*vestida de", 25 | } 26 | 27 | var regexpToNotMatch = []string{ 28 | "Un dia vi dos vacas vestidas de uniforme", 29 | "Un dia vi.*sin uniforme", 30 | } 31 | 32 | func TestSearchInOtherProcess(t *testing.T) { 33 | cmd, err := test.LaunchTestCaseAndWaitForInitialization() 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | defer cmd.Process.Kill() 38 | 39 | pid := uint(cmd.Process.Pid) 40 | proc, softerrors, err := process.OpenFromPid(pid) 41 | test.PrintSoftErrors(softerrors) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | defer proc.Close() 46 | 47 | for i, buf := range buffersToFind { 48 | found, _, softerrors, err := FindBytesSequence(proc, 0, buf) 49 | test.PrintSoftErrors(softerrors) 50 | if err != nil { 51 | t.Fatal(err) 52 | } else if !found { 53 | t.Fatalf("memoryGrep failed for case %d, the following buffer should be found: %+v", i, buf) 54 | } 55 | } 56 | 57 | // This must not be present 58 | found, _, softerrors, err := FindBytesSequence(proc, 0, notPresent) 59 | test.PrintSoftErrors(softerrors) 60 | if err != nil { 61 | t.Fatal(err) 62 | } else if found { 63 | t.Fatalf("memoryGrep failed, it found a sequense of bytes that it shouldn't") 64 | } 65 | } 66 | 67 | func TestRegexpSearchInOtherProcess(t *testing.T) { 68 | cmd, err := test.LaunchTestCaseAndWaitForInitialization() 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | defer cmd.Process.Kill() 73 | 74 | pid := uint(cmd.Process.Pid) 75 | proc, softerrors, err := process.OpenFromPid(pid) 76 | test.PrintSoftErrors(softerrors) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | defer proc.Close() 81 | 82 | for i, str := range regexpToMatch { 83 | r, err := regexp.Compile(str) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | found, _, softerrors, err := FindRegexpMatch(proc, 0, r) 89 | test.PrintSoftErrors(softerrors) 90 | if err != nil { 91 | t.Fatal(err) 92 | } else if !found { 93 | t.Fatalf("memoryGrep failed for case %d, the following regexp should be found: %s", i, str) 94 | } 95 | } 96 | 97 | // These must not match 98 | for i, str := range regexpToNotMatch { 99 | r, err := regexp.Compile(str) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | found, _, softerrors, err := FindRegexpMatch(proc, 0, r) 105 | test.PrintSoftErrors(softerrors) 106 | if err != nil { 107 | t.Fatal(err) 108 | } else if found { 109 | t.Fatalf("memoryGrep failed for case %d, the following regexp shouldnt be found: %s", i, str) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /process/process.go: -------------------------------------------------------------------------------- 1 | // Package process provides functions to interact with the os processes 2 | // You can list all the processes running on the os, filter them via a regexp 3 | // and then use them from in other masche modules, because they are already open. 4 | package process 5 | 6 | import ( 7 | "fmt" 8 | "regexp" 9 | ) 10 | 11 | // Process type represents a running processes that can be used by other modules. 12 | // In order to get a Process on of the Open* functions must be called, and once it's not needed it must be closed. 13 | type Process interface { 14 | // Pid returns the process' pid. 15 | Pid() uint 16 | 17 | // Name returns the process' binary full path. 18 | Name() (name string, softerrors []error, harderror error) 19 | 20 | // Closes this Process. 21 | Close() (softerrors []error, harderror error) 22 | 23 | // Handle returns an opaque value which's meaning dependes on the OS-specific implementation of it. 24 | // It works like an interface{} that you must cast, but we are using a uintptr because we need to return C values, 25 | // and casting between them in different modules panics if you use interface{}. 26 | Handle() uintptr 27 | } 28 | 29 | // OpenFromPid opens a process by its pid. 30 | func OpenFromPid(pid uint) (p Process, softerrors []error, harderror error) { 31 | // This function is implemented by the OS-specific openFromPid function. 32 | return openFromPid(pid) 33 | } 34 | 35 | // GetAllPids returns a slice with al the running processes' pids. 36 | func GetAllPids() (pids []uint, softerrors []error, harderror error) { 37 | // This function is implemented by the OS-specific getAllPids function. 38 | return getAllPids() 39 | } 40 | 41 | // OpenAll opens all the running processes returning a slice of Process. 42 | // A race condition may make this generate some softerrors because from the time pids are get to actually opened some 43 | // of them may have dead. 44 | func OpenAll() (ps []Process, softerrors []error, harderror error) { 45 | pids, softs, err := GetAllPids() 46 | var softerrs []error 47 | if softs != nil { 48 | softerrs = append(softerrs, softs...) 49 | } 50 | if err != nil { 51 | return nil, softerrs, err 52 | } 53 | 54 | ps = make([]Process, 0) 55 | for _, pid := range pids { 56 | p, softs, err := OpenFromPid(pid) 57 | if err != nil { 58 | softerrs = append(softerrs, fmt.Errorf("Pid: %d failed to Open. Error: %v", pid, err)) 59 | continue 60 | } 61 | if softs != nil { 62 | softerrs = append(softerrs, softs...) 63 | } 64 | ps = append(ps, p) 65 | } 66 | return ps, softerrs, nil 67 | } 68 | 69 | // CloseAll closes all the processes from the given slice. 70 | func CloseAll(ps []Process) (harderrors []error, softerrors []error) { 71 | harderrors = make([]error, 0) 72 | softerrors = make([]error, 0) 73 | 74 | for _, p := range ps { 75 | soft, hard := p.Close() 76 | if hard != nil { 77 | harderrors = append(harderrors, hard) 78 | } 79 | if soft != nil { 80 | softerrors = append(softerrors, soft...) 81 | } 82 | } 83 | 84 | return harderrors, softerrors 85 | } 86 | 87 | // OpenByName receives a Regexp an returns a slice with all the Processes whose name matches it. 88 | func OpenByName(r *regexp.Regexp) (ps []Process, softerrors []error, harderror error) { 89 | procs, softerrors, harderror := OpenAll() 90 | if harderror != nil { 91 | return nil, nil, harderror 92 | } 93 | 94 | var matchs []Process 95 | 96 | for _, p := range procs { 97 | name, softs, err := p.Name() 98 | if err != nil { 99 | softerrors = append(softerrors, err) 100 | } 101 | if softs != nil { 102 | softerrors = append(softerrors, softs...) 103 | } 104 | 105 | if r.MatchString(name) { 106 | matchs = append(matchs, p) 107 | } else { 108 | p.Close() 109 | } 110 | } 111 | 112 | return matchs, softerrors, nil 113 | } 114 | -------------------------------------------------------------------------------- /process/process.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_H 2 | #define PROCESS_H 3 | 4 | #include 5 | 6 | #include "../cresponse/response.h" 7 | 8 | /** 9 | * Process ID type. 10 | **/ 11 | typedef uint32_t pid_tt; 12 | 13 | #ifdef _WIN32 14 | 15 | #include 16 | 17 | /** 18 | * Windows specific process handle. 19 | * 20 | * NOTE: We use uintptr_t instead of HANDLE because Go doesn't allow 21 | * pointers with invalid values. Windows' HANDLE is a PVOID internally and 22 | * sometimes it is used as an integer. 23 | **/ 24 | typedef uintptr_t process_handle_t; 25 | 26 | #endif /* _WIN32 */ 27 | 28 | #ifdef __MACH__ 29 | 30 | #include 31 | 32 | /** 33 | * Mac specific process handle. 34 | **/ 35 | typedef task_t process_handle_t; 36 | 37 | #endif /* __MACH__ */ 38 | 39 | 40 | /** 41 | * Creates a handle for a given process based on its pid. 42 | * 43 | * If a fatal error ocurres the handle must not be used, but it must be closed 44 | * anyway to ensure that all resources are freed. 45 | **/ 46 | response_t *open_process_handle(pid_tt pid, process_handle_t *handle); 47 | 48 | /** 49 | * Closes a specific process handle, freen all its resources. 50 | * 51 | * The process_handle_t must not be used after calling this function. 52 | **/ 53 | response_t *close_process_handle(process_handle_t process_handle); 54 | 55 | #endif /* PROCESS_H */ 56 | 57 | -------------------------------------------------------------------------------- /process/process_c_wrapper.go: -------------------------------------------------------------------------------- 1 | // +build windows darwin 2 | 3 | package process 4 | 5 | // #include "process.h" 6 | // #cgo CFLAGS: -std=c99 7 | import "C" 8 | import ( 9 | "github.com/mozilla/masche/cresponse" 10 | "unsafe" 11 | ) 12 | 13 | type process struct { 14 | hndl C.process_handle_t 15 | pid uint 16 | } 17 | 18 | func (p process) Pid() uint { 19 | return p.pid 20 | } 21 | 22 | func (p process) Handle() uintptr { 23 | return uintptr(p.hndl) 24 | } 25 | 26 | func (p process) Close() (softerrors []error, harderror error) { 27 | resp := C.close_process_handle(p.hndl) 28 | defer C.response_free(resp) 29 | return cresponse.GetResponsesErrors(unsafe.Pointer(resp)) 30 | } 31 | 32 | func openFromPid(pid uint) (p Process, softerrors []error, harderror error) { 33 | var result process 34 | 35 | resp := C.open_process_handle(C.pid_tt(pid), &result.hndl) 36 | softerrors, harderror = cresponse.GetResponsesErrors(unsafe.Pointer(resp)) 37 | C.response_free(resp) 38 | 39 | if harderror == nil { 40 | result.pid = pid 41 | } else { 42 | resp = C.close_process_handle(result.hndl) 43 | C.response_free(resp) 44 | } 45 | 46 | return result, softerrors, harderror 47 | } 48 | -------------------------------------------------------------------------------- /process/process_darwin.c: -------------------------------------------------------------------------------- 1 | #include "process.h" 2 | 3 | response_t *open_process_handle(pid_tt pid, process_handle_t *handle) { 4 | task_t task; 5 | kern_return_t kret; 6 | response_t *response = response_create(); 7 | 8 | kret = task_for_pid(mach_task_self(), pid, &task); 9 | if (kret != KERN_SUCCESS) { 10 | response_set_fatal_from_kret(response, kret); 11 | } else { 12 | *handle = task; 13 | } 14 | 15 | return response; 16 | } 17 | 18 | response_t *close_process_handle(process_handle_t process_handle) { 19 | kern_return_t kret; 20 | response_t *response = response_create(); 21 | 22 | kret = mach_port_deallocate(mach_task_self(), process_handle); 23 | if (kret != KERN_SUCCESS) { 24 | response_set_fatal_from_kret(response, kret); 25 | } 26 | 27 | return response; 28 | } 29 | -------------------------------------------------------------------------------- /process/process_darwin.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | // #cgo CFLAGS: -std=c99 4 | // #include 5 | // #include 6 | // #include 7 | import "C" 8 | 9 | import ( 10 | "fmt" 11 | "path/filepath" 12 | "reflect" 13 | "unsafe" 14 | ) 15 | 16 | func (p process) Name() (name string, softerrors []error, harderror error) { 17 | cname := C.malloc(C.PROC_PIDPATHINFO_MAXSIZE) 18 | defer C.free(cname) 19 | 20 | _, err := C.proc_pidpath(C.int(p.pid), cname, C.PROC_PIDPATHINFO_MAXSIZE) 21 | if err != nil { 22 | harderr := fmt.Errorf("Error while reading name of process %d: %v", p.pid, err) 23 | return "", nil, harderr 24 | } 25 | 26 | name, harderror = filepath.EvalSymlinks(C.GoString((*C.char)(cname))) 27 | return 28 | } 29 | 30 | func getAllPids() (pids []uint, softerrors []error, harderror error) { 31 | var pid C.pid_t 32 | pidSize := unsafe.Sizeof(pid) 33 | cpidsSize := pidSize * 1024 * 2 34 | cpids := C.malloc(C.size_t(cpidsSize)) 35 | defer C.free(cpids) 36 | 37 | bytesUsed, err := C.proc_listpids(C.PROC_ALL_PIDS, 0, cpids, C.int(cpidsSize)) 38 | if err != nil { 39 | return nil, nil, err 40 | } 41 | 42 | numberOfPids := uintptr(bytesUsed) / pidSize 43 | pids = make([]uint, 0, numberOfPids) 44 | cpidsSlice := *(*[]C.pid_t)(unsafe.Pointer( 45 | &reflect.SliceHeader{ 46 | Data: uintptr(unsafe.Pointer(cpids)), 47 | Len: int(numberOfPids), 48 | Cap: int(numberOfPids)})) 49 | 50 | for i := range cpidsSlice { 51 | if cpidsSlice[i] == 0 { 52 | continue 53 | } 54 | 55 | pids = append(pids, uint(cpidsSlice[i])) 56 | } 57 | 58 | return pids, nil, nil 59 | } 60 | -------------------------------------------------------------------------------- /process/process_linux.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/mozilla/masche/common" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type proc uint 16 | 17 | func (p proc) Pid() uint { 18 | return uint(p) 19 | } 20 | 21 | func (p proc) Name() (name string, softerrors []error, harderror error) { 22 | exePath := filepath.Join("/proc", fmt.Sprintf("%d", p.Pid()), "exe") 23 | name, err := filepath.EvalSymlinks(exePath) 24 | 25 | if err != nil { 26 | // If the exe link doesn't take us to the real path of the binary of the process maybe it's not present anymore 27 | // or the process didn't started from a file. We mimic this ps(1) trick and take the name form 28 | // /proc//status in that case. 29 | 30 | statusPath := filepath.Join("/proc", fmt.Sprintf("%d", p.Pid()), "status") 31 | statusFile, err := os.Open(statusPath) 32 | if err != nil { 33 | return name, nil, err 34 | } 35 | 36 | r := bufio.NewReader(statusFile) 37 | for line, _, err := r.ReadLine(); err != io.EOF; line, _, err = r.ReadLine() { 38 | if err != nil { 39 | return name, nil, err 40 | } 41 | 42 | namePrefix := "Name:" 43 | if strings.HasPrefix(string(line), namePrefix) { 44 | name := strings.Trim(string(line[len(namePrefix):]), " \t") 45 | 46 | // We add the square brackets to be consistent with ps(1) output. 47 | return "[" + name + "]", nil, nil 48 | } 49 | } 50 | 51 | return name, nil, fmt.Errorf("No name found for pid %v", p.Pid()) 52 | } 53 | 54 | return name, nil, err 55 | } 56 | 57 | func (p proc) Close() (softerrors []error, harderror error) { 58 | return nil, nil 59 | } 60 | 61 | func (p proc) Handle() uintptr { 62 | return uintptr(p) 63 | } 64 | 65 | func getAllPids() (pids []uint, softerrors []error, harderror error) { 66 | files, err := ioutil.ReadDir("/proc/") 67 | if err != nil { 68 | return nil, nil, err 69 | } 70 | 71 | pids = make([]uint, 0) 72 | 73 | for _, f := range files { 74 | pid, err := strconv.Atoi(f.Name()) 75 | if err != nil { 76 | continue 77 | } 78 | pids = append(pids, uint(pid)) 79 | } 80 | 81 | return pids, nil, nil 82 | } 83 | 84 | func openFromPid(pid uint) (p Process, softerrors []error, harderror error) { 85 | // Check if we have permissions to read the process memory 86 | memPath := common.MemFilePathFromPid(pid) 87 | memFile, err := os.Open(memPath) 88 | if err != nil { 89 | harderror = fmt.Errorf("Permission denied to access memory of process %v", pid) 90 | return 91 | } 92 | defer memFile.Close() 93 | 94 | return proc(pid), nil, nil 95 | } 96 | -------------------------------------------------------------------------------- /process/process_test.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/mozilla/masche/test" 8 | ) 9 | 10 | func TestOpenFromPid(t *testing.T) { 11 | cmd, err := test.LaunchTestCase() 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | defer cmd.Process.Kill() 16 | 17 | pid := uint(cmd.Process.Pid) 18 | proc, softerrors, err := OpenFromPid(pid) 19 | defer proc.Close() 20 | test.PrintSoftErrors(softerrors) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | } 25 | 26 | func TestProcessName(t *testing.T) { 27 | cmd, err := test.LaunchTestCase() 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | defer cmd.Process.Kill() 32 | 33 | pid := uint(cmd.Process.Pid) 34 | proc, softerrors, err := OpenFromPid(pid) 35 | defer proc.Close() 36 | test.PrintSoftErrors(softerrors) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | name, softerrors, err := proc.Name() 42 | test.PrintSoftErrors(softerrors) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | if name != test.GetTestCasePath() { 48 | t.Error("Expected name", test.GetTestCasePath(), "and got", name) 49 | } 50 | } 51 | 52 | func TestOpenByName(t *testing.T) { 53 | cmd, err := test.LaunchTestCase() 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | defer cmd.Process.Kill() 58 | 59 | r := regexp.MustCompile("test[/\\\\]tools[/\\\\]test") 60 | procs, softerrors, err := OpenByName(r) 61 | defer CloseAll(procs) 62 | test.PrintSoftErrors(softerrors) 63 | if len(procs) == 0 { 64 | t.Error("The test case was launched and not opened.") 65 | } 66 | 67 | for _, proc := range procs { 68 | name, softerrors, err := proc.Name() 69 | test.PrintSoftErrors(softerrors) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | if name != test.GetTestCasePath() { 75 | t.Error("Expected name", test.GetTestCasePath(), "and got", name) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /process/process_windows.c: -------------------------------------------------------------------------------- 1 | #include "process.h" 2 | #include "process_windows.h" 3 | #include 4 | #include 5 | #include 6 | 7 | response_t *open_process_handle(pid_tt pid, process_handle_t *handle) { 8 | response_t *res = response_create(); 9 | 10 | *handle = (uintptr_t) OpenProcess(PROCESS_QUERY_INFORMATION | 11 | PROCESS_VM_READ, 12 | FALSE, 13 | pid); 14 | 15 | if (*handle == 0) { 16 | res->fatal_error = error_create(GetLastError()); 17 | } 18 | 19 | return res; 20 | } 21 | 22 | response_t *close_process_handle(process_handle_t process_handle) { 23 | //TODO(mvanotti): See which errors should be considered hard and which ones soft. 24 | response_t *res = response_create(); 25 | BOOL success = CloseHandle((HANDLE) process_handle); 26 | if (!success) { 27 | res->fatal_error = error_create(GetLastError()); 28 | } 29 | 30 | return res; 31 | } 32 | 33 | EnumProcessesResponse *getAllPids() { 34 | DWORD size = sizeof(DWORD) * 512; 35 | DWORD *aProcesses = NULL; 36 | DWORD cbNeeded; 37 | EnumProcessesResponse *res = calloc(1, sizeof * res); 38 | // EnumProcesses modifies cbNeeded, setting it to the amount of bytes 39 | // written into aProcesses. Thus, we need to check if cbNeeded is equal 40 | // to size. In that case, it means that the array was filled completely and 41 | // we need to use a bigger array because probably we left elements out. 42 | do { 43 | size *= 2; 44 | aProcesses = realloc(aProcesses, size); 45 | BOOL success = EnumProcesses(aProcesses, size, &cbNeeded); 46 | if (!success) { 47 | res->error = GetLastError(); 48 | free(aProcesses); 49 | return res; 50 | } 51 | } while (cbNeeded == size); 52 | res->error = 0; 53 | res->pids = aProcesses; 54 | res->length = cbNeeded / sizeof(DWORD); 55 | return res; 56 | } 57 | 58 | void EnumProcessesResponse_Free(EnumProcessesResponse *r) { 59 | if (r == NULL) { 60 | return; 61 | } 62 | free(r->pids); 63 | free(r); 64 | } 65 | 66 | response_t *GetProcessName(process_handle_t hndl, char **name) { 67 | response_t *res = response_create(); 68 | HMODULE hMod; 69 | DWORD cbNeeded; 70 | // The first module is the executable. 71 | BOOL success = EnumProcessModules( (HANDLE) hndl, &hMod, sizeof(hMod), &cbNeeded); 72 | if (!success) { 73 | res->fatal_error = error_create(GetLastError()); 74 | return res; 75 | } 76 | 77 | TCHAR buf[MAX_PATH + 1]; 78 | 79 | DWORD len = GetModuleFileNameEx((HANDLE) hndl, hMod, buf, sizeof(buf) / sizeof(TCHAR)); 80 | if (len == 0) { 81 | res->fatal_error = error_create(GetLastError()); 82 | return res; 83 | } 84 | buf[MAX_PATH] = '\0'; 85 | 86 | *name = (char *) _tcsdup(buf); 87 | return res; 88 | } 89 | -------------------------------------------------------------------------------- /process/process_windows.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | // #cgo CFLAGS: -std=c99 4 | // #cgo CFLAGS: -DPSAPI_VERSION=1 5 | // #cgo LDFLAGS: -lpsapi 6 | // #include "process.h" 7 | // #include "process_windows.h" 8 | import "C" 9 | 10 | import ( 11 | "fmt" 12 | "reflect" 13 | "unsafe" 14 | 15 | "github.com/mozilla/masche/cresponse" 16 | ) 17 | 18 | func (p process) Name() (name string, softerrors []error, harderror error) { 19 | var cname uintptr 20 | r := C.GetProcessName(p.hndl, (**C.char)(unsafe.Pointer(&cname))) 21 | 22 | harderror, softerrors = cresponse.GetResponsesErrors(unsafe.Pointer(r)) 23 | C.response_free(r) 24 | if harderror == nil { 25 | name = C.GoString((*C.char)(unsafe.Pointer(cname))) 26 | C.free(unsafe.Pointer(cname)) 27 | } 28 | return 29 | } 30 | 31 | func getAllPids() (pids []uint, harderror error, softerrors []error) { 32 | r := C.getAllPids() 33 | defer C.EnumProcessesResponse_Free(r) 34 | if r.error != 0 { 35 | return nil, fmt.Errorf("getAllPids failed with error %d", r.error), nil 36 | } 37 | 38 | pids = make([]uint, 0, r.length) 39 | // We use this to access C arrays without doing manual pointer arithmetic. 40 | cpids := *(*[]C.DWORD)(unsafe.Pointer( 41 | &reflect.SliceHeader{ 42 | Data: uintptr(unsafe.Pointer(r.pids)), 43 | Len: int(r.length), 44 | Cap: int(r.length)})) 45 | for i, _ := range cpids { 46 | pid := uint(cpids[i]) 47 | // pids 0 and 4 are reserved in windows. 48 | if pid == 0 || pid == 4 { 49 | continue 50 | } 51 | pids = append(pids, pid) 52 | } 53 | 54 | return pids, nil, nil 55 | } 56 | -------------------------------------------------------------------------------- /process/process_windows.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_WINDOWS_H 2 | #define PROCESS_WINDOWS_H 3 | #include 4 | #include 5 | #include "../cresponse/response.h" 6 | 7 | typedef struct t_EnumProcessesResponse { 8 | DWORD error; 9 | DWORD *pids; 10 | DWORD length; 11 | } EnumProcessesResponse; 12 | 13 | EnumProcessesResponse *getAllPids(); 14 | void EnumProcessesResponse_Free(EnumProcessesResponse *r); 15 | response_t *GetProcessName(process_handle_t hndl, char **name); 16 | 17 | #endif /* PROCESS_WINDOWS_H */ 18 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | // Package test contains utility methos for testing 2 | package test 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | ) 12 | 13 | // GetTestCasePath returns the path for test case executable 14 | func GetTestCasePath() string { 15 | //TODO: Right now the command is hardcoded. We should decide how to fix this. 16 | dirPath, err := filepath.Abs("../test/tools") 17 | testFileName := "test" 18 | 19 | // Ugly hack for windows: The testFileName must end in .exe 20 | if runtime.GOOS == "windows" { 21 | testFileName = "test.exe" 22 | } 23 | 24 | if err != nil { 25 | fmt.Fprintln(os.Stderr, err.Error()) 26 | os.Exit(-1) 27 | } 28 | 29 | path, err := filepath.EvalSymlinks(filepath.Join(dirPath, testFileName)) 30 | if err != nil { 31 | fmt.Fprintln(os.Stderr, err.Error()) 32 | os.Exit(-1) 33 | } 34 | 35 | return path 36 | } 37 | 38 | // PrintSoftErrors method prints all softerrors on Stderr 39 | func PrintSoftErrors(softerrors []error) { 40 | for _, err := range softerrors { 41 | fmt.Fprintln(os.Stderr, err.Error()) 42 | } 43 | } 44 | 45 | // LaunchTestCase method redirects the process's stdout to the test stdout 46 | func LaunchTestCase() (*exec.Cmd, error) { 47 | cmd := exec.Command(GetTestCasePath()) 48 | cmd.Stdout = os.Stdout 49 | cmd.Stderr = os.Stderr 50 | err := cmd.Start() 51 | return cmd, err 52 | } 53 | 54 | // LaunchTestCaseAndWaitForInitialization method launches test case and waits for initialization 55 | func LaunchTestCaseAndWaitForInitialization() (*exec.Cmd, error) { 56 | return launchProcessAndWaitInitialization(GetTestCasePath()) 57 | } 58 | 59 | // starts a process and waits until it writes everythin to stdout: that way we know it has been initialized. 60 | // the process launched should close stdout once it has been fully initialized. 61 | // this method redirects the process's stdout to the test stdout 62 | func launchProcessAndWaitInitialization(file string) (*exec.Cmd, error) { 63 | cmd := exec.Command(file) 64 | 65 | childout, err := cmd.StdoutPipe() 66 | if err != nil { 67 | return nil, err 68 | } 69 | defer childout.Close() 70 | 71 | if err := cmd.Start(); err != nil { 72 | return nil, err 73 | } 74 | 75 | io.Copy(os.Stdout, childout) 76 | 77 | return cmd, nil 78 | } 79 | -------------------------------------------------------------------------------- /test/tools/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-Wall -Wextra -pedantic -std=c99 -O0 3 | TESTFILE=known_byte_sequences.c 4 | 5 | all: test64 test32 6 | 7 | test64: 8 | $(CC) $(CFLAGS) $(TESTFILE) -o test 9 | 10 | test32: 11 | $(CC) $(CFLAGS) $(TESTFILE) -m32 -o test 12 | 13 | clean: 14 | rm test tests32 15 | -------------------------------------------------------------------------------- /test/tools/known_byte_sequences.c: -------------------------------------------------------------------------------- 1 | //Compile this program with -O0 2 | #include 3 | #include 4 | #ifdef _WIN32 5 | #include 6 | #define sleep(X) Sleep(X) 7 | #else 8 | #include 9 | #endif 10 | 11 | int main(void) { 12 | char *string_regexp = "Un dia vi una vaca vestida de uniforme"; 13 | char *in_data_segment = "\xC\xA\xF\xE"; 14 | 15 | char in_stack[] = {0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf}; 16 | 17 | char *in_heap = malloc(7 * sizeof(char)); 18 | in_heap[0] = 0xb; 19 | in_heap[1] = 0xe; 20 | in_heap[2] = 0xb; 21 | in_heap[3] = 0xe; 22 | in_heap[4] = 0xf; 23 | in_heap[5] = 0xe; 24 | in_heap[6] = 0x0; 25 | 26 | // By writing to stdout and flushing we are letting the parent process know that we have initialized everything. 27 | printf("In Data Segment: %p\n" 28 | "In Stack: %p\n" 29 | "In Heap: %p\n" 30 | "Regexp String: %p\n", in_data_segment, in_stack, in_heap, string_regexp); 31 | fclose(stdout); 32 | 33 | for (;;) sleep(1); 34 | 35 | return 0; 36 | } 37 | --------------------------------------------------------------------------------