Added initial client UI structure.

This commit is contained in:
Jakob Friedl
2025-09-02 12:48:46 +02:00
parent 4ae9add3af
commit f7d97908d1
8 changed files with 387 additions and 8 deletions

View File

@@ -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"
requires "ptr_math >= 0.3.0"
requires "imguin >= 1.92.2.0"

36
data/layout.ini Normal file
View File

@@ -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

View File

@@ -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 /<listener>/<agent>/<upload-task>/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 /<listener>/<agent>/<download>-task/file
- Encrypt file in-transit!!!

73
src/client/config.nims Normal file
View File

@@ -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

112
src/client/main.nim Normal file
View File

@@ -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)

View File

@@ -1,2 +0,0 @@
-d:"adwminor=4"
--outdir:"../bin"

97
src/client/utils/lib.nim Normal file
View File

@@ -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)

View File

@@ -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()