mirror of
https://github.com/Print3M/DllShimmer
synced 2026-06-06 16:34:32 +00:00
Huge refactor, +compile script
This commit is contained in:
Vendored
+8
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
+6
-5
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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."
|
||||
@@ -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>
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user