gimli-rs / object

A unified interface for reading and writing object file formats
https://docs.rs/object/
Apache License 2.0
673 stars 157 forks source link

pecopy on PE executables built by mingw not working #691

Open qknight opened 5 months ago

qknight commented 5 months ago

I've tried to use pecopy (commit aa2f5adc8badeac3dadaf26e9dacb66784d5d643) on Windows using:

./pecopy.exe pecopy.exe test.exe

This works nicely!

But using it on nix.exe, which i built using mingw from linux in a cross compiler setup, it hits all 3 assertions:

debug_assert_eq!(range.virtual_address, in_section.virtual_address.get(LE));
debug_assert_eq!(range.file_offset, in_section.pointer_to_raw_data.get(LE));
debug_assert_eq!(range.file_size, in_section.size_of_raw_data.get(LE));

I removed the asserts and added the !virtual_address and !pointer_to_raw_data print instead, this is what it looks like for the segments:

(base) PS C:\Users\joschie\Desktop\Projects\object\target\debug> .\pecopy.exe c:\nix-hack\nix.exe test.exe
section name: .text,   raw_data_size: 1e1e00
section name: .data,   raw_data_size: 1000
section name: .rdata,  raw_data_size: 99800
section name: .pdata,  raw_data_size: c000
section name: .xdata,  raw_data_size: 1f800        
section name: .bss,    raw_data_size: 0            
section name: .edata,  raw_data_size: 4fa00
section name: .idata,  raw_data_size: e800
section name: .CRT,    raw_data_size: 200
section name: .tls,    raw_data_size: 200
section name: /4,      raw_data_size: 22800   !virtual_address  !pointer_to_raw_data
section name: /19,     raw_data_size: 930e000 !virtual_address  !pointer_to_raw_data
section name: /31,     raw_data_size: 9b800   !virtual_address  !pointer_to_raw_data
section name: /45,     raw_data_size: 6c0c00  !virtual_address  !pointer_to_raw_data
section name: /57,     raw_data_size: ef200   !virtual_address  !pointer_to_raw_data
section name: /70,     raw_data_size: 131200  !virtual_address  !pointer_to_raw_data
section name: /81,     raw_data_size: 52a00   !virtual_address  !pointer_to_raw_data
section name: /97,     raw_data_size: 118d600 !virtual_address  !pointer_to_raw_data
section name: /113,    raw_data_size: 2f7e00  !virtual_address  !pointer_to_raw_data

This is the output of the relevant data points:

(base) PS C:\Users\joschie\Desktop\Projects\object\target\debug> .\pecopy.exe c:\nix-hack\nix.exe test.exe
section name: .text, chars: 1610612832, vsize: 1973416, raw_data_size: 1973760, virtual_address: 4096, pointer_to_raw_data: 1536
section name: .data, chars: 3221225536, vsize: 3840, raw_data_size: 4096, virtual_address: 1978368, pointer_to_raw_data: 1975296
section name: .rdata, chars: 1073741888, vsize: 628668, raw_data_size: 628736, virtual_address: 1982464, pointer_to_raw_data: 1979392
section name: .pdata, chars: 1073741888, vsize: 48852, raw_data_size: 49152, virtual_address: 2613248, pointer_to_raw_data: 2608128
section name: .xdata, chars: 1073741888, vsize: 128684, raw_data_size: 129024, virtual_address: 2662400, pointer_to_raw_data: 2657280
section name: .bss, chars: 3221225600, vsize: 7936, raw_data_size: 0, virtual_address: 2793472, pointer_to_raw_data: 0
section name: .edata, chars: 1073741888, vsize: 325739, raw_data_size: 326144, virtual_address: 2801664, pointer_to_raw_data: 2786304
section name: .idata, chars: 3221225536, vsize: 58904, raw_data_size: 59392, virtual_address: 3129344, pointer_to_raw_data: 3112448
section name: .CRT, chars: 3221225536, vsize: 96, raw_data_size: 512, virtual_address: 3190784, pointer_to_raw_data: 3171840
section name: .tls, chars: 3221225536, vsize: 16, raw_data_size: 512, virtual_address: 3194880, pointer_to_raw_data: 3172352
section name: /4, chars: 1107296320, vsize: 141040, raw_data_size: 141312, virtual_address: 3235840, pointer_to_raw_data: 3206144
 !virtual_address 3198976 != 3235840
 !pointer_to_raw_data 3172864 != 3206144
section name: /19, chars: 1107296320, vsize: 154197633, raw_data_size: 154198016, virtual_address: 3379200, pointer_to_raw_data: 3347456
 !virtual_address 3342336 != 3379200
 !pointer_to_raw_data 3314176 != 3347456
section name: /31, chars: 1107296320, vsize: 636447, raw_data_size: 636928, virtual_address: 157577216, pointer_to_raw_data: 157545472
 !virtual_address 157540352 != 157577216
 !pointer_to_raw_data 157512192 != 157545472
section name: /45, chars: 1107296320, vsize: 7080865, raw_data_size: 7080960, virtual_address: 158216192, pointer_to_raw_data: 158182400
 !virtual_address 158179328 != 158216192
 !pointer_to_raw_data 158149120 != 158182400
section name: /57, chars: 1107296320, vsize: 979280, raw_data_size: 979456, virtual_address: 165298176, pointer_to_raw_data: 165263360
 !virtual_address 165261312 != 165298176
 !pointer_to_raw_data 165230080 != 165263360
section name: /70, chars: 1107296320, vsize: 1249787, raw_data_size: 1249792, virtual_address: 166281216, pointer_to_raw_data: 166242816
 !virtual_address 166244352 != 166281216
 !pointer_to_raw_data 166209536 != 166242816
section name: /81, chars: 1107296320, vsize: 337933, raw_data_size: 338432, virtual_address: 167534592, pointer_to_raw_data: 167492608
 !virtual_address 167497728 != 167534592
 !pointer_to_raw_data 167459328 != 167492608
section name: /97, chars: 1107296320, vsize: 18404433, raw_data_size: 18404864, virtual_address: 167874560, pointer_to_raw_data: 167831040
 !virtual_address 167837696 != 167874560
 !pointer_to_raw_data 167797760 != 167831040
section name: /113, chars: 1107296320, vsize: 3112009, raw_data_size: 3112448, virtual_address: 186281984, pointer_to_raw_data: 186235904
 !virtual_address 186245120 != 186281984
 !pointer_to_raw_data 186202624 != 186235904
Failed to write file 'test.exe': The requested operation cannot be performed on a file with a user-mapped section open. (os error 1224)

And .bss section looks like this from the PE header:

grafik

Ideas how this could be fixed?

philipc commented 5 months ago

And .bss section looks like this from the PE header:

What's the reason you showed this? It seems fine to me. sizeOfRawData is always 0 for .bss.

section name: /4, raw_data_size: 22800 !virtual_address !pointer_to_raw_data

The first problem here is that this is using long section names, which requires writing the symbol table and string table, but pe::Writer doesn't support that.

For the assertions, I'm not 100% sure because I don't have your binary to look at, but for the binary I tested on here, the problem is that the .reloc section occurs in between this section and the previous one, but pecopy skips over .reloc, which messes up the address and file offsets.

You could maybe fix that with a patch like this:

--- a/crates/examples/src/bin/pecopy.rs
+++ b/crates/examples/src/bin/pecopy.rs
@@ -114,6 +114,11 @@ fn copy_file<Pe: ImageNtHeaders>(in_data: &[u8]) -> Result<Vec<u8>, Box<dyn Erro
     let mut in_sections_data = Vec::new();
     for index in &in_sections_index {
         let in_section = in_sections.section(*index)?;
+        let offset = in_section.pointer_to_raw_data.get(LE);
+        if offset != 0 {
+            writer.reserve_until(offset);
+        }
+        writer.reserve_virtual_until(in_section.virtual_address.get(LE));
         let range = writer.reserve_section(
             in_section.name,
             in_section.characteristics.get(LE),

but that still doesn't produce a working executable, and I haven't debugged further to find out why.

So, the pecopy example is not a useful tool. It exists only as an example of how to use the API for write::pe::Writer, and to show that it is capable of writing a valid executable. It copies another file simply because that is the easiest way to provide data for it to write, but it shouldn't be expected that is it capable of copying any file.

If we were to provide support for being able to copy any file, what needs to be done is to add build::pe::Builder, similar to the existing ELF builder. I made a start on this a while ago, but didn't get far yet. I don't know when I'll get back to it.

In the meantime, you may want to look at using goblin. I know that someone else has done work there in this area, see https://github.com/m4b/goblin/pull/389 and https://github.com/RaitoBezarius/ifrit.

qknight commented 5 months ago

I've added .xdata and .bss as I originally thought to be wrong and the screenshot might help you.

The main.exe in question can be found at https://nixcloud.io/mini-main.zip and it also contains the source code. It creates some files in your windows temp for testing std::filesystem on windows with symlinks. It requires c:\nix to exist. Don't run it but if you do it probably won't damage anything. This is how I compile it: https://lastlog.de/blog/libnix_mingw_status.html and I provide this since you might be curious what I'm doing.

Can you please explain why pecopy is not useful? It seems to do exactly what I want from it: parse a PE header, relocate symbols and write it back. The only thing I want to add is parsing & rewriting the .idata section because this contains the dll names I want to change.

I'll need to have a closer look into the goblin link you provided, thanks!

running main.exe, the original

[nixos@nixos:~/nix/t]$ ./main.exe
0
0
1
0
"c:" -> dir is not an absolute path
not an absolute path
"c:"
"c:\\" -> "c:\\"
"c:\\nix\\..\\nix" -> "c:\\nix"
 da test -> c:\nix-hack\foo
c:\nix
c:\nix\asdf\
\\wsl.localhost\nixos\home\nixos\nix\t
creating tmp path: C:\Users\joschie\AppData\Local\Temp\
Error creating file symlink

A required privilege is not held by the client
Error creating directory symlink

A required privilege is not held by the client
Error creating directory symlink

A required privilege is not held by the client
Error creating directory symlink

A required privilege is not held by the client
c:\nix\lnks -> "c:\\nix\\lnks"

For me, using your patch and reenabling assertions, it produces a working executable!

running the pecopy main.exe copy

.\main-pecopy.exe
0
0
1
0
"c:" -> not an absolute path
"c:"
"c:\\" -> "c:\\"
"c:\\nix\\..\\nix" -> "c:\\nix"
 da test -> c:\nix-hack\foo
c:\nix
c:\nix\asdf\
C:\nix-hack
creating tmp path: C:\Users\joschie\AppData\Local\Temp\
Error creating file symlink

A required privilege is not held by the client
Error creating directory symlink

A required privilege is not held by the client
Error creating directory symlink

A required privilege is not held by the client
Error creating directory symlink

A required privilege is not held by the client
c:\nix\lnks -> "c:\\nix\\lnks"
philipc commented 5 months ago

Can you please explain why pecopy is not useful? It seems to do exactly what I want from it: parse a PE header, relocate symbols and write it back. The only thing I want to add is parsing & rewriting the .idata section because this contains the dll names I want to change.

It's not useful as is because it doesn't do any modifications to the data, and it doesn't claim to support everything possible in a PE file. However, if you can get it to work for what you need then that's great.

The mini-main.zip doesn't appear to be the same file that you were using in your initial report, because it doesn't contain any sections with long names, and pecopy works on it without the need for any patch.

qknight commented 5 months ago

I think I uploaded the original binary to https://nixcloud.io/nix.exe but know it is rather big.

Thanks for your explanation. PE modifications are indeed very hard as there are so many compiler/linker specific details which are easy to miss when doing rewrites.

I looked at the ifrit link you pointed me to, looks promising but bails out for main.exe and nix.exe with:

.\pe_add_section.exe c:\nix-hack\nix.exe out.exe
...
thread 'main' panicked at examples\pe_add_section.rs:33:44:
called `Result::unwrap()` on an `Err` value: Malformed("DOS header is malformed (signature 0x0)")

I'll have to re-consider possible solutions to my dll name patching with this knowledge.

Seems it does not like mingw/ld binaries.