Reworked installation instructions.
@@ -27,7 +27,7 @@ nimble client
|
|||||||
3. Start the team server with a C2 profile.
|
3. Start the team server with a C2 profile.
|
||||||
|
|
||||||
```
|
```
|
||||||
bin/server -p data/profile.toml
|
sudo bin/server -p data/profile.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Start the operator client and connect it to a team server
|
4. Start the operator client and connect it to a team server
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
BIN
assets/listener.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
@@ -12,27 +12,48 @@ cd conquest
|
|||||||
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
|
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
3. The Conquest binaries for team server and client are designed to be compiled on a UNIX system using the `nimble` command. This command installs and updates all dependencies and third-party libraries automatically.
|
After it is installed, the Nim binaries need to be added to the PATH.
|
||||||
|
|
||||||
|
```
|
||||||
|
export PATH=/home/kali/.nimble/bin:$PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Install dependencies
|
||||||
|
|
||||||
|
The operator client requires the following dependencies to be installed on Ubuntu/Debian systems.
|
||||||
|
```
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install gcc g++ make git curl xz-utils
|
||||||
|
sudo apt install libglfw3-dev libgl1-mesa-dev libglu1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. The Conquest binaries for team server and client are designed to be compiled on a UNIX system using the `nimble` command. This command installs and updates all dependencies and third-party libraries automatically.
|
||||||
```
|
```
|
||||||
nimble server
|
nimble server
|
||||||
nimble client
|
nimble client
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optionally, the required dependencies can be installed manually using the following command.
|
||||||
|
|
||||||
|
```
|
||||||
|
nimble install -d
|
||||||
|
```
|
||||||
|
|
||||||
4. Start the Conquest team server with a C2 profile. The default profile is located in data/profile.toml and can be adapted by the operator.
|
4. Start the Conquest team server with a C2 profile. The default profile is located in data/profile.toml and can be adapted by the operator.
|
||||||
```
|
```
|
||||||
bin/server -p data/profile
|
sudo bin/server -p data/profile
|
||||||
```
|
```
|
||||||
|
|
||||||
On the first start, the Conquest team server creates the Conquest database in the data directory, as well as the team server's private key in data/keys, which is used for the key exchange between team server, client and agent.
|
On the first start, the Conquest team server creates the Conquest database in the data directory, as well as the team server's private key in data/keys, which is used for the key exchange between team server, client and agent.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
5. Start the Conquest operator client
|
5. Start the Conquest operator client
|
||||||
```
|
```
|
||||||
bin/client
|
bin/client
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the Conquest client connects to localhost:37573 to connect to the team server. The address and port can be specified from the command-line using the `-i` and `-p` flags. The team server port is specified in the malleable C2 profile.
|
By default, the Conquest client connects to localhost:37573 to connect to the team server. The address and port can be specified from the command-line using the `-i` and `-p` flags, in order to connect to a remote team server. The team server port is specified in the malleable C2 profile.
|
||||||
|
|
||||||
```
|
```
|
||||||
bin/client -i <team-server-address> -p <team-server-port>
|
bin/client -i <team-server-address> -p <team-server-port>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
The Conquest command & control framework consist of three major components that interact with each other in different ways. Together, they enable penetration tester and red teamers to remotely control systems, transfer files and more. The diagram below shows Conquests's overall architecture.
|
The Conquest command & control framework consist of three major components that interact with each other in different ways. Together, they enable penetration tester and red teamers to remotely control systems, transfer files and more. The diagram below shows Conquests's overall architecture.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Team Server
|
### Team Server
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ The Conquest client is used by the operator to conduct the engagement. It is use
|
|||||||
bin/client -i <team-server-ip> -p <team-server-port>
|
bin/client -i <team-server-ip> -p <team-server-port>
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
More information about the user interface can be found [here](./4-CLIENT.md)
|
More information about the user interface can be found [here](./4-CLIENT.md)
|
||||||
|
|
||||||
@@ -349,4 +349,4 @@ The `teamserver.log` records other events, that don't involve an interaction wit
|
|||||||
|
|
||||||
In Conquest, the term loot encompasses file downloads and screenshots retrieved from an agent. While metadata about these loot items is stored in the database, the actual files and images are also stored on disk on the team server in the data/loot directory.
|
In Conquest, the term loot encompasses file downloads and screenshots retrieved from an agent. While metadata about these loot items is stored in the database, the actual files and images are also stored on disk on the team server in the data/loot directory.
|
||||||
|
|
||||||

|

|
||||||
@@ -79,7 +79,7 @@ prefix = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
|
|||||||
suffix = ".######################################-####"
|
suffix = ".######################################-####"
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Check the [default profile](../data/profile.toml) for more examples.
|
Check the [default profile](../data/profile.toml) for more examples.
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ Connection = "Keep-Alive"
|
|||||||
Cache-Control = "no-cache"
|
Cache-Control = "no-cache"
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
### Response options
|
### Response options
|
||||||
@@ -165,4 +165,4 @@ Server = "nginx"
|
|||||||
placement = { type = "body" }
|
placement = { type = "body" }
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Listeners <!-- omit from toc -->
|
||||||
|
|
||||||
|
Listeners can be started by pressing the **Start Listener** button in the **Listeners** view. This opens the following modal popup.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| Protocol | Listener type. Currently only `http` listeners are implemented |
|
||||||
|
| Host (Bind) | IP address or interface that the listener binds to on the team server |
|
||||||
|
| Port (Bind) | Port that the listeners bind to on the team server |
|
||||||
|
| Hosts (Callback) | Callback hosts, one per line. The hosts are defined, separated by new-lines, in the format `<ip/domain>:<port>`. If no port is specified, the bind port is used instead. If no callback hosts are defined at all, the bind host and bind port are used.<br>Callback hosts are the endpoints that the `Monarch` agent connects to. If multiple are defined, a random entry of the list of callback hosts is selected for each request.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Agents <!-- omit from toc -->
|
||||||
|
|
||||||
|
## Contents <!-- omit from toc -->
|
||||||
|
|
||||||
|
- [The Monarch](#the-monarch)
|
||||||
|
- [Sleep settings](#sleep-settings)
|
||||||
|
- [Sleep Obfuscation](#sleep-obfuscation)
|
||||||
|
- [Working hours](#working-hours)
|
||||||
|
- [String obfuscation](#string-obfuscation)
|
||||||
|
- [Kill date](#kill-date)
|
||||||
|
- [Evasion](#evasion)
|
||||||
|
|
||||||
|
## The Monarch
|
||||||
|
|
||||||
|
The `Monarch` agent can be customized using the payload generation modal pop-up, which is opened by pressing the **Generate Payload** button in the **Listeners** view.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Sleep settings
|
||||||
|
|
||||||
|
### Sleep Obfuscation
|
||||||
|
|
||||||
|
### Working hours
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## String obfuscation
|
||||||
|
|
||||||
|
## Kill date
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Evasion
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Modules <!-- omit from toc -->
|
||||||
|
|
||||||
|
## Contents <!-- omit from toc -->
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [EXIT](#exit)
|
||||||
|
- [SLEEP](#sleep)
|
||||||
|
- [SHELL](#shell)
|
||||||
|
- [BOF](#bof)
|
||||||
|
- [DOTNET](#dotnet)
|
||||||
|
- [FILESYSTEM](#filesystem)
|
||||||
|
- [FILETRANSFER](#filetransfer)
|
||||||
|
- [SCREENSHOT](#screenshot)
|
||||||
|
- [SYSTEMINFO](#systeminfo)
|
||||||
|
- [TOKEN](#token)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Currently, the following commands are available in the `Monarch` agent when all modules are activated.
|
||||||
|
|
||||||
|
```
|
||||||
|
* exit Exit the agent.
|
||||||
|
* self-destruct Exit the agent and delete the executable from disk.
|
||||||
|
* sleep Update sleep delay settings.
|
||||||
|
* sleepmask Update sleepmask settings.
|
||||||
|
* shell Execute a shell command and retrieve the output.
|
||||||
|
* bof Execute an object file in memory and retrieve the output.
|
||||||
|
* dotnet Execute a .NET assembly in memory and retrieve the output.
|
||||||
|
* pwd Retrieve current working directory.
|
||||||
|
* cd Change current working directory.
|
||||||
|
* ls List files and directories.
|
||||||
|
* rm Remove a file.
|
||||||
|
* rmdir Remove a directory.
|
||||||
|
* move Move a file or directory.
|
||||||
|
* copy Copy a file or directory.
|
||||||
|
* download Download a file.
|
||||||
|
* upload Upload a file.
|
||||||
|
* screenshot Take a screenshot of the target system.
|
||||||
|
* ps Display running processes.
|
||||||
|
* env Display environment variables.
|
||||||
|
* make-token Create an access token from username and password.
|
||||||
|
* steal-token Steal the primary access token of a remote process.
|
||||||
|
* rev2self Revert to original access token.
|
||||||
|
* token-info Retrieve information about the current access token.
|
||||||
|
* enable-privilege Enable a token privilege.
|
||||||
|
* disable-privilege Disable a token privilege.
|
||||||
|
```
|
||||||
|
|
||||||
|
## EXIT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## SLEEP
|
||||||
|
|
||||||
|
## SHELL
|
||||||
|
|
||||||
|
## BOF
|
||||||
|
|
||||||
|
## DOTNET
|
||||||
|
|
||||||
|
## FILESYSTEM
|
||||||
|
|
||||||
|
## FILETRANSFER
|
||||||
|
|
||||||
|
## SCREENSHOT
|
||||||
|
|
||||||
|
## SYSTEMINFO
|
||||||
|
|
||||||
|
## TOKEN
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# "Monarch" Agent commands:
|
|
||||||
|
|
||||||
House-keeping
|
|
||||||
-------------
|
|
||||||
- [x] sleep : Set sleep obfuscation duration to a different value and persist that value in the agent
|
|
||||||
|
|
||||||
Basic API-only Commands
|
|
||||||
-----------------------
|
|
||||||
- [x] pwd : Get current working directory
|
|
||||||
- [x] cd : Change directory
|
|
||||||
- [x] ls/dir : List all files in directory (including hidden ones)
|
|
||||||
- [x] rm : Remove a file
|
|
||||||
- [x] rmdir : Remove a empty directory
|
|
||||||
- [x] mv : Move a file
|
|
||||||
- [x] cp : Copy a file
|
|
||||||
- [ ] cat/type : Display contents of a file
|
|
||||||
- [x] env : Display environment variables
|
|
||||||
- [x] ps : List processes
|
|
||||||
- [ ] whoami : Get UID and privileges, etc.
|
|
||||||
|
|
||||||
- [ ] token : Token impersonation
|
|
||||||
- [ ] make : Create a token from a user's plaintext password (LogonUserA, ImpersonateLoggedOnUser)
|
|
||||||
- [ ] steal : Steal the access token from a process (OpenProcess, OpenProcessToken, DuplicateToken, ImpersonateLoggedOnUser)
|
|
||||||
- [ ] use : Impersonate a token from the token vault (ImpersonateLoggedOnUser) -> update username like in Cobalt Strike
|
|
||||||
- [ ] rev2self : Revert to original logon session (RevertToSelf)
|
|
||||||
|
|
||||||
Execution Commands
|
|
||||||
------------------
|
|
||||||
- [x] shell : Execute shell command (to be implemented using Windows APIs instead of execCmdEx)
|
|
||||||
- [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)
|
|
||||||
- [x] dotnet : Execute .NET assembly inline in memory and retrieve output (dotnet /local/path/Rubeus.exe )
|
|
||||||
|
|
||||||
Post-Exploitation
|
|
||||||
-----------------
|
|
||||||
- [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
|
|
||||||
- [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!!!
|
|
||||||
- [x] screenshot : Take a screenshot of the entire desktop and all monitors
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Installation Guide
|
|
||||||
|
|
||||||
1. Clone the Conquest repository
|
|
||||||
```
|
|
||||||
git clone https://github.com/jakobfriedl/conquest
|
|
||||||
cd conquest
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install Nim
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Install Nimble dependencies
|
|
||||||
```
|
|
||||||
nimble install -d
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Build conquest binaries
|
|
||||||
```
|
|
||||||
nimble server
|
|
||||||
nimble client
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Start the Conquest server with a C2 Profile and connect to it with the client
|
|
||||||
```bash
|
|
||||||
./bin/server -p ./data/profile.toml
|
|
||||||
./bin/client -i localhost -p 35753
|
|
||||||
```
|
|
||||||
|
|
||||||
BIN
docs/image-1.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
docs/image-2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/image.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
@@ -29,6 +29,18 @@ else: # for Linux
|
|||||||
|
|
||||||
when STATIC_LINK_GLFW: # GLFW static link
|
when STATIC_LINK_GLFW: # GLFW static link
|
||||||
switch "define","glfwStaticLib"
|
switch "define","glfwStaticLib"
|
||||||
|
when defined(windows):
|
||||||
|
discard # Windows-specific handling if needed
|
||||||
|
else: # Linux
|
||||||
|
switch "passL","-lglfw"
|
||||||
|
switch "passL","-lX11"
|
||||||
|
switch "passL","-lXrandr"
|
||||||
|
switch "passL","-lXinerama"
|
||||||
|
switch "passL","-lXcursor"
|
||||||
|
switch "passL","-lXi"
|
||||||
|
switch "passL","-lpthread"
|
||||||
|
switch "passL","-ldl"
|
||||||
|
switch "passL","-lm"
|
||||||
else: # shared/dll
|
else: # shared/dll
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
if TC == "vcc":
|
if TC == "vcc":
|
||||||
@@ -39,6 +51,8 @@ else: # shared/dll
|
|||||||
#switch "define","cimguiDLL"
|
#switch "define","cimguiDLL"
|
||||||
else:
|
else:
|
||||||
switch "passL","-lglfw"
|
switch "passL","-lglfw"
|
||||||
|
# Add X11 libs for shared linking too
|
||||||
|
switch "passL","-lX11"
|
||||||
|
|
||||||
when STATIC_LINK_CC: # gcc static link
|
when STATIC_LINK_CC: # gcc static link
|
||||||
case TC
|
case TC
|
||||||
@@ -75,4 +89,5 @@ case TC
|
|||||||
of "clang":
|
of "clang":
|
||||||
switch "cc.exe","clang"
|
switch "cc.exe","clang"
|
||||||
switch "cc.linkerexe","clang"
|
switch "cc.linkerexe","clang"
|
||||||
switch "cc",TC
|
switch "cc",TC
|
||||||
|
|
||||||
|
|||||||
@@ -123,12 +123,12 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
|||||||
of CLIENT_AGENT_PAYLOAD:
|
of CLIENT_AGENT_PAYLOAD:
|
||||||
let payload = decode(event.data["payload"].getStr())
|
let payload = decode(event.data["payload"].getStr())
|
||||||
try:
|
try:
|
||||||
let outFilePath = fmt"{CONQUEST_ROOT}/bin/monarch.x64.exe"
|
let path = fmt"{CONQUEST_ROOT}/bin/monarch.x64.exe"
|
||||||
|
|
||||||
# TODO: Using native file dialogs to have the client select the output file path (does not work in WSL)
|
# TODO: Using native file dialogs to have the client select the output file path (does not work in WSL)
|
||||||
# let outFilePath = callDialogFileSave("Save Payload")
|
# let path = callDialogFileSave("Save Payload")
|
||||||
|
|
||||||
writeFile(outFilePath, payload)
|
writeFile(path, payload)
|
||||||
except IOError:
|
except IOError:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ when defined(windows):
|
|||||||
("segoeui.ttf", "Seoge UI", 14.4),
|
("segoeui.ttf", "Seoge UI", 14.4),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else: # For Debian/Ubuntu/Mint
|
else: # Linux
|
||||||
const
|
const
|
||||||
fontInfo = TFontInfo(
|
fontInfo = TFontInfo(
|
||||||
osRootDir: "/",
|
osRootDir: "/",
|
||||||
fontDir: "usr/share/fonts",
|
fontDir: "usr/share/fonts",
|
||||||
fontTable: @[
|
fontTable: @[
|
||||||
("truetype/noto/NotoSansMono-Regular.ttf", "Noto Sans Mono", 20.0)
|
("truetype/noto/NotoSansMono-Regular.ttf", "Noto Sans Mono", 14.4)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import strformat, strutils, times, os, tables
|
import strformat, strutils, times, os, tables # native_dialogs
|
||||||
import imguin/[cimgui, glfw_opengl, simple]
|
import imguin/[cimgui, glfw_opengl, simple]
|
||||||
import ../../utils/[appImGui, colors]
|
import ../../utils/[appImGui, colors]
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, utils]
|
||||||
@@ -96,8 +96,11 @@ proc draw*(component: DownloadsComponent, showComponent: ptr bool, connection: W
|
|||||||
if igMenuItem("Download", nil, false, true):
|
if igMenuItem("Download", nil, false, true):
|
||||||
# Download file
|
# Download file
|
||||||
try:
|
try:
|
||||||
# TODO: Use native dialogs to select the download location
|
|
||||||
let path = item.path & ".download"
|
let path = item.path & ".download"
|
||||||
|
|
||||||
|
# TODO: Use native dialogs to select the download location
|
||||||
|
# let path = callDialogFileSave("Save File")
|
||||||
|
|
||||||
let data = component.contents[item.lootId]
|
let data = component.contents[item.lootId]
|
||||||
writeFile(path, data)
|
writeFile(path, data)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import strformat, strutils, times, os, tables
|
import strformat, strutils, times, os, tables # native_dialogs
|
||||||
import imguin/[cimgui, glfw_opengl, simple]
|
import imguin/[cimgui, glfw_opengl, simple]
|
||||||
import ../../utils/[appImGui, colors]
|
import ../../utils/[appImGui, colors]
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, utils]
|
||||||
@@ -104,8 +104,11 @@ proc draw*(component: ScreenshotsComponent, showComponent: ptr bool, connection:
|
|||||||
if igMenuItem("Download", nil, false, true):
|
if igMenuItem("Download", nil, false, true):
|
||||||
# Download screenshot
|
# Download screenshot
|
||||||
try:
|
try:
|
||||||
# TODO: Use native dialogs to select the download location
|
|
||||||
let path = item.path & "_download.jpeg"
|
let path = item.path & "_download.jpeg"
|
||||||
|
|
||||||
|
# TODO: Use native dialogs to select the download location
|
||||||
|
# let path = callDialogFileSave("Save File")
|
||||||
|
|
||||||
let data = component.textures[item.lootId].data
|
let data = component.textures[item.lootId].data
|
||||||
writeFile(path, data)
|
writeFile(path, data)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
|
|||||||
component.killDate = killDate
|
component.killDate = killDate
|
||||||
|
|
||||||
# Working hours
|
# Working hours
|
||||||
igText("Working Hours: ")
|
igText("Working hours: ")
|
||||||
igSameLine(0.0f, textSpacing)
|
igSameLine(0.0f, textSpacing)
|
||||||
igCheckbox("##InputWorkingHours", addr component.workingHoursEnabled)
|
igCheckbox("##InputWorkingHours", addr component.workingHoursEnabled)
|
||||||
igSameLine(0.0f, textSpacing)
|
igSameLine(0.0f, textSpacing)
|
||||||
|
|||||||