├── .gitignore
├── .idea
├── .gitignore
├── modules.xml
└── misc.xml
├── README.md
├── JVMDumper.iml
├── Cargo.toml
├── LICENSE
└── src
└── lib.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | Cargo.lock
3 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JVMDumper
2 | Injectable dll to dump classes from the jvm.
3 | Inject this dll file into the java process you want to dump the class files from.
4 | It will hook the ClassLoader and write all dumped classes into 'C:\Dump'.
5 | If the process it is injected into exits all dumped class files will be packed into a .jar archive.
6 |
7 |
8 | [download dll](https://github.com/Slimig/JVMDumper/releases/tag/1.0.0)
9 |
--------------------------------------------------------------------------------
/JVMDumper.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "JVMDumper"
3 | version = "0.1.0"
4 | authors = ["Slimig "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | winapi = { version = "0.3.6", features = ["libloaderapi"] }
9 | wio = "0.2.2"
10 | jni = "0.18.0"
11 | detour = "0.8.0"
12 | zip = "0.5"
13 | walkdir = "2.3.1"
14 |
15 | [lib]
16 | name = "jvmdumper"
17 | # defines a DLL to be exported. a "cdylib" would also work just fine, and is in some cases necessary, but wouldn't necessarily allow you to use all the features in the stdlib
18 | crate-type = ["dylib"]
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Slimig
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![feature(fn_traits)]
2 |
3 | use std::ffi::CStr;
4 | use std::fs::{create_dir, File, remove_dir_all};
5 | use std::io::{Read, Seek, Write};
6 | use std::path::Path;
7 | use std::thread;
8 |
9 | use detour::static_detour;
10 | use jni::JNIEnv as JEnv;
11 | use jni::objects::JString;
12 | use jni::sys::{jbyteArray, jclass, jint, JNIEnv, jobject, jstring};
13 | use walkdir::{DirEntry, WalkDir};
14 | use winapi::_core::mem;
15 | use winapi::shared::minwindef::{DWORD, HINSTANCE, LPVOID};
16 | use wio::wide::ToWide;
17 | use zip::CompressionMethod;
18 | use zip::result::ZipError;
19 | use zip::write::FileOptions;
20 | use winapi::um::libloaderapi::{GetProcAddress, GetModuleHandleW};
21 |
22 | #[cfg(not(target_os = "windows"))]
23 | compile_error!("only for windows");
24 |
25 | static PREFIX: &str = "[JVMDump]";
26 | static PATH: &str = "C:\\Dump";
27 | static DUMP_FILE: &str = "C:\\Dump\\Dump.jar";
28 |
29 | static_detour! {
30 | static define_class_1_hook: unsafe extern "C" fn(*mut JNIEnv,
31 | jobject,
32 | jstring,
33 | jbyteArray,
34 | jint,
35 | jint,
36 | jobject,
37 | jstring) -> jclass;
38 | }
39 |
40 | type FnDefineClass1 = extern "C" fn(*mut JNIEnv, jobject, jstring, jbyteArray,
41 | jint, jint, jobject, jstring) -> jclass;
42 |
43 | fn dll_attach() {
44 | unsafe {
45 | winapi::um::consoleapi::AllocConsole();
46 | winapi::um::wincon::SetConsoleTitleA(cstr!("JVMDump | by Slimig"));
47 |
48 | println!("{} initializing...", PREFIX);
49 |
50 | let module_name = "java.dll";
51 | let java_module = GetModuleHandleW(module_name.to_wide_null().as_ptr());
52 |
53 | if java_module.is_null() {
54 | println!("{} java.dll module not found! exiting..", PREFIX);
55 | return;
56 | }
57 |
58 | println!("{} found java.dll (0x{:X})", PREFIX, java_module as usize);
59 |
60 | let address = GetProcAddress(java_module, cstr!("Java_java_lang_ClassLoader_defineClass1"));
61 |
62 | if address.is_null() {
63 | println!("{} function defineClass1 not found! exiting..", PREFIX);
64 | return;
65 | }
66 |
67 | println!("{} found defineClass1 (0x{:X})", PREFIX, address as usize);
68 |
69 | let hook: FnDefineClass1 = mem::transmute(address);
70 |
71 | define_class_1_hook
72 | .initialize(hook, classloader_hook).unwrap()
73 | .enable().unwrap();
74 |
75 | println!("{} hooked defineClass1", PREFIX);
76 | println!("{} dumping classes to '{}'", PREFIX, PATH);
77 | }
78 | }
79 |
80 | fn classloader_hook(env: *mut JNIEnv, loader: jobject, name: jstring, data: jbyteArray,
81 | offset: jint, length: jint, pd: jobject, source: jstring) -> jclass {
82 | unsafe {
83 | let java = JEnv::from_raw(env.clone()).unwrap();
84 | let name_ptr = java.get_string_utf_chars(JString::from(name)).unwrap();
85 |
86 | let class_path = to_string(name_ptr).replace(".", "\\");
87 | let class_name: String = [class_path.split('\\').last().unwrap(), ".class"].join("");
88 | let path = format!("{}\\{}", PATH, class_path.replace(class_path.split('\\').last().unwrap(), ""));
89 | let bytes = java.convert_byte_array(data).unwrap();
90 |
91 | if !Path::new(path.as_str()).exists() {
92 | std::fs::create_dir_all(path.as_str()).unwrap();
93 | }
94 |
95 | let mut file = std::fs::File::create(format!("{}\\{}", path, class_name)).unwrap();
96 | match file.write_all(bytes.as_slice()) {
97 | Ok(_) => {
98 | println!("{} dumped class {}", PREFIX, class_name);
99 | }
100 | Err(_) => {
101 | println!("{} failed to dump {}", PREFIX, class_name);
102 | }
103 | }
104 |
105 | define_class_1_hook.call(env, loader, name, data, offset, length, pd, source)
106 | }
107 | }
108 |
109 | fn dll_detach() {
110 | unsafe {
111 | winapi::um::wincon::FreeConsole();
112 | match copy_to_jar(PATH, DUMP_FILE, CompressionMethod::Stored) {
113 | Ok(_) => println!("{} dumped class files copied into '{}'", PREFIX, DUMP_FILE),
114 | Err(e) => println!("Error: {:?}", e),
115 | }
116 | }
117 | }
118 |
119 | fn copy_to_jar(
120 | src_dir: &str,
121 | dst_file: &str,
122 | method: zip::CompressionMethod,
123 | ) -> zip::result::ZipResult<()> {
124 | if !Path::new(src_dir).is_dir() {
125 | return Err(ZipError::FileNotFound);
126 | }
127 |
128 | let path = Path::new(dst_file);
129 | let file = File::create(&path).unwrap();
130 |
131 | let walkdir = WalkDir::new(src_dir.to_string());
132 | let it = walkdir.into_iter();
133 |
134 | zip_dir(&mut it.filter_map(|e| e.ok()), src_dir, file, method)?;
135 |
136 | Ok(())
137 | }
138 |
139 | #[allow(deprecated)]
140 | fn zip_dir(it: &mut dyn Iterator- , prefix: &str,
141 | writer: T, method: zip::CompressionMethod, ) -> zip::result::ZipResult<()> where T: Write + Seek, {
142 | let mut zip = zip::ZipWriter::new(writer);
143 | let options = FileOptions::default()
144 | .compression_method(method);
145 |
146 | let mut buffer = Vec::new();
147 | for entry in it {
148 | let path = entry.path();
149 | let name = path.strip_prefix(Path::new(prefix)).unwrap();
150 |
151 | if path.to_str().unwrap().contains(DUMP_FILE) {
152 | continue;
153 | }
154 |
155 | if path.is_file() {
156 | zip.start_file_from_path(name, options)?;
157 | let mut file = File::open(path)?;
158 |
159 | file.read_to_end(&mut buffer)?;
160 | zip.write_all(&*buffer)?;
161 | buffer.clear();
162 | } else if name.as_os_str().len() != 0 {
163 | zip.add_directory_from_path(name, options)?;
164 | }
165 | }
166 | zip.finish()?;
167 | Result::Ok(())
168 | }
169 |
170 | fn init() {
171 | if !Path::new(PATH).exists() {
172 | create_dir(PATH).unwrap();
173 | } else {
174 | remove_dir_all(PATH).unwrap();
175 | create_dir(PATH).unwrap()
176 | }
177 |
178 | thread::spawn(move || {
179 | dll_attach();
180 | });
181 | }
182 |
183 | #[no_mangle]
184 | #[allow(non_snake_case, unused_variables)]
185 | pub extern "system" fn DllMain(
186 | dll_module: HINSTANCE,
187 | call_reason: DWORD,
188 | reserved: LPVOID)
189 | -> i32
190 | {
191 | const DLL_PROCESS_ATTACH: DWORD = 1;
192 | const DLL_PROCESS_DETACH: DWORD = 0;
193 |
194 | match call_reason {
195 | DLL_PROCESS_ATTACH => init(),
196 | DLL_PROCESS_DETACH => {
197 | unsafe {
198 | dll_detach();
199 |
200 | winapi::um::libloaderapi::FreeLibraryAndExitThread(reserved as _, 1);
201 |
202 | unreachable!()
203 | }
204 | }
205 | _ => ()
206 | }
207 |
208 | return true as i32;
209 | }
210 |
211 | fn to_string(pointer: *const i8) -> String {
212 | let slice = unsafe { CStr::from_ptr(pointer).to_bytes() };
213 | std::str::from_utf8(slice).unwrap().to_string()
214 | }
215 |
216 | #[macro_export]
217 | macro_rules! cstr {
218 | ( $ literal: literal ) => {
219 | ( concat ! ( $ literal, "\0").as_ptr() as * const i8)
220 | };
221 | }
222 |
--------------------------------------------------------------------------------