Quantcast

mkbundle & Assembly.GetEntryAssembly()

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

mkbundle & Assembly.GetEntryAssembly()

Rick Tillery
I checked the archives, but didn't see a reference to this issue (within the last few years).

I have a project with a need to validate the assembly that is its entry point.

Using Assembly.GetEntryAssembly() works for a stand-alone EXE, both in Windows and in Linux (launched with "$ mono app.exe") but the path returned by Assembly.CodeBase is for the EXE when I use mkbundle.

I've distilled the issue down to the following code, but in the project, the path is used to check for a signed entry assembly.  mkbundle is needed for several unrelated reasons, which would be difficult to address in other ways.

$ cat assytest.cs
using System.Reflection;

public class Test
{
    public static void Main()
    {
        Assembly thisassy = Assembly.GetEntryAssembly();
        System.Console.WriteLine("Assembly Type: " + thisassy.GetType());
        System.Console.WriteLine(" CodeBase: " + thisassy.CodeBase);
        System.Console.WriteLine(" FullName: " + thisassy.FullName);
        System.Console.WriteLine(" Location: " + thisassy.Location);
    }
}

$ mcs assytest.cs

Any of these bundles provide the same output:
$ mkbundle -L /usr/lib/mono/4.5 -o assytest-noargs assytest.exe
$ mkbundle -L /usr/lib/mono/4.5 -o assytest-deps --deps assytest.exe
$ mkbundle -L /usr/lib/mono/4.5 -o assytest-static --static assytest.exe
$ mkbundle -L /usr/lib/mono/4.5 -o assytest-staticdeps --static --deps assytest.exe
$ rm assytest.exe
$ ./assytest-staticdeps
Assembly Type: System.Reflection.MonoAssembly
 CodeBase: file:///home/user/assytest.exe
 FullName: assytest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
 Location: assytest.exe

The EXE itself, launched with mono returns nearly the same thing:
$ mcs assytest.cs
$ mono assytest.exe
Assembly Type: System.Reflection.MonoAssembly
 CodeBase: file:///home/user/assytest.exe
 FullName: assytest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
 Location: /home/user/assytest.exe

The problem is that this means the entry assembly being returned is not the actual entry point, and I don't see a way to determine the assembly that is the correct entry point.

Any suggestions?

Thanks,
Rick

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: mkbundle & Assembly.GetEntryAssembly()

Robert Jordan
On 15.05.2017 21:14, Rick Tillery wrote:
> The problem is that this means the entry assembly being returned is not
> the actual entry point, and I don't see a way to determine the assembly
> that is the correct entry point.

This is by design. From the point of view of the Mono runtime,
the entry assembly is actually that one that was passed to mkbundle.

It would make little sense to modify the code base of the
main assembly to point to the native executable of the bundle,
because you won't be able to load this file anyway, as
it isn't an assembly anymore.

If you're only interested in the code base path you may want to
look how it's done natively:

http://stackoverflow.com/questions/933850/how-do-i-find-the-location-of-the-executable-in-c

Robert

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: mkbundle & Assembly.GetEntryAssembly()

Rick Tillery
Thanks! That looks like it might work in C# (we don't really have any C in this project). I'll give it a try.

However, I respectfully disagree that the path to the assembly is a good choice. I understand that the Linux binary isn't the same as the .Net assembly, but the EXE file is not there at all.

Worse, if I place the properly signed EXE in the directory on the target machine with the Linux binary (at the path provided by GetEntryAssembly()), the validation passes. This occurs even if the Linux binary has no code from the EXE (just the same name at mkbundle time). That's not really a very good validation.

Given that I can identify which platform I'm on, I can modify the validation code to recognize the container/binary differences. But that has to start with being able to locate the actual file that is the entry point. (Your suggestion may get us there. Thanks again.)

With a path to the actual entry point, I'm guessing I'll have the choice of locating the embedded EXE PE/COFF within the ELF & checking it as usual, or introducing a new signing/validation approach on the Linux ELF binary level. (Not sure of the implications of these two approaches yet.)

Rick

On May 15, 2017 7:17 PM, "Robert Jordan" <[hidden email]> wrote:
On 15.05.2017 21:14, Rick Tillery wrote:
The problem is that this means the entry assembly being returned is not the actual entry point, and I don't see a way to determine the assembly that is the correct entry point.

This is by design. From the point of view of the Mono runtime,
the entry assembly is actually that one that was passed to mkbundle.

It would make little sense to modify the code base of the
main assembly to point to the native executable of the bundle,
because you won't be able to load this file anyway, as
it isn't an assembly anymore.

If you're only interested in the code base path you may want to
look how it's done natively:

http://stackoverflow.com/questions/933850/how-do-i-find-the-location-of-the-executable-in-c

Robert

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: mkbundle & Assembly.GetEntryAssembly()

Robert Jordan
On 16.05.2017 01:48, Rick Tillery wrote:
> Thanks! That looks like it might work in C# (we don't really have any C
> in this project). I'll give it a try.
>
> However, I respectfully disagree that the path to the assembly is a good
> choice. I understand that the Linux binary isn't the same as the .Net
> assembly, but the EXE file is not there at all.

 From the point of view of .NET Assembly APIs the EXE file *is* there.

You can access it by System.Reflection, you can load it (once more ;)
as it's already loaded). You can load resources from it, you can check
its meta data, assembly name, public key, etc.

Indeed, System.IO APIs won't see this file, but you don't
really expect that mkbundle's infrastructure is going to redirect
file access into the bundle, do you?

The only bug I can sense is how the mono runtime is reporting
the Assembly.Location of bundled assemblies. I'd rather
expect an empty string for Location as this is usually an
indicator that the assembly was loaded from a byte blob
rather than from a file.

Robert

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: mkbundle & Assembly.GetEntryAssembly()

Rick Tillery
An empty path would be interesting, but still not provide a path to the actual entry point file. (I'll be trying the suggestion this morning, so that may be the key to getting that).

But you mentioned that there is already a built-in way to access the mkbundle'd EXE? I tried looking at the Modules returned by GetModules(), but these similarly report nonexistent filenames. How would I go about accessing the EXE within the bundle?

Thanks!
Rick

On May 15, 2017 10:31 PM, "Robert Jordan" <[hidden email]> wrote:
On 16.05.2017 01:48, Rick Tillery wrote:
Thanks! That looks like it might work in C# (we don't really have any C in this project). I'll give it a try.

However, I respectfully disagree that the path to the assembly is a good choice. I understand that the Linux binary isn't the same as the .Net assembly, but the EXE file is not there at all.

From the point of view of .NET Assembly APIs the EXE file *is* there.

You can access it by System.Reflection, you can load it (once more ;)
as it's already loaded). You can load resources from it, you can check its meta data, assembly name, public key, etc.

Indeed, System.IO APIs won't see this file, but you don't
really expect that mkbundle's infrastructure is going to redirect
file access into the bundle, do you?

The only bug I can sense is how the mono runtime is reporting
the Assembly.Location of bundled assemblies. I'd rather
expect an empty string for Location as this is usually an
indicator that the assembly was loaded from a byte blob
rather than from a file.


Robert

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list


_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: mkbundle & Assembly.GetEntryAssembly()

Rick Tillery
The suggestion from Robert seems to work!  For posterity:

$ cat assytest.cs
using System.Reflection;
using System.Text;
using Mono.Unix.Native;

public class Test
{
    public static void Main()
    {
        Assembly thisassy = Assembly.GetEntryAssembly();
        System.Console.WriteLine("Assembly Type: " + thisassy.GetType());
        System.Console.WriteLine(" CodeBase: " + thisassy.CodeBase);
        System.Console.WriteLine(" FullName: " + thisassy.FullName);
        System.Console.WriteLine(" Location: " + thisassy.Location);
        var builder = new StringBuilder(1024);
        Syscall.readlink("/proc/self/exe", builder);
        System.Console.WriteLine("BundleEnvironment: " + builder.ToString());
    }
}
$ mcs -r /opt/mono/lib/mono/4.5-api/Mono.Posix.dll assytest.cs
warning CS8029: Compatibility: Use -r:LIBRARY instead of -r library
$ mono assytest.exe
Assembly Type: System.Reflection.MonoAssembly
 CodeBase: file:///home/user/assytest.exe
 FullName: assytest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
 Location: /home/user/assytest.exe
BundleEnvironment: /opt/mono/bin/mono-sgen
$ mkbundle -L /usr/lib/mono/4.5 -o assytest-staticdeps --static --deps assytest.exe
$ ./assytest-staticdeps
Assembly Type: System.Reflection.MonoAssembly
 CodeBase: file:///home/user/assytest.exe
 FullName: assytest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
 Location: assytest.exe
BundleEnvironment: /home/user/assytest-staticdeps

Of course, if there is a better way of addressing the EXE assembly, it would be better than directly accessing the Linux binary as a file and searching for it.  As I mentioned, I looked at the Modules list returned by GetModules(), but the name of the EXE appeared there.  Now I'm wondering if that might be a good thing.  If I use the Location name with Assembly.GetModules("assytest.exe"), can I use the Module to access the EXE?

Additionally, I found this information about checking signatures.  (I believe our project uses signtool.exe.)

Are either of these leading me in the right direction?

Rick


On Tue, May 16, 2017 at 7:11 AM, Rick Tillery <[hidden email]> wrote:
An empty path would be interesting, but still not provide a path to the actual entry point file. (I'll be trying the suggestion this morning, so that may be the key to getting that).

But you mentioned that there is already a built-in way to access the mkbundle'd EXE? I tried looking at the Modules returned by GetModules(), but these similarly report nonexistent filenames. How would I go about accessing the EXE within the bundle?

Thanks!
Rick

On May 15, 2017 10:31 PM, "Robert Jordan" <[hidden email]> wrote:
On 16.05.2017 01:48, Rick Tillery wrote:
Thanks! That looks like it might work in C# (we don't really have any C in this project). I'll give it a try.

However, I respectfully disagree that the path to the assembly is a good choice. I understand that the Linux binary isn't the same as the .Net assembly, but the EXE file is not there at all.

From the point of view of .NET Assembly APIs the EXE file *is* there.

You can access it by System.Reflection, you can load it (once more ;)
as it's already loaded). You can load resources from it, you can check its meta data, assembly name, public key, etc.

Indeed, System.IO APIs won't see this file, but you don't
really expect that mkbundle's infrastructure is going to redirect
file access into the bundle, do you?

The only bug I can sense is how the mono runtime is reporting
the Assembly.Location of bundled assemblies. I'd rather
expect an empty string for Location as this is usually an
indicator that the assembly was loaded from a byte blob
rather than from a file.


Robert

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list



_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: mkbundle & Assembly.GetEntryAssembly()

Robert Jordan
On 16.05.2017 17:02, Rick Tillery wrote:
> Of course, if there is a better way of addressing the EXE assembly, it
> would be better than directly accessing the Linux binary as a file and
> searching for it.  As I mentioned, I looked at the Modules list returned
> by GetModules(), but the name of the EXE appeared there.  Now I'm
> wondering if that might be a good thing.  If I use the Location name
> with Assembly.GetModules("assytest.exe"), can I use the Module to access
> the EXE?

I'm not aware of any corlib API which gives you the raw bytes of a
System.Reflection.Assembly or .Module object back.

If your verification code is actually necessitating the whole PE file
to operate successfully, I'm afraid there is no way to achieve this
sanely at the moment.

Now the insane way:

With the so called "old embedding" options of mkbundle you might
be able to intercept the build process and inject a function which
exposes the bundles as BLOBs.

See "-c", "--keeptemp", "--bundled-header" (undocumented) and
the source code of mkbundle. Maybe --custom-main would be sufficient.

What you basically have to do is: expose the "bundled" variable
(of type MonoAssemblyBundle*, see output of temp files when
--bundled-header was specified) with a function:

/* returns a zero-terminated MonoBundledAssemby[] array.
void* mkbundle_get_bundles()
{
        return bundled;
}

and p/invoke it from your managed code with:

[DllImport("__Internal")]
static extern IntPtr mkbundle_get_bundles();

Then you'll need the get the raw bytes of the assembly from
the "data" field of MonoBundledAssembly:

typedef struct {
        const char *name;
        const unsigned char *data;
        const unsigned int size;
} MonoBundledAssembly;


Don't blame me... :)

>
> Additionally, I found this information
> <https://blogs.msdn.microsoft.com/shawnfa/2004/06/07/checking-for-a-valid-strong-name-signature/>
> about checking signatures.  (I believe our project uses signtool.exe.)
>
> Are either of these leading me in the right direction?

The first part (public key token check) from this blog post won't
help you much, unless you really want a *very* basic integrity check.
Assembly Strong Names were not intended for security/integrity checks.

And the second part (p/invoke into mscoree) does not work with
Mono because it's specific to MS.NET.

Robert

_______________________________________________
Mono-devel-list mailing list
[hidden email]
http://lists.dot.net/mailman/listinfo/mono-devel-list
Loading...