diff --git a/conquest.nimble b/conquest.nimble index 36a439f..27accff 100644 --- a/conquest.nimble +++ b/conquest.nimble @@ -9,12 +9,12 @@ srcDir = "src" # Build tasks import os, strformat +let cqRoot = getCurrentDir() task server, "Build conquest server binary": - let cqRoot = getCurrentDir() exec fmt"nim c -d:CONQUEST_ROOT={cqRoot} src/server/main.nim" task client, "Build conquest client binary": - discard + exec fmt"nim c -d:release -d:CONQUEST_ROOT={cqRoot} src/client/main.nim" # Dependencies @@ -27,4 +27,5 @@ requires "nimcrypto >= 0.6.4" requires "tiny_sqlite >= 0.2.0" requires "prologue >= 0.6.6" requires "winim >= 3.9.4" -requires "ptr_math >= 0.3.0" \ No newline at end of file +requires "ptr_math >= 0.3.0" +requires "imguin >= 1.92.2.0" \ No newline at end of file diff --git a/data/layout.ini b/data/layout.ini new file mode 100644 index 0000000..bdb4c84 --- /dev/null +++ b/data/layout.ini @@ -0,0 +1,36 @@ +[Window][CQ] +Size=2868,1695 +Collapsed=0 + +[Window][Dear ImGui Demo] +Pos=0,30 +Size=2868,1665 +Collapsed=0 +DockId=0x00000001,0 + +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Window][Conquest] +Pos=0,0 +Size=2868,1695 +Collapsed=0 + +[Window][Dear ImGui Style Editor] +Pos=123,79 +Size=1080,1629 +Collapsed=0 + +[Window][Example: Console] +Pos=0,781 +Size=2868,914 +Collapsed=0 +DockId=0x00000002,0 + +[Docking][Data] +DockSpace ID=0x3674675E Window=0x538FB738 Pos=0,30 Size=2868,1665 Split=Y Selected=0x5E5F7166 + DockNode ID=0x00000001 Parent=0x3674675E SizeRef=2868,749 CentralNode=1 Selected=0x5E5F7166 + DockNode ID=0x00000002 Parent=0x3674675E SizeRef=2868,914 Selected=0x1BCA3180 + diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index c7b5340..a47ce4b 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -27,7 +27,7 @@ Basic API-only Commands Execution Commands ------------------ - [x] shell : Execute shell command (to be implemented using Windows APIs instead of execCmdEx) -- [ ] bof : Execute Beacon Object File in memory and retrieve output (bof /local/path/file.o) +- [x] bof : Execute Beacon Object File in memory and retrieve output (bof /local/path/file.o) - Read from listener endpoint directly to memory - Base for all kinds of BOFs (Situational Awareness, ...) - [ ] pe : Execute PE file in memory and retrieve output (pe /local/path/mimikatz.exe) @@ -35,10 +35,10 @@ Execution Commands Post-Exploitation ----------------- -- [ ] upload : Upload file from server to agent (upload /local/path/to/file C:\Windows\Tasks) +- [x] upload : Upload file from server to agent (upload /local/path/to/file C:\Windows\Tasks) - File to be downloaded moved to specific endpoint on listener, e.g. GET ////file - Read from webserver and written to disk -- [ ] download : Download file from agent to teamserver +- [x] download : Download file from agent to teamserver - Create loot directory for agent to store files in - Read file into memory and send byte stream to specific endpoint, e.g. POST ///-task/file - Encrypt file in-transit!!! diff --git a/src/client/config.nims b/src/client/config.nims new file mode 100644 index 0000000..8da85d1 --- /dev/null +++ b/src/client/config.nims @@ -0,0 +1,73 @@ +switch "o", "bin/client" + +# Select compiler +var TC = "gcc" +# var TC = "clang" + +# Dismiss background window +switch "app", "gui" + +# Select static link or shared/dll link +when defined(windows): + const STATIC_LINK_GLFW = false + const STATIC_LINK_CC = true #libstd++ or libc + if TC == "vcc": + switch "passL","d3d9.lib kernel32.lib user32.lib gdi32.lib winspool.lib" + switch "passL","comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib" + switch "passL","uuid.lib odbc32.lib odbccp32.lib" + switch "passL","imm32.lib" + else: + switch "passL","-lgdi32 -limm32 -lcomdlg32 -luser32 -lshell32" +else: # for Linux + const STATIC_LINK_GLFW = true + const STATIC_LINK_CC= false + +when STATIC_LINK_GLFW: # GLFW static link + switch "define","glfwStaticLib" +else: # shared/dll + when defined(windows): + if TC == "vcc": + discard + else: + switch "passL","-lglfw3.dll" + switch "define", "glfwDLL" + #switch "define","cimguiDLL" + else: + switch "passL","-lglfw" + +when STATIC_LINK_CC: # gcc static link + case TC + of "vcc": + discard + else: + switch "passC", "-static" + switch "passL", "-static " + +# Set compiler options +case TC + of "vcc" , "clang_cl": + switch "define","lto" + else: + if "" == findExe(TC): # GCC is default compiler if TC dosn't exist on the PATH + echo "#### Set to cc = ",TC + TC = "gcc" + if "" == findExe(TC): # if gcc dosn't exist, try clang + TC = "clang" + echo "#### Set to cc = ",TC + +# Reduce code size further +when false: + switch "gc", "arc" + switch "define", "useMalloc" + switch "define", "noSignalHandler" + +case TC + of "gcc": + switch "passC", "-ffunction-sections" + switch "passC", "-fdata-sections" + switch "passL", "-Wl,--gc-sections" + switch "cc",TC + of "clang": + switch "cc.exe","clang" + switch "cc.linkerexe","clang" + switch "cc",TC \ No newline at end of file diff --git a/src/client/main.nim b/src/client/main.nim new file mode 100644 index 0000000..f783831 --- /dev/null +++ b/src/client/main.nim @@ -0,0 +1,112 @@ +import nimgl/[opengl, glfw] +import imguin/glfw_opengl +import ./utils/[lib, windowInit] + +proc main(hWin: glfw.GLFWWindow) = + var + clearColor: CColor + showWindowDelay = 1 + showMainWindow = true + windowClass = ImGuiWindowClass_ImGuiWindowClass() + opt_fullscreen: bool = true + opt_padding: bool = false + dockspaceFlags: ImGuiDockNodeFlags = ImGuiDockNodeFlags_None.int32 + windowFlags: ImGuiWindow_Flags = ImGuiWindowFlags_MenuBar.int32 or ImGuiWindowFlags_NoDocking.int32 + + # Setup background and theme colors (Background is only seen when opt_padding = true) + clearColor = CColor(elm:(x:0.0f, y:0.0f, z:0.0f, w:0.0f)) + igStyleColorsClassic(nil) + + # Setup fonts + discard setupFonts() + + let io = igGetIO() + + # main loop + while not hWin.windowShouldClose: + glfwPollEvents() + + if getWindowAttrib(hWin, GLFW_ICONIFIED) != 0: + ImGui_ImplGlfw_Sleep(10) + continue + + # Start ImGUI frame + ImGui_ImplOpenGL3_NewFrame() + ImGui_ImplGlfw_NewFrame() + igNewFrame() + + # Create Dockspace where all windows are placed in + if opt_fullscreen: + var vp = igGetMainViewport() + igSetNextWindowPos(vp.WorkPos, ImGui_Cond_None.int32, vec2(0.0f, 0.0f)) + igSetNextWindowSize(vp.WorkSize, 0) + igSetNextWindowViewport(vp.ID) + igPushStyleVar_Float(ImGuiStyleVar_WindowRounding.int32, 0.0f) + igPushStyleVar_Float(ImGuiStyleVar_WindowBorderSize.int32, 0.0f) + + windowFlags = windowFlags or ImGuiWindowFlags_NoTitleBar.int32 or ImGuiWindowFlags_NoCollapse.int32 or ImGuiWindowFlags_NoResize.int32 or ImGuiWindowFlags_NoMove.int32 + windowFlags = windowFlags or ImGuiWindowFlags_NoBringToFrontOnFocus.int32 or ImGuiWindowFlags_NoNavFocus.int32 + + else: + dockspaceFlags = cast[ImGuiDockNodeFlags](dockspaceFlags and not ImGuiDockNodeFlags_PassthruCentralNode.int32) + + if (dockspaceFlags and ImGuiDockNodeFlags_PassthruCentralNode.int32) == ImGuiDockNodeFlags_None.int32: + windowFlags = cast[ImGuiWindow_Flags](windowFlags or ImGuiWindowFlags_NoBackground.int32) + + if not opt_padding: + igPushStyleVar_Vec2(ImGuiStyleVar_WindowPadding.int32, vec2(0.0f, 0.0f)) + + igBegin("Conquest", addr showMainWindow, windowFlags) + + if not opt_padding: + igPopStyleVar(1) + + if opt_fullscreen: + igPopStyleVar(2) + + # Create dockspace + if (io.ConfigFlags and ImGui_ConfigFlags_DockingEnable.int32) != ImGui_ConfigFlags_None.int32: + igDockSpace(igGetID_Str("Conquest-Dockspace"), vec2(0.0f, 0.0f), dockspaceFlags, windowClass) + + # Create Dockspace menu bar + if igBeginMenuBar(): + if igBeginMenu("Options", true): + + igMenuItem("Fullscreen", nil, addr opt_fullscreen, true) + igMenuItem("Padding", nil, addr opt_padding, true) + + if igMenuItem("Close", nil, false, (addr showMainWindow) != nil): + showMainWindow = false + igEndMenu() + igEndMenuBar() + + # Components and widgets + igShowDemoWindow(nil) + + + + + igEnd() + + # render + igRender() + glClearColor(clearColor.elm.x, clearColor.elm.y, clearColor.elm.z, clearColor.elm.w) + glClear(GL_COLOR_BUFFER_BIT) + ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData()) + + if 0 != (io.ConfigFlags and ImGui_ConfigFlags_ViewportsEnable.int32): + var backup_current_window = glfwGetCurrentContext() + igUpdatePlatformWindows() + igRenderPlatformWindowsDefault(nil, nil) + backup_current_window.makeContextCurrent() + + hWin.swapBuffers() + + if showWindowDelay > 0: + dec showWindowDelay + else: + once: # Avoid flickering screen at startup. + hWin.showWindow() + +when isMainModule: + windowInit(main) diff --git a/src/client/nim.cfg b/src/client/nim.cfg deleted file mode 100644 index 5c8294a..0000000 --- a/src/client/nim.cfg +++ /dev/null @@ -1,2 +0,0 @@ --d:"adwminor=4" ---outdir:"../bin" \ No newline at end of file diff --git a/src/client/utils/lib.nim b/src/client/utils/lib.nim new file mode 100644 index 0000000..bcd645c --- /dev/null +++ b/src/client/utils/lib.nim @@ -0,0 +1,97 @@ +import imguin/cimgui + +type + Vec2* = ImVec2 + Vec4* = ImVec4 + +proc vec2*(x, y: auto): ImVec2 = + ImVec2(x: x.cfloat, y: y.cfloat) + +proc vec4*(x, y, z, w: auto): ImVec4 = + ImVec4(x: x.cfloat , y: y.cfloat , z: z.cfloat , w: w.cfloat) + +# Tooltips +proc setTooltip*(str:string, delay=Imgui_HoveredFlags_DelayNormal.cint, color=ImVec4(x: 1.0, y: 1.0, z: 1.0, w: 1.0)) = + if igIsItemHovered(delay): + if igBeginTooltip(): + igPushStyleColorVec4(ImGuiCol_Text.cint, color) + igText(str) + igPopStyleColor(1) + igEndTooltip() + +# IM_COL32 +proc IM_COL32*(a,b,c,d:uint32): ImU32 = + return igGetColorU32_Vec4(vec4(a.cfloat/255, b.cfloat/255, c.cfloat/255, d.cfloat/255)) + +# Definitions from imguin/simple (https://github.com/dinau/imguin/blob/main/src/imguin/simple.nim) +{.push discardable.} # Push discardable applies the {.discardable.} pragma to all functions until the {.pop.} pragma is reached +when false: + type CColor* = object + x,y,z,w: cfloat + + proc array3(self:ccolor): array[3,cfloat] = + result = cast[array[3,cfloat]]([self.x,self.y,self.z]) + + proc newCColor(col:ImVec4):ccolor = + result.x = col.x + result.y = col.y + result.z = col.z + result.w = col.w + + proc vec4*(self:ccolor): ImVec4 = + ImVec4(x:self.x,y:self.y,z:self.z,w:self.z) + +else: + type CColor* {.union.} = object + elm*: tuple[x,y,z,w: cfloat] + array3*: array[3, cfloat] + vec4*: ImVec4 + +proc igInputTextWithHint*(label: string, hint: string, buf: string, bufsize: int = buf.len, flags:Imguiinputtextflags = 0.Imguiinputtextflags, callback: ImguiInputTextCallback = nil, userdata: pointer = nil): bool {.inline,discardable.} = + igInputTextWithHint(label.cstring, hint.cstring, buf.cstring, bufsize.cuint, flags, callback, userdata) + +proc igPlotLines*[T](label:string, arry: openArray[T], size:int= arry.len, offset:int = 0, overlayText:string = "", smin:float = igGetFLTMax(), smax:float = igGetFLTMax(), graphSize:Imvec2 = ImVec2(x:0,y:0), stride:int = sizeof(cfloat)) {.inline.} = + igPlotLinesFloatPtr(label.cstring, cast[ptr T](addr arry), size.cint, offset.cint, overlayText.cstring, smin.cfloat, smax.cfloat, graphSize, stride.cint) + +when defined(ImKnobsEnable) or defined(ImKnobs): + proc IgKnobEx*(label: cstring; p_value: ptr cfloat; v_min: cfloat; v_max: cfloat; speed: cfloat; format: cstring; variant: IgKnobVariant; size: cfloat; flags: IgKnobFlags; steps: cint; angle_min: cfloat; angle_max: cfloat): bool = + return IgKnobFloat(label, p_value, v_min, v_max, speed, format, variant, size, flags, steps, angle_min, angle_max) + + proc IgKnob*(label: cstring; p_value: ptr cfloat; v_min: cfloat; v_max: cfloat): bool = + return IgKnobFloat(label, p_value, v_min, v_max, 0, "%.3f", IgKnobVariant_Tick.IgKnobVariant,0, cast[IgKnobFlags](0),10,-1,-1) + +proc igPushStyleColor*(idx: ImGuiCol; col: ImU32) = igPushStyleColor_U32(idx, col) +proc igPushStyleColor*(idx: ImGuiCol; col: ImVec4) = igPushStyleColor_Vec4(idx, col) +proc igSameLine*() = igSameLine(0.0, -1.0) + +proc igBeginMenuEx*(label: cstring, icon: cstring, enabled: bool = true): bool {.importc: "igBeginMenuEx".} +proc igMenuItem*(label: cstring, shortcut: cstring = nil, selected: bool = false, enabled: bool = true): bool {.importc: "igMenuItem_Bool".} +proc igMenuItem*(label: cstring, shortcut: cstring, p_selected: ptr bool, enabled: bool = true): bool {.importc: "igMenuItem_BoolPtr".} +proc igMenuItemEx*(label: cstring, icon: cstring, shortcut: cstring = nil, selected: bool = false, enabled: bool = true): bool {.importc: "igMenuItemEx".} + +proc igBeginChild*(str_id: cstring, size: ImVec2 = ImVec2(x: 0, y: 0), border: bool = false, flags: ImGuiWindowFlags = 0.ImGuiWindowFlags): bool {.importc: "igBeginChild_Str".} +proc igBeginChild*(id: ImGuiID, size: ImVec2 = ImVec2(x: 0, y: 0), border: bool = false, flags: ImGuiWindowFlags = 0.ImGuiWindowFlags): bool {.importc: "igBeginChild_ID".} + +when not defined(igGetIO): + template igGetIO*(): ptr ImGuiIO = + igGetIO_Nil() + +{.pop.} + +# Fonts +proc pointToPx*(point: float32): cfloat = + return ((point * 96) / 72).cfloat + +proc setupFonts*(): (bool, string, string) = + + let io = igGetIO() + let + fontPath = "/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf" + fontName = "NotoMono-Regular" + fontSize = pointToPx(18.0f) + + # Set base font + io.Fonts.ImFontAtlas_AddFontFromFileTTF(fontPath.cstring, fontSize, nil, nil) + + result = (true, fontPath, fontName) + diff --git a/src/client/utils/windowInit.nim b/src/client/utils/windowInit.nim new file mode 100644 index 0000000..e96d723 --- /dev/null +++ b/src/client/utils/windowInit.nim @@ -0,0 +1,62 @@ +import os +import nimgl/[opengl, glfw] +import imguin/glfw_opengl +import ./lib + +const CONQUEST_ROOT* {.strdefine.} = "" + +when defined(windows): + when not defined(vcc): # imguinVcc.res TODO WIP + include ./res/resource + import tinydialogs + +# Forward definitions +proc windowInit*(winMain: proc(win: glfw.GLFWWindow)) = + doAssert glfwInit() + defer: glfwTerminate() + + glfwWindowHint(GLFWContextVersionMajor, 3) + glfwWindowHint(GLFWContextVersionMinor, 3) + glfwWindowHint(GLFWOpenglForwardCompat, GLFW_TRUE) + glfwWindowHint(GLFWOpenglProfile, GLFW_OPENGL_CORE_PROFILE) + glfwWindowHint(GLFWResizable, GLFW_TRUE) + glfwWindowHint(GLFWVisible, GLFW_FALSE) + glfwWindowHint(GLFWMaximized, GLFW_TRUE) # Maximize Window on startup + + var glfwWin = glfwCreateWindow(1024, 800, "Conquest") + if glfwWin.isNil: + quit(-1) + + glfwWin.makeContextCurrent() + defer: glfwWin.destroyWindow() + + glfwSwapInterval(1) # Enable vsync + + # TODO: Set application icon (requires imguin_examples/utils/loadImage [https://github.com/dinau/imguin_examples/blob/main/utils/opengl/loadImage.nim]) + # var IconName = joinPath(CONQUEST_ROOT, "src/client/resources/icon.png") + # LoadTileBarIcon(glfwWin, IconName) + + doAssert glInit() # OpenGL init + + # Setup ImGui + let context = igCreateContext(nil) + defer: context.igDestroyContext() + + # Configure docking + var pio = igGetIO() + pio.ConfigFlags = pio.ConfigFlags or ImGui_ConfigFlags_DockingEnable.int32 + + # GLFW + OpenGL + const glsl_version = "#version 130" # GL 3.0 + GLSL 130 + doAssert ImGui_ImplGlfw_InitForOpenGL(cast[ptr GLFWwindow](glfwwin), true) + defer: ImGui_ImplGlfw_Shutdown() + doAssert ImGui_ImplOpenGL3_Init(glsl_version) + defer: ImGui_ImplOpenGL3_Shutdown() + + # Set ini filename + pio.IniFileName = CONQUEST_ROOT & "/data/layout.ini" + + glfwWin.winMain() + + +