Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Linux examples - LD_PRELOAD and commandline cloaking #48

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ vpath %.nim src/
#NIMFLAGS = -d=danger -d=mingw -d=strip --passc=-flto --passl=-flto --opt=size
NIMFLAGS = -d=debug -d=mingw --embedsrc=on --hints=on

SRCS_BINS = $(notdir $(wildcard src/*_bin.nim))
SRCS_LIBS = $(notdir $(wildcard src/*_lib.nim))
SRCS_BINS = $(notdir $(filter-out $(wildcard src/linux_*), $(wildcard src/*_bin.nim)))
SRCS_LIBS = $(notdir $(filter-out $(wildcard src/linux_*), $(wildcard src/*_lib.nim)))
BINS = $(patsubst %.nim,%.exe,$(SRCS_BINS))
DLLS = $(patsubst %.nim,%.dll,$(SRCS_LIBS))

Expand All @@ -22,9 +22,7 @@ clean:
rm -rf bin/*.exe bin/*.dll

%.exe : %.nim
nim c $(NIMFLAGS) --app=console --cpu=amd64 --out=bin/$*_64.exe $<
#nim c $(NIMFLAGS) --app=console --cpu=i386 --out=bin/$*_32.exe $<
echo $*

%.dll: %.nim
nim c $(NIMFLAGS) --app=lib --nomain --cpu=amd64 --out=bin/$*_64.dll $<
#nim c $(NIMFLAGS) --app=lib --nomain --cpu=i386 --out=bin/$*_32.dll $<
echo $*
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ My experiments in weaponizing [Nim](https://nim-lang.org/) for implant developme
| [list_remote_shares.nim](../master/src/list_remote_shares.nim) | Use NetShareEnum to list the share accessible by the current user |
| [chrome_dump_bin.nim](../master/src/chrome_dump_bin.nim) | Read and decrypt cookies from Chrome's sqlite database|
| [suspended_thread_injection.nim](../master/src/suspended_thread_injection.nim) | Shellcode execution via suspended thread injection |
| [linux_commandline_cloaking_bin.nim](../master/src/linux_commandline_cloaking_bin.nim) | Various Linux techniques to hide real process name and commandline |
| [linux_ld_preload_libc_lib.nim](../master/src/linux_ld_preload_libc_lib.nim) | Using `LD_PRELOAD` on linux to hook libc |
## Examples that are a WIP

| File | Description |
Expand Down Expand Up @@ -335,3 +337,4 @@ var buf: array[5, byte] = [byte 0xfc,0x48,0x81,0xe4,0xf0,0xff]
- [@frknayar](https://twitter.com/frknayar)
- [@OffenseTeacher](https://twitter.com/OffenseTeacher)
- [@fkadibs](https://twitter.com/fkadibs)
- [@pathtofile](https://twitter.com/pathtofile)
90 changes: 90 additions & 0 deletions src/linux_commandline_cloaking_bin.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#[
Author: @pathtofile
License: BSD 3-Clause

# Overview:
This example demonstrates how to mess with our own arguments at runtime,
to 'cloak' what the actual arguments and parent process is. It does:
- Uses prctl syscall to change the value in /proc/<pid>/comm
- Overwrites the address of argv[0] and argv[1] to fake the real values to tools such as ps.
- Double-forks to make it's parent appear to be PID 1

To edit our own argv, we need to overwrite Nim's default main function, which means
taking control of setting up Nim's environment ourselves with a call to NimMain()

# Building:
nim compile --nomain --out:./linux_commandline_cloaking ./linux_commandline_cloaking.nim

# Using:
./linux_commandline_cloaking

# Reference:
For more information see: https://github.com/pathtofile/commandline_cloaking/tree/main/dodgy
]#

import os
import posix
import strformat
import strutils

proc NimMain() {.cdecl, importc.}

# Use syscall function call from stdlib
proc syscall(number: clong): clong {.importc, varargs, header: "sys/syscall.h".}
var NR_PRCTL {.importc: "__NR_prctl", header: "unistd.h".}: int
var PR_SET_NAME {.importc: "PR_SET_NAME", header: "sys/prctl.h".}: int

proc memset(s: pointer, c: cint, n: csize_t): pointer {.importc, header: "string.h"}

# Compiled with --nomain so we overwrite Nim's default main func
proc main(argc: int, argv: cstringArray, envp: cstringArray): int {.cdecl, exportc.} =
# Need to call NimMain ourselves first to avoid explosions
NimMain()

# Print data
echo("-------- REAL --------")
echo(fmt" PID {getpid()}")
echo(fmt" PPID {getppid()}")
echo(fmt" argc {argc}")
for i in 0..(argc-1):
echo(fmt" argv[{i}] {argv[i]}")

# Double-fork to make parent pid look like PID 1
var childPID = fork()
if childPID != 0:
# First parent, exit
return 0
childPID = fork()
if childPID != 0:
# Second parent, exit
return 0

# Use prctl syscall to change /proc/pid/comm
var err = syscall(NR_PRCTL, PR_SET_NAME, cstring("faked"))
if err < 0:
echo(fmt"Error calling PRCTL {err}")
return -1

# Overwrite args, have to use 'memset'
discard memset(argv[0], ord('F'), csize_t(len(argv[0])))
if argc > 1:
discard memset(argv[1], ord('B'), csize_t(len(argv[1])))

# Sleep for a second for parent to be reaped
# and PID 1 to adopt us
sleep(1 * 1000)

# Print data
echo("---- FORK & FAKE -----")
echo(fmt" PID {getpid()}")
echo(fmt" PPID {getppid()}")
echo(fmt" argc {argc}")
for i in 0..(argc-1):
echo(fmt" argv[{i}] {argv[i]}")

echo("----------------------")
echo(" Sleeping for 60 seconds so you can lookup the PID")
setControlCHook(proc() {.noconv.} = discard)
sleep(60 * 1000)

return 0
76 changes: 76 additions & 0 deletions src/linux_ld_preload_libc_lib.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#[
Author: @pathtofile
License: BSD 3-Clause

# Overview:
This example demonstrates how to create an LD_PRELOAD library
that hooks "__libc_start_main", the first libc function that is called
prior to the program's actual 'main' function. This enables you to
alter the commandline arguments after the program has be started using
the kernal with execve, but before the program actually reads and uses them.

This leads to a missmatch between what programs like 'ps' and commandline
loggers see as the programs commandline args, and the 'real' arguments.

This only works for program dynamically loading and using Libc, e.g. Golang
programs wil not be hooked.

# Building:
nim compile --app:lib --out:./linix_ld_preload_libc_lib.so ./linix_ld_preload_libc_lib.nim

# Using:
LD_PRELOAD=./linix_ld_preload_libc_lib.so /bin/echo AAAA

# Reference:
For more information see: https://github.com/pathtofile/commandline_cloaking/tree/main/preload
]#

import dynlib

proc memset(s: pointer, c: cint, n: csize_t): pointer {.importc, header: "string.h"}
type
LibCMain = proc (
main: pointer,
argc: cint,
argv: cstringArray,
init: pointer,
fini: pointer,
rtld_fini: pointer): int {.cdecl}


# Set this to true to overwrite argv in-place
# instead of giving main our own argv array.
# When overwrite_argv = false the output of 'ps'
# will match the original argv from the commandline,
# when it is true the output of 'ps' will be updated
# to reflect the overwritten values
const overwrite_argv = false

# This hooks __libc_start_main
proc hookedLibcMain(
main: pointer,
argc: cint,
argv: cstringArray,
init: pointer,
fini: pointer,
rtld_fini: pointer
): int {.cdecl, exportc:"__libc_start_main", dynlib} =
# Find original function
let lib = loadLib("libc.so.6")
assert lib != nil, "Error loading library"
let origFunc = cast[LibCMain](lib.symAddr("__libc_start_main"))
assert origFunc != nil, "Error loading function from library"

if overwrite_argv:
# Overwrite args, have to use 'memset'
discard memset(argv[0], ord('F'), csize_t(len(argv[0])))
if argc > 1:
# If one or more arguments, just overwrite the first one
discard memset(argv[1], ord('B'), csize_t(len(argv[1])))

return origFunc(main, argc, argv, init, fini, rtld_fini)
else:
# Create a new argv array to use
let newArgc = cint(2)
var newArgv = alloccstringArray(["from_preload", "BBBB"])
return origFunc(main, newArgc, newArgv, init, fini, rtld_fini)