optool
optool copied to clipboard
Uninstalling a load command doesn't update dynamic loader binding ordinals
When removing a load command entry, the dylib ordinal should be reduced by one for all bindings with a higher ordinal than the removed LC.
Failing to do this, the application will crash on startup as the opcodes in the dynamic loader bindings point to the incorrect dylib.
Thanks for reporting this. I designed the uninstall command really to remove a command that was originally added with the tool so I didn't expect it to have to modify any dynamic loader info. However, I've looked at this a bit and it seems the opcodes are stored in the LC_DYLD_INFO
command nlist in the n_desc
field. The first 8 bits being the opcode command such as BIND_OPCODE_SET_DYLIB_ORDINAL_IMM
with the following 8 being the index if it is immediate. I'm going to have to do some more research on the format of the ULEB128 format and where that data goes but will come back when I have more.
From the ABI:
If this file is a two-level namespace image (that is, if the MH_TWOLEVEL flag of the mach_header
structure is set), the high 8 bits of n_desc specify the number of the library in which this undefined
symbol is defined. Use the macro GET_LIBRARY_ORDINAL to obtain this value and the macro
SET_LIBRARY_ORDINAL to set it. Zero specifies the current image. 1 through 253 specify the library
number according to the order of LC_LOAD_DYLIB commands in the file. The value 254 is used for
undefined symbols that are to be dynamically looked up (supported only in OS X v10.3 and later). For
plug–ins that load symbols from the executable program they are linked against, 255 specifies the
executable image. For flat namespace images, the high 8 bits must be 0.
Hi Alex,
I ran into the same issue, but managed to get it working (better) by editing the opcodes manually.
From what I found;
- Opcodes can use BIND_OPCODE_SET_DYLIB_ORDINAL_IMM (0x1N with N being the 1-16th LC)
- Opcodes using higher ordinal LCs use BIND_OPCODE_SET_DYLIB_ULEB (0x20) to specify an offset and follow on with a uleb128 to specify the LC ordinal
After doing some hand editing of the hex values, the binary made it further before crashing. However in the lazy binding info, all opcodes specify their dylib and there were hundreds needing to be edited.
On Fri, Aug 29, 2014 at 10:43 AM, Alex Zielenski [email protected] wrote:
Thanks for reporting this. I've looked at this a bit and it seems the opcodes are stored in the nlist in the n_desc field. The first 8 bits being the opcode command such as BIND_OPCODE_SET_DYLIB_ORDINAL_IMM with the following 8 being the index if it is immediate. I'm going to have to do some more research on the format of the ULEB128 format and where that data goes but will come back when I have more.
From the ABI:
If this file is a two-level namespace image (that is, if the MH_TWOLEVEL flag of the mach_header structure is set), the high 8 bits of n_desc specify the number of the library in which this undefined symbol is defined. Use the macro GET_LIBRARY_ORDINAL to obtain this value and the macro SET_LIBRARY_ORDINAL to set it. Zero specifies the current image. 1 through 253 specify the library number according to the order of LC_LOAD_DYLIB commands in the file. The value 254 is used for undefined symbols that are to be dynamically looked up (supported only in OS X v10.3 and later). For plug–ins that load symbols from the executable program they are linked against, 255 specifies the executable image. For flat namespace images, the high 8 bits must be 0.
— Reply to this email directly or view it on GitHub https://github.com/alexzielenski/optool/issues/2#issuecomment-53831483.
Alright, thanks for the info. With automation it should be trivial to be able to edit all of the higher opcodes. My only concern is about removing the opcodes relating to the uninstalled binary. Is this necessary?
I found that I didn't need to remove opcodes pointing to the removed dylib, and the binary was ok.
We could try removing them and see if it breaks. Some of the DATA segments reference the opcode bindings, but I assume if the codepath isn't run we won't have a problem.
The problem I'm trying to solve is removing unused dylibs that have been included in a binary.
On Fri, Aug 29, 2014 at 11:15 AM, Alex Zielenski [email protected] wrote:
Alright, thanks for the info. With automation it should be trivial to be able to edit all of the higher opcodes. My only concern is about removing the opcodes relating to the uninstalled binary. Is this necessary?
— Reply to this email directly or view it on GitHub https://github.com/alexzielenski/optool/issues/2#issuecomment-53832956.
I think we have to remove them incase another dylib is added, then the opcode would reference that dylib and cause this issue all over again.
Here is a snippet of code I wrote to parse the opcodes and log some offsets (using Chess.app)
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY: {
NSLog(@"%lu", (unsigned long)binary.currentOffset);
struct dyld_info_command info;
[binary getBytes:&info range:NSMakeRange(binary.currentOffset, size)];
NSLog(@"%u", info.bind_off);
NSLog(@"%u", info.weak_bind_off);
NSLog(@"%u", info.lazy_bind_off);
uint8_t *p = malloc(info.bind_size);
[binary getBytes:p range:NSMakeRange(info.bind_off, info.bind_size)];
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
NSLog(@"%d, %d", opcode, immediate);
NSLog(@"%d", BIND_OPCODE_SET_DYLIB_ORDINAL_IMM);
NSLog(@"%d", BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
NSLog(@"%d", BIND_OPCODE_SET_DYLIB_SPECIAL_IMM);
binary.currentOffset += size;
break;
}
And the output:
2014-08-30 14:49:21.631 optool[3959:303] 2960
2014-08-30 14:49:21.631 optool[3959:303] 255200
2014-08-30 14:49:21.632 optool[3959:303] 259400
2014-08-30 14:49:21.632 optool[3959:303] 259424
2014-08-30 14:49:21.632 optool[3959:303] 16, 1
2014-08-30 14:49:21.633 optool[3959:303] 16
2014-08-30 14:49:21.633 optool[3959:303] 32
2014-08-30 14:49:21.633 optool[3959:303] 48
So it successfully gets the offsets of the bindings and as you can see the opcode in this example uses BIND_OPCODE_SET_DYLIB_ORDINAL_IMM
and the opcode for it is 1 (immediately after the binding). I just need now to find and test some ULEB binaries.
Hi Alex,
That looks promising. Here's a real world use case for you. It's our game that we're submitting to the Apple Mac App Store. The macho binary contains an unused GameKit LC that we want to strip out (cause by Unity).
https://www.dropbox.com/s/5w2utv8g2shggpd/UberStrike.app.zip?dl=0
I initially just replaced the LC with another dummy, but if we can actually remove it an realign the dylib opcode bindings that would be great.
Best regards, Shaun
On Sun, Aug 31, 2014 at 2:54 AM, Alex Zielenski [email protected] wrote:
Here is a snippet of code I wrote to parse the opcodes and log some offsets (using Chess.app)
case LC_DYLD_INFO:case LC_DYLD_INFO_ONLY: { NSLog(@"%lu", (unsigned long)binary.currentOffset); struct dyld_info_command info; [binary getBytes:&info range:NSMakeRange(binary.currentOffset, size)]; NSLog(@"%u", info.bind_off); NSLog(@"%u", info.weak_bind_off); NSLog(@"%u", info.lazy_bind_off);
uint8_t *p = malloc(info.bind_size); [binary getBytes:p range:NSMakeRange(info.bind_off, info.bind_size)]; uint8_t immediate = *p & BIND_IMMEDIATE_MASK; uint8_t opcode = *p & BIND_OPCODE_MASK; NSLog(@"%d, %d", opcode, immediate); NSLog(@"%d", BIND_OPCODE_SET_DYLIB_ORDINAL_IMM); NSLog(@"%d", BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); NSLog(@"%d", BIND_OPCODE_SET_DYLIB_SPECIAL_IMM); binary.currentOffset += size; break;}
And the output:
2014-08-30 14:49:21.631 optool[3959:303] 2960 2014-08-30 14:49:21.631 optool[3959:303] 255200 2014-08-30 14:49:21.632 optool[3959:303] 259400 2014-08-30 14:49:21.632 optool[3959:303] 259424 2014-08-30 14:49:21.632 optool[3959:303] 16, 1 2014-08-30 14:49:21.633 optool[3959:303] 16 2014-08-30 14:49:21.633 optool[3959:303] 32 2014-08-30 14:49:21.633 optool[3959:303] 48
So it successfully gets the offsets of the bindings and as you can see the opcode in this example uses BIND_OPCODE_SET_DYLIB_ORDINAL_IMM and the opcode for it is 1 (immediately after the binding). I just need now to find and test some ULEB binaries.
— Reply to this email directly or view it on GitHub https://github.com/alexzielenski/optool/issues/2#issuecomment-53967173.
I know it's been a while but I committed a first iteration of an implementation of this. I haven't tested it and I don't think it'll work on the first try, but the code is there...
@shaunls what app is shown in your screenshot?
@kastiglione That's MachOView https://sourceforge.net/projects/machoview/