Skip to content

Commit

Permalink
Merge pull request #33 from Squiblydoo/cert-support
Browse files Browse the repository at this point in the history
Reviewed, pushed to pypi. All systems are go.
  • Loading branch information
Squiblydoo authored May 18, 2024
2 parents a6201b4 + 025ad72 commit 8a19f0e
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 14 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
# Debloat
Debloat is a GUI and CLI tool to remove excess garbage from bloated executables.

By excess garbage, I mean 100 - 800 MB of junk bytes added to a binary to keep it from going into a sandbox.
By excess garbage, I mean 100 - 800 MB of junk bytes added to a binary to keep it from going into a sandbox. This method of adding junk is called "inflating" or "pumping" a binary. Debloat currently handles the 10 most common inflation tactics.

Being built with Python, the code and logic is easily accessible for others to take the concepts and apply them to their own tools. The program can be compiled for Windows, MacOS, Linux. The GUI removes any need for remembering commandline options and reading through CLI manuals: it is intended to be as simple as possible. The logic within the program handles the different use cases automatically.
Being built with Python, the application can easily be leveraged in other workflows. Currently, debloat is used by [CCCS's AssemblyLine](https://www.cyber.gc.ca/en/tools-services/assemblyline) and [CERT Polska's MWDB](https://github.com/CERT-Polska/karton-archive-extractor).

The program can be compiled for Windows, MacOS, Linux. The GUI and CLI have minimal options: it is intended to be as simple as possible and the logic within the program handles the different use cases automatically.

Compiled binaries have already been included in the [Releases](https://github.com/Squiblydoo/debloat/releases/).

Expand All @@ -31,7 +33,7 @@ The gui can also be launched from the CLI using the command `debloat-gui`.

## Does it always work?
Not yet.
My unscientific guess is that it should work for every 7 of 8 binaries. There are specific usecases I know where it does not work and I am working to implement solutions for those usecases.
My unscientific guess is that it should work for every 9 of 10 binaries. There are specific usecases I know where it does not work and I am working to implement solutions for those usecases.

In previous versions, `debloat` could accidentally remove too much of the binary. That is no longer the case unless you use the "--last-ditch" switch. If you ever need this switch, consider sharing the sample for additional analysis. This option has now been added to the GUI. Functionally, what the function does is it will remove the whole overlay, if there is one. In some cases this is necessary as no pattern for the junk was found---this is most commonly the case in samples that do not compress well.

Expand Down
7 changes: 7 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
1.5.6
- Cert Support
- Added support in both CLI and GUI to preserve the authenticode certificate.
- Authenticode certificate is removed by default because the certificate becomes invalid. When it becomes invalid it becomes unclear whether the certificate was always invalid or not.
- Bug Fix
- A result code was missing which could cause problems in processing that looked for a result code.

1.5.5
- General Improvements
- Added functionality to print debloat version/ added to GUI UI
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "debloat"
version = "1.5.5"
version = "1.5.6"
authors = [
{ name="Squiblydoo", email="Squiblydoo@pm.me" },
]
Expand Down
7 changes: 7 additions & 0 deletions src/debloat/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ def __init__(self) -> None:
variable=self.unsafe_processing)
self.unsafe_checkbox.pack()

self.cert_preservation = BooleanVar(value=False)
self.cert_checkbox = Checkbutton(self,
text="Preserve Cert. Cert will be invalid but informational.",
variable=self.cert_preservation)
self.cert_checkbox.pack()



# Define Scrollbox for output of program.
Expand Down Expand Up @@ -90,6 +96,7 @@ def process(self) -> None:

result_code = debloat.processor.process_pe(pe, out_path,
self.unsafe_processing.get(),
self.cert_preservation.get(),
log_message=self.output_scrollbox_handler,
beginning_file_size=file_size)
self.output_scrollbox_handler("Tactic identified: " , RESULT_CODES.get(result_code) +"\n")
Expand Down
8 changes: 8 additions & 0 deletions src/debloat/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ def main() -> int:
whole PE Overlay as a last resort if no smarter method works.
""",
action='store_true', default=False)
parser.add_argument("-c", "--cert", dest="cert_preservation",
help="""
Preserve the certificate on the end of the file if there is a certificate.
The certificate will no longer be valid.""",
action='store_true',
required=False,
default=False)
parser.add_argument("-v", "--version", action='version', version='debloat version ' + DEBLOAT_VERSION, help="Prints program version")
args = parser.parse_args()

Expand All @@ -49,6 +56,7 @@ def main() -> int:
result_code = debloat.processor.process_pe(pe,
out_path=str(out_path),
last_ditch_processing=args.last_ditch_processing,
cert_preservation=args.cert_preservation,
log_message=print,
beginning_file_size=file_size
)
Expand Down
2 changes: 1 addition & 1 deletion src/debloat/performanceTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
def process_samples(sample, directory):
file_size=os.path.getsize(args.directory +"/"+ sample)
setup = f"import pefile; import debloat; filename = '{args.directory}/{sample}'; "
code = f"binary = pefile.PE(filename, fast_load=True); result= debloat.processor.process_pe(binary, filename + '.patched', last_ditch_processing=False, log_message=lambda *args, **kwargs: None, beginning_file_size={file_size}); print(result, end=' ')"
code = f"binary = pefile.PE(filename, fast_load=True); result= debloat.processor.process_pe(binary, filename + '.patched', last_ditch_processing=False, cert_preservation=True, log_message=lambda *args, **kwargs: None, beginning_file_size={file_size}); print(result, end=' ')"

if args.mem:
mem_profiler(setup, code, file_size, sample, directory)
Expand Down
30 changes: 21 additions & 9 deletions src/debloat/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,14 @@ def find_last_section(pe: pefile.PE) -> Optional[pefile.SectionStructure]:
last_section = section
return last_section

def get_signature_info(pe: pefile.PE) -> Tuple[int, int]:
def get_signature_info(pe: pefile.PE, cert_preservation) -> Tuple[int, int]:
'''Remove PE signature and update header.'''
signature_address = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress
signature_size = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].Size
pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress = 0
pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].Size = 0
# If the cert is to be preservered, we do not need to modify the size in the header.
if cert_preservation == False:
pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].Size = 0

return signature_address, signature_size

Expand Down Expand Up @@ -395,6 +397,7 @@ def check_section_compression(pe: pefile.PE, data_to_delete: List,
# Remove the junk from the section.
if delta_last_non_junk > original_section_size:
log_message("Section was not able to be reduced.")
result_code = 0
return result
data_to_delete.append((biggest_section.PointerToRawData + delta_last_non_junk, biggest_section_end))

Expand Down Expand Up @@ -484,21 +487,28 @@ def trim_junk(pe: pefile.PE, bloated_content: memoryview,
return delta_last_non_junk, result_code

def process_pe(pe: pefile.PE, out_path: str, last_ditch_processing: bool,
log_message: Callable[[str], None], beginning_file_size: int = 0) -> None:
cert_preservation: bool,log_message: Callable[[str], None],
beginning_file_size: int = 0) -> None:
'''Prepare PE, perform checks, remote junk, write patched binary.'''
result_code = 0
if not beginning_file_size:
beginning_file_size = len(pe.write())

# Remove Signature and modify size of Optional Header Security entry.
signature_address, signature_size = get_signature_info(pe)
data_to_delete = [(signature_address, signature_address + signature_size)]
signature_address, signature_size = get_signature_info(pe, cert_preservation)
if cert_preservation == True:
cert = [(signature_address, signature_address + signature_size)]
data_to_delete = []
else:
if signature_size > 0:
log_message("""A certificate is being removed from this file.\n-To preserve the certificate use the Cert Preservation option.""")
data_to_delete = [(signature_address, signature_address + signature_size)]

signature_abnormality = handle_signature_abnormality(signature_address,
signature_size,
beginning_file_size)
if signature_abnormality:
log_message('''
We detected data after the signature. This is abnormal. Removing signature and extra data...''')
data_to_delete.append((signature_address, beginning_file_size))
data_to_delete.append((signature_address + signature_size, beginning_file_size))
result_code = 1 # Junk after signture

# Handle Overlays: this includes packers and overlays which are completely junk
Expand Down Expand Up @@ -558,7 +568,7 @@ def process_pe(pe: pefile.PE, out_path: str, last_ditch_processing: bool,
log_message(result)
# All processing is done. Report results.
# There is always the signature in the list
if len(data_to_delete) == 1 or sum(slice_end-slice_start for slice_start, slice_end in data_to_delete) <= (beginning_file_size * 0.1):
if len(data_to_delete) == 0 or sum(slice_end-slice_start for slice_start, slice_end in data_to_delete) <= (beginning_file_size * 0.1):
log_message("""No automated method for reducing the size worked. Please consider sharing the
sample for additional analysis.
Email: Squiblydoo@pm.me
Expand All @@ -573,6 +583,8 @@ def process_pe(pe: pefile.PE, out_path: str, last_ditch_processing: bool,
pe_data += bytearray(pe.__data__[start:slice_start])
start = slice_end
pe_data += bytearray(pe.__data__[start:beginning_file_size])
if cert_preservation == True and signature_size > 0:
pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']].VirtualAddress = len(pe_data) - signature_size

pe.__data__ = pe_data
final_filesize, new_pe_name = write_patched_file(out_path,
Expand Down

0 comments on commit 8a19f0e

Please sign in to comment.