-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Getting rid of huge switch statement #61
base: master
Are you sure you want to change the base?
Conversation
Nice! That mirrors the style for the adding in the functions for registration on the Mythic piece more with the init() style declarations. I'll have to pull it down and do some testing to make sure there's no issues with that mod for the What is it you're looking to do for dynamic loading? |
I often faced the situation when I need some particular code (e.g. chmod) to execute on some particular agent, but want to avoid using pty. And the process of running new instance of poseidon with added specific functions can be tricky, noisy or even can crash the service because of the nature of the initial vulnerability. In that case it would be great to be able to load code into poseidon dynamically. Currently i am working on API to dynamically download shared object from external service to in-memory file and load the code from it. Currently the main issue is that |
Interesting. I didn't think Go played well with dynamically loading new functionality, so I'm super interested to see where this goes! |
By the way, I found why i cannot import poseidon structs from external modules. I trying to import
Should I open another PR in order to fix at least second go.mod? |
how is it you're trying to import it? Neither were made with the intent to be libraries for 3rd party programs to import, so maybe that's why you're running into issues. There's two separate Go projects - the one at |
Will changing the module name for As I said, I want to implement dynamic loaded modules, I think it will be great if these modules will not be a part of poseidon repo, but it requires to re-use some part of poseidon code, for example Here is the example of the dynamically loaded task handler I want to make package main
import (
"github.com/MythicAgents/poseidon/Payload_Type/poseidon/agent_code"
)
func Run(structs.Task) {
// handler logic here
}
func OnLoad(registerFunc func(string, func(structs.Task))) bool {
registerFunc("funcName", Run)
} So basically we are adding only
However, I am not sure if that is the only thing which will not allow us to reuse the part of poseidon code in dynamic modules, not really good at golang to be honest :) |
Ah I see what you mean about the pathing now. I don't think it'll be an issue, but by all means change the path and see if it breaks anything :) There is one other issue though. You want to create |
Regarding the Mythic side, I am planning to follow the same aproach as Apollo does here https://github.com/MythicAgents/Apollo/blob/d3e58d65be1350789ff2268998cb96356ac454bf/Payload_Type/apollo/apollo/mythic/agent_functions/load.py#L204 Not sure if I will be able to do that, but at least i am ready to try :) |
That'll only work for commands that are already registered with Mythic. Apollo allows you to build an agent with only a subset of commands, then at run-time dynamically load commands that you chose not to include initially. It doesn't support adding completely new commands like that at run-time. |
Hmm, thank you for pointing this out. Will do some more research then |
There's still options, but that way won't work. We can also potentially add in new things to support something like this, but just gotta brainstorm how it might work. One stop-gap that you might be able to do in the meantime:
|
d5b7965
to
2f2cf48
Compare
Well, after some research it turned out that golang officially doesnt provide any compatibility between different toolchain versions and even between builds using the same toolchain but the different source code. It means that there is always a chance that main program will crash if we will try to dynamically load external module just because compiler decided to organize structs in a different way. Considering this, my idea of dynamic module loading doesnt feel reliable enough, however I still think that getting rid of the huge switch statement is a good improvement :) |
However I realized that we can use C pointers to communicate between main process and loaded libraries. It will require a bit of boilerplate, manual memory management and structs serialization, but should work well. So the current idea is to call module functions as following: import (
"unsafe"
"os"
)
/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
typedef char* (*wrapper_type)(char*); // function pointer type
char* wrapper(void* f, char* s) { // wrapper function
return ((wrapper_type) f)(s);
}
*/
import "C"
func CallLibraryFunc(library, fname, fargs string) {
// fname is function name, fargs is serialized (JSON?) arguments
libname := C.CString(library)
defer C.free(unsafe.Pointer(libname))
handle := C.dlopen(libname, C.RTLD_LAZY)
if handle == nil {
panic("Cannot open lib")
}
sym := C.CString(fname)
defer C.free(unsafe.Pointer(sym))
p := C.dlsym(handle, sym)
if p == nil {
panic("Cannot find symbol")
}
args := C.CString(fargs)
defer C.free(unsafe.Pointer(args))
cres := C.wrapper(p, args)
result := C.GoString(cres)
defer C.free(unsafe.Pointer(cres))
// serialized result processing here
} And the module part package main
import (
"C"
"fmt"
)
//export run
func run(args *C.char) *C.char {
fmt.Printf("In library with %s\n", C.GoString(args))
return C.CString("result")
}
func main(){} Basically, on the module side we need only to boilerplate communication protocol, which can be provided as a reusable package I believe |
That's pretty similar to what we do already for the The downside to this command and the one you're describing is that they're non streaming. So, say you wanted to run a long-running task with this, you'd only get output when the entire thing finishes. |
We probably can fix this downside using However, if we dont bother about memory consumption, it seems to be possible to implement some kind of C middleware which will provide channel between different Go runtimes. Writing dynamic modules in C should also work fine, btw :) Here is the example how this channel can look like. package main
import (
"fmt"
"unsafe"
"os"
)
/*
#cgo LDFLAGS: -ldl -Wl,--allow-multiple-definition
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
typedef char* (*wrapper_type)(void *);
static inline char* wrapper(void* f, void *h) {
return ((wrapper_type) f)(h);
}
void my_go_func(int);
void do_print(int a) {
my_go_func(a);
}
*/
import "C"
var c chan int
//export my_go_func
func my_go_func(arg C.int) {
fmt.Println("my_go_func called")
c <- int(arg)
fmt.Println("int sent to chan")
}
func main() {
c = make(chan int, 10)
name := os.Args[1]
fmt.Printf("Trying to use %s\n", name)
libname := C.CString(name)
defer C.free(unsafe.Pointer(libname))
handle := C.dlopen(libname, C.RTLD_LAZY)
if handle == nil {
panic("Cannot open lib")
}
init_sym := C.CString("module_init")
defer C.free(unsafe.Pointer(init_sym))
init := C.dlsym(handle, init_sym)
if init == nil {
panic("Cannot find symbol")
}
C.wrapper(init, C.do_print)
} and the module part package main
import (
"C"
"fmt"
"unsafe"
)
/*
typedef char* (*wrapper_type)(int );
static inline char* wrapper(void* f, int h) {
return ((wrapper_type) f)(h);
}
*/
import "C"
//export module_init
func module_init(cb unsafe.Pointer) {
C.wrapper(cb, C.int(1337))
}
func main(){} |
Hi. I am planning to add dynamic loading features to poseidon, however it seems to be impossible in current implementation because of this huge switch statement. This PR is trying to change this switch statement to a map of functions.
Feel free to comment with your suggestions on improvements if I missed a point somewhere