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

Uninstalling a load command doesn't update dynamic loader binding ordinals #2

Open
shaunsales opened this issue Aug 28, 2014 · 10 comments

Comments

@shaunsales
Copy link

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.

screen shot 2014-08-29 at 01 19 08

@alexzielenski
Copy link
Owner

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.

@shaunsales
Copy link
Author

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
#2 (comment).

@alexzielenski
Copy link
Owner

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?

@shaunsales
Copy link
Author

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
#2 (comment).

@alexzielenski
Copy link
Owner

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.

@alexzielenski
Copy link
Owner

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.

@shaunsales
Copy link
Author

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
#2 (comment).

alexzielenski added a commit that referenced this issue Jun 26, 2015
@alexzielenski
Copy link
Owner

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...

@kastiglione
Copy link

@shaunls what app is shown in your screenshot?

@shaunsales
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants