Huge refactor, +compile script

This commit is contained in:
Print3M
2025-08-17 21:12:02 +02:00
parent e6e9604006
commit 9ac684aab2
12 changed files with 256 additions and 144 deletions
+8 -1
View File
@@ -4,6 +4,13 @@
"*.template": "c",
"cstdarg": "cpp",
"typeinfo": "c",
"functional": "cpp"
"functional": "cpp",
"array": "c",
"deque": "c",
"string": "c",
"unordered_map": "c",
"vector": "c",
"string_view": "c",
"initializer_list": "c"
}
}
+33 -12
View File
@@ -2,23 +2,41 @@
<div style="text-align:center;font-style: italic;">Weaponize DLL hijacking easily. Backdoor any function in any DLL without disrupting normal process operation.</div>
![DllShimmer flowchart](_img/img-1.jpg)
## How it works
DllShimmer parses the original DLL and extracts information about exported functions (name, ordinal number, and forwarder info). Based on this information, DllShimmer creates a boilerplate C++ file (`.cpp`). **The generated file allows you to add your own code to each function exported from the original DLL without disrupting the normal operation of the program.** No reverse engineering or instrumentation is required, because DllShimmer does not rely on function signatures (see more in “Limitations”)
DllShimmer parses the original DLL and extracts information about exported functions (name, ordinal number, and forwarder info). Based on this information, DllShimmer creates a boilerplate C++ file (`.cpp`). **The generated file allows you to add your own code to each function exported from the original DLL without disrupting the normal operation of the program.** No reverse engineering or instrumentation is required, because DllShimmer does not rely on function signatures (see more in “Limitations”).
The second file generated is a `.def` file, which ensures that all DLLs exported from the proxy after compilation will have the same names and ordinal numbers as in the original DLL.
At the end, DllShimmer generates a ready-made command to compile the proxy DLL easily.
**After compilation, the EAT in the proxy DLL is an exact copy of the EAT in the original DLL. All names and ordinal numbers of exported functions match, and forwarded functions are forwarded as well.** DllShimmer does not explicitly forward all functions (like most tools), creating a completely new and suspicious EAT structure.
## Installation
**Requirements**:
Compile Go source code or [download the compiled binary](https://github.com/Print3M/DllShimmer/releases).
- MinGW:
- `x86_64-w64-mingw32-g++`
- `x86_64-w64-mingw32-dlltool`
**Dependencies**:
## Options
- `x86_64-w64-mingw32-g++`
- `x86_64-w64-mingw32-dlltool`
## Usage
Example:
```bash
# Backdoor version.dll (proxy to absolute path)
./DllShimmer -i version.dll -o project/ -x "C:/Windows/System32/version.dll" -m
# Backdoor random chat.dll (proxy to relative path)
./DllShimmer -i chat.dll -o project/ -x "lib/chat2.dll" -m
# Backdoor random app.dll (static linking to the original DLL)
./DllShimmer -i app.dll -o project/ -x "app2.dll" -m --static
```
Parameters:
**`-i / --input <file>`** [required]
@@ -56,13 +74,17 @@ By default, DllShimmer always uses dynamic linking with the `LoadLibraryA()` and
- Only x86-64 / AMD64 architecture is supported.
- Most likely, the generic proxy code will not work for functions with floating-point parameters, as they use different registers than integer ones utilized by DllShimmer. If you know the function signature, you can manually adjust it in the generated file.
- Functions with more than 12 arguments will not work because this number has been hardcoded into DllShimmer templates.
- There are some huge obfuscated DLLs with weird name mangling, calling conventions and tricks (e.g. Qt framework DLL). I don't recommend to use them as a proxy DLL. DllShimmer will generate some garbage in this case.
- There are some huge obfuscated DLLs with weird name mangling, calling conventions and tricks (e.g. compiled Qt framework DLL). I don't recommend to use them as a proxy DLL. DllShimmer most probably will generate some garbage in this case.
## Troubleshooting
### 1. Strange loader error (126) while loading original DLL
### _In the generated `.cpp` file, I don't see all the exported functions from the original DLL._
Sometimes, your proxy DLL displays an error when loading the original DLL, and the error code is 126, even though you theoretically specified the correct relative path in the `--proxy` parameter. Why isn't it working?!?
Functions defined in the original DLL as “forwarded” are not included in the `.cpp` file. However, they are visible in the `.def` file. They will also be exported after compilation, exactly as in the original DLL.
### _Strange loader error (126) while loading original DLL_
Sometimes, your proxy DLL displays an error when loading the original DLL, and the error code is 126, even though you theoretically specified the correct relative path in the `-x` parameter. Why isn't it working?!?
DLLs are searched for in the `Current Directory`. In 98% of cases, this is simply the location of the main EXE file, but there are programs (mostly old legacy ones) that arbitrarily change the `Current Directory` using, for example, `SetCurrentDirectoryW()`. The main program is aware of this change, so it loads your proxy DLL correctly, but you are unaware of this and try to load the original DLL relatively, while the program searches for it in the changed `Current Directory`.
@@ -70,7 +92,7 @@ This rule applies to both static and dynamic loading of the original DLL. Unfort
In the case of dynamic linking, we have two options:
1. Adjust the path in `--proxy` to the new `Current Directory` situation.
1. Adjust the path in the `-x` parameter to the new `Current Directory` situation.
2. Change the `Current Directory` dynamically to search for DLLs where we want.
In case of static linking, we really only have one option:
@@ -79,5 +101,4 @@ In case of static linking, we really only have one option:
## TODO
- Add some interesting graphics: PE -> Proxy DLL -> Original DLL
- Auto-generate compilation script to output folder
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+6 -5
View File
@@ -3,7 +3,6 @@ package cli
import (
"flag"
"fmt"
"log"
"os"
"strings"
)
@@ -16,7 +15,7 @@ type CliFlags struct {
Static bool
}
func IsValidWindowsDllName(filename string) bool {
func IsValidDllName(filename string) bool {
invalidChars := []rune{'<', '>', ':', '"', '/', '\\', '|', '?', '*'}
// Check for invalid characters
@@ -26,7 +25,7 @@ func IsValidWindowsDllName(filename string) bool {
}
}
if !strings.HasSuffix(filename, ".dll") {
if !strings.HasSuffix(strings.ToLower(filename), ".dll") {
return false
}
@@ -77,8 +76,10 @@ func ParseCli() *CliFlags {
os.Exit(1)
}
if flags.Static && !IsValidWindowsDllName(flags.OriginalPath) {
log.Fatalln("[!] In case of static linking enabled the proxy file (-p, --proxy) must be valid Windows DLL file name with no path information. E.g. kernel32.dll, user32.dll")
if flags.Static && !IsValidDllName(flags.OriginalPath) {
fmt.Fprintf(os.Stderr, "[!] Invalid parameter value:\n")
fmt.Fprintf(os.Stderr, "In case of static linking enabled '-x' parameter must be valid Windows DLL file name with no path information. E.g. kernel32.dll, user32.dll.")
os.Exit(1)
}
return &flags
+4 -10
View File
@@ -2,8 +2,6 @@ package def
import (
"fmt"
"log"
"os"
)
type exportedFunction struct {
@@ -41,7 +39,7 @@ func (d *DefFile) AddForwardedFunction(originalName string, forwarder string, or
})
}
func (d *DefFile) SaveFile(path string, withOrdinals bool) {
func (d *DefFile) GetContent() string {
var content string
content += "LIBRARY \"" + d.DllName + "\"\n"
@@ -62,15 +60,11 @@ func (d *DefFile) SaveFile(path string, withOrdinals bool) {
content += "\t" + function.OriginalName + "=" + function.Rename
}
if withOrdinals {
content += " " + "@" + fmt.Sprintf("%d", function.Ordinal)
}
// Add ordinals
content += " " + "@" + fmt.Sprintf("%d", function.Ordinal)
content += "\n"
}
err := os.WriteFile(path, []byte(content), 0644)
if err != nil {
log.Fatalf("[!] Error while creating .def file: %v", err)
}
return content
}
+1 -31
View File
@@ -1,10 +1,7 @@
package dll
import (
"dllshimmer/def"
"log"
"os"
"os/exec"
"path/filepath"
peparser "github.com/saferwall/pe"
@@ -18,6 +15,7 @@ type ExportedFunction struct {
type Dll struct {
Name string
OriginalPath string
ExportedFunctions []ExportedFunction
}
@@ -46,31 +44,3 @@ func ParseDll(path string) *Dll {
return &dll
}
func (d *Dll) CreateLibFile(path string, proxyName string) {
var def def.DefFile
def.DllName = proxyName
for _, function := range d.ExportedFunctions {
if function.Forwarder == "" {
def.AddExportedFunction(function.Name, function.Ordinal)
} else {
def.AddForwardedFunction(function.Name, function.Forwarder, function.Ordinal)
}
}
f, err := os.CreateTemp("", "dllshimmer-*.def")
if err != nil {
panic(err)
}
defer os.Remove(f.Name())
def.SaveFile(f.Name(), true)
// Convert DLL to .lib file
cmd := exec.Command("x86_64-w64-mingw32-dlltool", "-d", f.Name(), "-l", path, "-m", "i386:x86-64")
_, err = cmd.CombinedOutput()
if err != nil {
panic(err)
}
}
+13 -54
View File
@@ -2,71 +2,30 @@ package main
import (
"dllshimmer/cli"
"dllshimmer/def"
"dllshimmer/dll"
"dllshimmer/tmpl"
"fmt"
"dllshimmer/output"
"path/filepath"
)
func main() {
flags := cli.ParseCli()
outputDir := filepath.Clean(flags.Output)
// var params tmpl.CodeFileParams
// params.Functions = dll.ExportedFunctions
// params.OriginalPath = flags.OriginalPath
// params.DllName = filepath.Base(flags.Input)
// params.Mutex = flags.Mutex
dll := dll.ParseDll(flags.Input)
var params tmpl.TemplateParams
params.Functions = dll.ExportedFunctions
params.OriginalPath = flags.OriginalPath
params.DllName = filepath.Base(flags.Input)
params.Mutex = flags.Mutex
if flags.Static {
tmpl.CreateCodeFile(outputDir, params, "templates/static-shim.c.template")
// Create .lib based on original DLL
dll.CreateLibFile(filepath.Join(outputDir, "original.lib"), params.OriginalPath)
} else {
tmpl.CreateCodeFile(outputDir, params, "templates/dynamic-shim.c.template")
out := output.Output{
Dll: dll.ParseDll(flags.Input),
OutputDir: filepath.Clean(flags.Output),
}
func() {
var def def.DefFile
def.DllName = params.DllName
out.CreateCodeFile(flags.Mutex, flags.Static)
out.CreateDefFile()
out.CreateCompileScript(flags.Static)
for _, function := range dll.ExportedFunctions {
if function.Forwarder == "" {
def.AddRenamedFunction(function.Name, function.Name+"Fwd", function.Ordinal)
} else {
def.AddForwardedFunction(function.Name, function.Forwarder, function.Ordinal)
}
}
def.SaveFile(filepath.Join(outputDir, params.DllName+".def"), true)
}()
codeFile := filepath.Join(outputDir, params.DllName+".cpp")
defFile := filepath.Join(outputDir, params.DllName+".def")
dllFile := filepath.Join(outputDir, params.DllName)
var cmd string
if flags.Static {
cmd = fmt.Sprintf(
"x86_64-w64-mingw32-g++ -shared %s %s -o %s -L %s -l original -static-libstdc++ -static-libgcc -D DEBUG=1",
codeFile,
defFile,
dllFile,
outputDir,
)
} else {
cmd = fmt.Sprintf(
"x86_64-w64-mingw32-g++ -shared %s %s -o %s -static-libstdc++ -static-libgcc -D DEBUG=1",
codeFile,
defFile,
dllFile,
)
out.CreateLibFile()
}
println(cmd)
}
+168
View File
@@ -0,0 +1,168 @@
package output
import (
"dllshimmer/def"
"dllshimmer/dll"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"text/template"
)
type Output struct {
Dll *dll.Dll
OutputDir string
}
func (o *Output) GetDefFileName() string {
return o.Dll.Name + ".def"
}
func (o *Output) GetCodeFileName() string {
return o.Dll.Name + ".cpp"
}
func (o *Output) GetCompileScriptName() string {
return "compile.sh"
}
func (o *Output) GetOutputDllName() string {
return o.Dll.Name
}
func (o *Output) GetLibFileName() string {
return "original.lib"
}
type CodeFileParams struct {
Functions []dll.ExportedFunction
OriginalPath string
Mutex bool
DllName string
}
func (o *Output) CreateCodeFile(mutex bool, isStaticLinked bool) {
templateFile := "templates/dynamic-shim.c.template"
if isStaticLinked {
templateFile = "templates/static-shim.c.template"
}
tmpl := template.Must(template.ParseFiles(templateFile))
outputPath := filepath.Join(o.OutputDir, o.GetCodeFileName())
f, err := os.Create(outputPath)
if err != nil {
log.Fatalf("[!] Error while creating '%s' file: %v", outputPath, err)
}
defer f.Close()
params := CodeFileParams{
Functions: o.Dll.ExportedFunctions,
OriginalPath: o.Dll.OriginalPath,
Mutex: mutex,
DllName: o.Dll.Name,
}
err = tmpl.Execute(f, params)
if err != nil {
log.Fatalf("[!] Error of template engine: %v", err)
}
fmt.Printf("[+] '%s' file created\n", outputPath)
}
func (o *Output) CreateDefFile() {
var def def.DefFile
def.DllName = o.Dll.Name
for _, function := range o.Dll.ExportedFunctions {
if function.Forwarder == "" {
def.AddRenamedFunction(function.Name, function.Name+"Fwd", function.Ordinal)
} else {
def.AddForwardedFunction(function.Name, function.Forwarder, function.Ordinal)
}
}
content := def.GetContent()
outputPath := filepath.Join(o.OutputDir, o.GetDefFileName())
err := os.WriteFile(outputPath, []byte(content), 0644)
if err != nil {
log.Fatalf("[!] Error while creating '%s' file: %v", outputPath, err)
}
fmt.Printf("[+] '%s' file created\n", outputPath)
}
func (o *Output) CreateLibFile() {
var def def.DefFile
// In case of static linking OriginalPath is DLL name itself
def.DllName = o.Dll.OriginalPath
for _, function := range o.Dll.ExportedFunctions {
if function.Forwarder == "" {
def.AddExportedFunction(function.Name, function.Ordinal)
} else {
def.AddForwardedFunction(function.Name, function.Forwarder, function.Ordinal)
}
}
temp, err := os.CreateTemp("", "dllshimmer-*.def")
if err != nil {
panic(err)
}
defer os.Remove(temp.Name())
content := def.GetContent()
err = os.WriteFile(temp.Name(), []byte(content), 0644)
if err != nil {
log.Fatalf("[!] Error while creating '%s' file: %v", temp.Name(), err)
}
// Convert DLL to .lib file
outputPath := filepath.Join(o.OutputDir, o.GetLibFileName())
cmd := exec.Command("x86_64-w64-mingw32-dlltool", "-d", temp.Name(), "-l", outputPath, "-m", "i386:x86-64")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(string(out))
panic(err)
}
fmt.Printf("[+] '%s' file created\n", outputPath)
}
type CompileScriptParams struct {
Code string
Def string
Output string
IsStaticLinked bool
}
func (o *Output) CreateCompileScript(isStaticLinked bool) {
tmpl := template.Must(template.ParseFiles("templates/compile.sh.template"))
outputPath := filepath.Join(o.OutputDir, o.GetCompileScriptName())
f, err := os.Create(outputPath)
if err != nil {
log.Fatalf("[!] Error while creating '%s' file: %v", outputPath, err)
}
defer f.Close()
params := CompileScriptParams{
Code: o.GetCodeFileName(),
Def: o.GetDefFileName(),
Output: o.GetOutputDllName(),
IsStaticLinked: isStaticLinked,
}
err = tmpl.Execute(f, params)
if err != nil {
log.Fatalf("[!] Error of template engine: %v", err)
}
fmt.Printf("[+] '%s' file created\n", outputPath)
}
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
#
# Generated by DllShimmer (https://github.com/Print3M/DllShimmer)
#
# Author: Print3M (https://print3m.github.io/)
set -ueo pipefail
{{ if .IsStaticLinked }}
x86_64-w64-mingw32-g++ -shared '{{.Code}}' '{{.Def}}' -o '{{.Output}}' -L . -l original -static-libstdc++ -static-libgcc -D DEBUG=1
{{ else }}
x86_64-w64-mingw32-g++ -shared '{{.Code}}' '{{.Def}}' -o '{{.Output}}' -static-libstdc++ -static-libgcc -D DEBUG=1
{{ end }}
echo "[+] Proxy DLL compiled successfully."
+4
View File
@@ -1,3 +1,7 @@
// Generated by DllShimmer (https://github.com/Print3M/DllShimmer)
//
// Author: Print3M (https://print3m.github.io/)
{{- $r := . }}
#include <windows.h>
#include <stdio.h>
+4
View File
@@ -1,3 +1,7 @@
// Generated by DllShimmer (https://github.com/Print3M/DllShimmer)
//
// Author: Print3M (https://print3m.github.io/)
#include <stdio.h>
#include <iostream>
// Put your imports here...
-31
View File
@@ -1,31 +0,0 @@
package tmpl
import (
"dllshimmer/dll"
"log"
"os"
"path/filepath"
"text/template"
)
type TemplateParams struct {
Functions []dll.ExportedFunction
OriginalPath string
DllName string
Mutex bool
}
func CreateCodeFile(outputDir string, params TemplateParams, path string) {
tmpl := template.Must(template.ParseFiles(path))
f, err := os.Create(filepath.Join(outputDir, params.DllName+".cpp"))
if err != nil {
panic(err)
}
defer f.Close()
err = tmpl.Execute(f, params)
if err != nil {
log.Fatalf("[!] Error of template engine: %v", err)
}
}