IT_Programming/Dev Tools

.Net binary 들 disassemble 및 assemble 하기

JJun ™ 2011. 3. 16. 13:26

------------------------------------------------------------------------------------------------

 출처: http://b4you.net/blog/192

------------------------------------------------------------------------------------------------

 

프로그램을 사용하다 보면 .Net으로 제작 된 binary가 있습니다.

이 바이너리는 CLI(Common Intermediate Language) 형식으로 되어 있는데요.

자바의 .class와 같이 중간 언어라고 생각하면 됩니다.


일반적으로 Visual Studio와 같은데서 생성된 PE 파일은 IDA, ollydbg와 같은 disassembler에서 까보면 되는데 이 .Net 파일들은 ollydbg에서 로딩이 되지 않죠. (olly 2.0i, 아무 플러그인도 설치안한 상태 기준) IDA에서

로딩이 되기는 하는데.. 중요한건 .Net에는 ildasm이라는 강력한 disassembler가 포함되어 있기 때문에

Visual Studio 2005(2003에는 ilasm이 없는듯 합니다)가 설치되어 있다면 언제든지 소스코드(!)를 볼 수

있습니다. 물론 자바 계열의 decompiler와 같이 완벽한 자바 코드로 바꾸어 주는건 아니지만, 어느 정도

읽을 수 있는 CLI코드로 바로 나오기 때문에 공부(또는 이외의 목적)하시는 분들은 도움이 될 것이라

생각하네요.


실행하기 전에:

ildasm과 ilasm은 특별한 환경(?)에서 실행되어야 합니다. 이를 위해, 


위의 그림과 같이 "Visual Studio 2005 명령 프롬프트"를 실행합니다.

 

 

 

1. Disassemble 하기

 

ildasm을 실행하여 disassemble를 하면 되는데, ildasm에는 다음과 같은 옵션들이 있습니다.

 

 사용법: ildasm [options] <file_name> [options]
 
 출력 리디렉션 옵션:
  /OUT=<file name>    GUI를 통해 출력하는 대신 파일에 직접 출력합니다.
  /TEXT               GUI를 통해 출력하는 대신 콘솔 창에 직접 출력합니다.
 
  /HTML               HTML 형식으로 출력합니다(/OUT 옵션이 없는 경우에는 사용할 수 없음).
  /RTF                서식있는 텍스트로 출력합니다(/TEXT 옵션과 함께 사용할 수 없음).


 GUI 또는 파일/콘솔 출력 옵션(EXE 및 DLL 파일에만 해당):
  /BYTES              16진수 형식의 실제 바이트를 명령 주석으로 표시합니다.
  /RAWEH              예외 처리 절을 원시 형태로 표시합니다.
  /TOKENS             클래스 및 멤버의 메타데이터 토큰을 표시합니다.
  /SOURCE             원본 소스 줄을 주석으로 표시합니다.
  /LINENUM            원본 소스 줄에 대한 참조를 포함합니다.
  /VISIBILITY=<vis>[+<vis>...]    표시 범위가 지정된 항목만
          디스어셈블합니다(<vis> = PUB | PRI | FAM | ASM | FAA | FOA | PSC).
  /PUBONLY            public 항목만 디스어셈블합니다(/VIS=PUB와 같음).
  /QUOTEALLNAMES      모든 이름을 작은따옴표로 묶습니다.
  /NOCA               사용자 지정 특성을 출력하지 않습니다.
  /CAVERBAL           CA blob을 일반 텍스트 형식으로 출력합니다(기본값: 이진 형식).
  /NOBAR              디스어셈블리 진행률 표시줄 창 팝업을 표시하지 않습니다.
 
 파일/콘솔 출력 전용 옵션:
 EXE 및 DLL 파일 옵션:
  /UTF8               출력에 UTF-8 인코딩을 사용합니다(기본값 - ANSI).
  /UNICODE            출력에 유니코드 인코딩을 사용합니다.
  /NOIL               IL 어셈블러 코드 출력을 표시하지 않습니다.
  /FORWARD            정방향 클래스 선언을 사용합니다.
  /TYPELIST           라운드트립에서 형식 순서를 유지하기 위해 전체 형식 목록을 출력합니다.
  /HEADERS            출력에 파일 헤더 정보를 포함합니다.
  /ITEM=<class>[::<method>[(<sig>)]  지정한 항목만 디스어셈블합니다.
 
  /STATS              이미지에 대한 통계를 포함합니다.
  /CLASSLIST          모듈에 정의된 클래스 목록을 포함합니다.
  /ALL                /HEADER, /BYTES, /STATS, /CLASSLIST, /TOKENS 조합입니다.
 
 EXE, DLL, OBJ 및 LIB 파일 옵션:
  /METADATA[=<specifier>] 메타데이터를 표시합니다. <specifier>는 다음과 같습니다.
          MDHEADER    메타데이터 헤더 정보 및 크기를 표시합니다.
          HEX         단어뿐만 아니라 추가 정보를 16진수로 표시합니다.
          CSV         레코드 개수 및 힙 크기를 표시합니다.
          UNREX       확인할 수 없는 외부 참조를 표시합니다.
          SCHEMA      메타데이터 헤더 및 스키마 정보를 표시합니다.
          RAW         원시 메타데이터 테이블을 표시합니다.
          HEAPS       원시 힙을 표시합니다.
          VALIDATE    메타데이터의 일관성을 확인합니다.
 LIB 파일 전용 옵션:
  /OBJECTFILE=<obj_file_name> 라이브러리에 있는 단일 개체 파일의 메타데이터를 표시합니다.
 
 옵션 키는 '-' 또는 '/'입니다. 옵션은 처음 3자로 인식됩니다.
 예:  ildasm /tok /byt myfile.exe /out=myfile.il

 

 

 

그럼 이 옵션들을 이용해서 MyApp.exe 라는 파일을 disassemble 하도록 하겠습니다.

 

 

  ildasm /METADATA /UTF8 /ALL /OUT=MyApp.exe.il MyApp.exe 

 

 

 

 /METADATA 는 부가적인 정보(힙 크기, 확인 불가능한 외부 참조, 스키마 정보등)를 출력합니다.

 /UTF8 은 MyApp.exe.il 파일의 인코딩 방식을 utf-8로 합니다.

 

 /ALL 을 선택하면 disassemble 결과물을 개발자가 좀 더 편안하게 볼 수 있는 정보를 출력합니다.

 단순히 disassemble를 하는거라면, 굳이 필요 없습니다.

 

 /OUT=MyApp.exe.il 은 출력 파일의 이름을 지정합니다.

 MyApp.exe disassemble를 수행 할 파일의 이름을 지정합니다.

 

위와 같은 옵션을 통해 il 파일이 생성되었다면, 이 파일을 수정한 뒤 다시 assemble를 수행할 수 있습니다.

 

 

 

2. Assemble 하기

assemble은 ilasm을 실행하여 수행할 수 있습니다. ildasm과 마찬가지로 ilasm/? 를 하면 제공하는 옵션을

볼 수 있습니다. ildasm과는 다르게 영문이군요.

 

 

 Microsoft (R) .NET Framework IL Assembler.  Version 2.0.50727.1433
 Copyright (c) Microsoft Corporation.  All rights reserved.
  
 Usage: ilasm [Options] <sourcefile> [Options]
 
 Options:
 /NOLOGO         Don't type the logo
 /QUIET          Don't report assembly progress
 /NOAUTOINHERIT  Disable inheriting from System.Object by default
 /DLL            Compile to .dll
 /EXE            Compile to .exe (default)
 /PDB            Create the PDB file without enabling debug info tracking
 /DEBUG          Disable JIT optimization, create PDB file, use sequence points from PDB
 /DEBUG=IMPL     Disable JIT optimization, create PDB file, use implicit sequence points
 /DEBUG=OPT      Enable JIT optimization, create PDB file, use implicit sequence points
 /OPTIMIZE       Optimize long instructions to short
 /FOLD           Fold the identical method bodies into one
 /CLOCK          Measure and report compilation times


 /RESOURCE=<res_file>    Link the specified resource file (*.res)
                        into resulting .exe or .dll
 /OUTPUT=<targetfile>    Compile to file with specified name
                        (user must provide extension, if any)


 /KEY=<keyfile>      Compile with strong signature
                        (<keyfile> contains private key)


 /KEY=@<keysource>   Compile with strong signature
                        (<keysource> is the private key source name)


 /INCLUDE=<path>     Set path to search for #include'd files
 /SUBSYSTEM=<int>    Set Subsystem value in the NT Optional header
 /FLAGS=<int>        Set CLR ImageFlags value in the CLR header
 /ALIGNMENT=<int>    Set FileAlignment value in the NT Optional header
 /BASE=<int>     Set ImageBase value in the NT Optional header (max 2GB for 32-bit images)
 /STACK=<int>    Set SizeOfStackReserve value in the NT Optional header
 /MDV=<version_string>   Set Metadata version string
 /MSV=<int>.<int>   Set Metadata stream version (<major>.<minor>)
 /PE64           Create a 64bit image (PE32+)
 /NOCORSTUB      Suppress generation of CORExeMain stub
 /STRIPRELOC     Indicate that no base relocations are needed
 /ITANIUM        Target processor: Intel Itanium
 /X64            Target processor: 64bit AMD processor
 /ENC=<file>     Create Edit-and-Continue deltas from specified source file
 
 Key may be '-' or '/'
 Options are recognized by first 3 characters
 Default source file extension is .il
 
 Target defaults:
 /PE64      => /PE64 /ITANIUM
 /ITANIUM   => /PE64 /ITANIUM
 /X64       => /PE64 /X64

 


위에서 disassemble 되었던 파일을 다시 assemble 하도록 하겠습니다.

ilasm /exe /resource=MyApp.exe.res MyApp.exe.il


/exe 는 대상 파일이 exe임을 표시합니다. .il파일이 dll일 경우 /dll를 지정합니다.

/resource=MyApp.exe.res 아이콘 등과 같은 리소스 정보가 있을 때 이 옵션을 이용하여야만

대상 실행 파일에 추가적으로 리소스가 삽입됩니다. MyApp.exe.il 은 assemble할 il파일을 나타냅니다.

 

위와 같은 과정을 수행하여 .exe를 생성한 뒤 실행하면 원본 실행 파일과 동일한 기능을 수행하는 것을

보실 수 있습니다. (원본 파일과 assemble후 파일의 내용은 일부 다릅니다.)

 

다만, 위의 방법을 이용하여 disassemble 및 assemble를 하였다면 strong-named 파일의 경우

서명 정보를 잃어버리기 때문에 assemble 후 정상적으로 동작하지 않을 수 있습니다.

 

* strong-name 이용하기
http://msdn.microsoft.com/en-us/magazine/cc163583.aspx

 

 

더보기
Strong name signatures (and signing in general) are a key facet of Microsoft® .NET Framework security. But regardless of how well designed .NET signatures may be, they won’t offer the maximum benefit if you don’t know how to use them properly. This installment of CLR Inside Out talks about strong names, strong name signatures, and how to use them.


A Short Refresher
Digital signatures are used to verify the integrity of data being passed from the originator (the signer) to a recipient (the verifier). The signatures are generated and verified using public key cryptography. The signer of a message has a pair of cryptographic keys: a public key, which everyone in the world knows, and a private key, which is kept secret by the signer. The verifier knows only the public key, which is used to verify that the signer knew the private key and the message.
In some cases, when some additional infrastructure is in place, digital signatures can also be used to reliably learn the name of the signer, and to ensure some chunk of data (a message, some code, or so on) has not been modified after the signer created the signature for the data.
Various mechanisms are used to implement digital signatures. The current implementation of strong names in the .NET Framework relies on the RSA public key algorithm and the SHA-1 hash algorithm.


Strong-Name Signing
Strong names offer a powerful mechanism for giving .NET Framework assemblies unique identities. The strong name for an assembly consists of five parts: a public key, a simple name, a version, an optional culture, and an optional processor architecture. The public key is an RSA public key. The simple name is just a text string—usually the name of the file (without the extension) that contains the assembly. The version is a four-part version number, in the form of Major.Minor.Build.Revision (for example, 1.0.0.1).
To get a valid strong name, an assembly is strong-name signed during the build process. This is done using the private key that corresponds to the public key in the strong name. The strong name signature can then be verified using the public key. There are a few different techniques for creating a strong name signature and for managing the private key used in signing. I’ll discuss these various techniques later in the column.
When one assembly references a strong-named assembly, the referring assembly captures the strong name information for the referenced assembly. When the .NET Framework loads a strong-named assembly, it verifies the strong name signature. If the strong name signature of the assembly cannot be verified, the .NET Framework will not load the assembly.
One exception to this process has to do with strong-named assemblies that come from the Global Assembly Cache (GAC). These are not verified each time the .NET Framework loads them. This is because assemblies in the GAC are verified when installed in the GAC. Since the GAC is a locked-down, admin-only store, assemblies located here do not need to be verified each time they are loaded.


Why Use Strong Names?
Strong names prevent spoofing of your code by a third party (that is, of course, as long as you keep the private key secure). As mentioned, the .NET Framework verifies the signature either when loading the assembly or when installing it in the GAC. Without access to the private key, a malicious user cannot modify your code and successfully re-sign it.
Given that strong names prevent spoofing, they can be used to make certain security decisions. An enterprise, for example, could choose to trust all code that comes from the enterprise’s intranet and has been signed with the enterprise’s strong name key. But strong name signatures do not contain any reliable information about the publisher, so while it is safe to trust keys you control, it is dicier to trust keys from other organizations unless you have a secure channel to get their public key.
It is also important to note that strong names do not have any revocation mechanism that can be used if the private key is compromised. It goes without saying that if you plan to use your enterprise’s strong name for trust, keep the private key private! (I’ll have tips for this later in the column.) It’s a good idea to scope the trust of a strong name to a particular location (such as your enterprise’s application server) or at least a zone (such as your intranet zone). This provides some mitigation in case your private key is ever compromised or a buggy version of a signed assembly makes its way to some bad guys and they try to lure users into running it from outside the intranet.


What Strong Names Can’t Do
Strong-name signing is useful for preventing a malicious user from tampering with an assembly and then re-signing it with the original signer’s key. It cannot do anything, however, to prevent a malicious user from stripping the strong name signature entirely, modifying the assembly, re-signing it with his own key, and then passing off the assembly as his own code. Digital signatures in general and strong name signatures in particular cannot help with this problem. Watermarking is a more appropriate solution for protecting against this scenario.
Strong names are secure only when the strong name private key is kept secure. As I mentioned earlier, they have no revocation capabilities and they don’t expire. Nor do strong names inherently provide any way to securely map a public key to a particular publisher. Thus, strong names should be used very cautiously when making security decisions. Ideally, these security decisions should only be made when you can be sure the strong name public key corresponds to a particular publisher (for example, when the public keys are generated by your organization’s IT department). You should note that more sophisticated signing schemes (for example, Authenticode®) are available that do offer revocation and publisher verification capabilities.


Working with Strong Names
Strong-name signing is a good idea for most applications, especially those that are deployed over a network or any medium not fully controlled by the deployer. The anti-spoofing and anti-tampering benefits are quite valuable. However, there are some challenges to be aware of when using strong-name signing.
First, all assemblies referenced by a strong-named assembly must also be strong-named. If you reference an assembly written by a third party that is not strong-name signed, you cannot strong-name sign your assembly.
Second, strong-name signing makes servicing more complicated. Under current versioning policy, an assembly will always attempt to load the exact version of the assembly it was built against. If, for example, your application was built against version 1.0.0.0 of a strong-named assembly and you fix a bug in the assembly, bumping the version number to 1.0.0.1, the existing application will not find the updated assembly.
There are a few ways to deal with this situation. You can rebuild the application against the new assembly. Obviously, this is an annoying process for just picking up a bug fix and it may not be an option in some situations. Still, it works fine when your code is not widely deployed or the assembly being serviced is not widely shared across applications.
Another option is if the assembly is installed in the GAC, you can use publisher policy to redirect loads for version 1.0.0.0 to version 1.0.0.1. However, publisher policy is complicated.
Yet another option is to fix the bug and not change the assembly version number, so existing applications can still find the assembly. This is the approach the .NET Framework uses for bug fixes, although it’s not appropriate for new features or anything that breaks compatibility. If you are adding new features, you should bump the assembly version up and have applications rebuild to opt in to the new features.
Needless to say, strong-name signing adds some complexity to your development and build processes. But this is necessary to keep the private key secure. I’ll discuss how to deal with this complexity in a moment.
Now let’s take a quick look at a simple signing scenario: creating a random key pair and storing it in a file. I’m only using this particular approach as a way to introduce you to how strong-name signing works. Obviously, storing the private key in a file next to your code is not a good way to protect your key so you generally should not use this approach in production.
If you are using command-line tools, you can create a key pair with sn.exe in the SDK. Then you pass that key pair to the compiler when compiling your code. The following commands create a new random key pair using sn.exe and build a strong-name signed assembly with csc.exe:

 

sn -k mykey.snk csc /keyfile:mykey.snk myapp.cs
This produces a strong-name signed application named myapp.exe. You can do the same thing with Visual Studio® 2005. The Signing pane on the project properties page controls strong-name signing for your project (see Figure 1). To create a simple key file, select the "Sign the assembly" option, and create a new key file that is not password protected. Visual Studio will add the key file to your project and use it to sign your application.

 

 

Figure 1
 Signing the Assembly 
As I mentioned, storing the private key in a file next to your code is not a secure way to protect your private key. Fortunately, Visual Studio allows you to password-protect the key file. When you create a new strong name key for your project, just leave the "Protect my key file with a password" option selected and enter a password for your key file (see Figure 2). Visual Studio adds a personal certificate, a PFX file, to your project. This file includes your encrypted private key, protected by the password you selected.

 

 

Figure 2
 Creating a Key 
While password-protecting your key files is a much better solution than storing them in the clear, it is still not ideal. You would still have to distribute the PFX file to all of your developers, and they would all have to know the password for the PFX file. Secrets that are widely shared like this do not tend to stay secret for very long. Ideally, you should not have to distribute the private key to build and test your code during development.


Delay Signing
This is where delay signing enters the picture. Delay signing allows you to generate a partial signature during development with access only to the public key. The private key can be stored securely out of the hands of the developers and used to apply the final strong name signature just before shipping your code.

To use delay signing, follow these steps:

  1. Extract the public key from the key pair. The public key can be distributed to developers in your organization without any security risk.
  2. Use the public key to delay sign your assemblies during development. Because the assemblies are not fully signed yet, you’ll also have to configure your development machines to skip strong name signature verification for your key—otherwise, the .NET Framework will not allow you to load the delay-signed assemblies.
  3. When you are ready to ship your code, use the (well guarded) private key to apply the final strong name signature to your delay-signed assemblies.
For step one, you can use the sn.exe tool to extract the public key from a key pair. For example, say you have your key pair stored in a securely configured key container named EnterpriseKey on a locked-down machine. You would use the following command to extract the public key to the EnterprisePublicKey.pk file:

 

sn -pc EnterpriseKey EnterprisePublicKey.pk
You can then distribute EnterprisePublicKey.pk to the developers in your organization. You can usually use the same public key for multiple assemblies because each will have a different simple name.
For step two, you use compiler options to delay sign your assemblies with the public key when building them. The example that follows creates a delay-signed application using options for the C# compiler:

 

csc /delaysign+ /keyfile:EnterprisePublicKey.pk myapp.cs
Now if you try to run myapp.exe, it will not run because it does not have a valid full strong name signature. You must configure the .NET Framework to skip verification for the delay-signed assemblies using your public key. To do this, you use the sn.exe tool again. The following example configures the .NET Framework to skip strong name signature verification for the myapp.exe assembly on your development machines:

 

sn -Vr myapp.exe
You can also use the sn.exe tool to skip signature verification for all assemblies signed with a particular key. For example, if you want to configure your machine to skip all assemblies delay signed with the same key as your application, you first do the following on your development machines:

 

sn -T myapp.exe
This prints out the value of the public key token, a shortened version of the public key that looks something like this:
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. Public key token is b03f5f7f11d50a3a
(Note that the actual value of the public key token will vary depending on your public key.) You then execute the following command to skip strong name verification for any assembly using that public key token:
sn -Vr *,b03f5f7f11d50a3a
Any assembly delay signed with that public key will now skip strong name signature verification and run on your development machine. Note that skipping strong name signature verification is something that should only be done on development machines. It should never be done on production computers because it opens up those machines to assembly spoofing attacks.
Finally, for step three, you use the real private key to generate the final full strong name signature before shipping your code. Again, you use the sn.exe tool to do this. For example, if your key pair is stored in a key container named EnterpriseKey on your signing computer, you would use the following command to sign a delay-signed assembly:

 

sn -Rc myapp.exe EnterpriseKey
Now the assembly has a full signature. one of the nice things about delay signing is that it does not require any of the assemblies that use the delay-signed assembly to be rebuilt. Any assemblies that refer to the delay-signed assembly had access to its public key and were therefore able to create a full assembly reference, even though the assembly did not have a full signature.


Test Key Signing
There is a new feature in the .NET Framework 2.0 called test key signing. This is similar to delay signing. With test key signing, you sign your assembly with an alternate private key during development and then configure your development computers to verify the signatures against the alternate public key that corresponds to the alternate private key. Test key signing is valuable when you want almost the exact same behavior as full signing for testing purposes. For example, with delay signing, assemblies are not verified at all. This could hide performance issues if your application loads many strong-named assemblies because none of the strong name signatures would be verified during test runs when delay signing is turned on.

Test key signing involves the following steps:

  1. Delay sign your assembly.
  2. Create a test key pair with which to sign your assemblies. You can do this using sn.exe with the -k option.
  3. Test sign your assembly. You can do this using sn.exe and either the -TS or -TSc option. This will generate a signature for the assembly using the private key from your test key pair.
  4. Configure your development machines to use the test public key to verify the test key signed assemblies. You can use the –Vr option on sn.exe to do this.
When you are ready to ship your assemblies, you sign them with the real private key using sn.exe with either the -R or -Rc option, just as you would for a delay-signed assembly. The following series of commands creates a test key pair, test key signs a delay-signed assembly, extracts the test public key, and configures a development machine to verify the assembly against the test key:
sn -k TestKey.snk 
sn -TS myapp.exe TestKey.snk 
sn -p TestKey.snk TestPublicKey.pk 
sn -Vr myapp.exe TestPublicKey.pk
When using test key signing, you should still protect the test private key, but you do not have to guard it as closely as the real private key. It is also a good idea to change the test key pair periodically.


Managing the Private Key
It is much easier to keep your private key secure if you are using delay signing or test key signing. You only have to have the private key installed on one or possibly a small number of locked-down signing computers. You can install the private key in a key container using restrictive access control lists (ACLs) to prevent unauthorized access to the key container. Alternatively, you can store the private key on a smart card or other separate hardware device. The sn.exe tool allows you to use a different cryptographic service provider (CSP) for signing. Take a look at the –c option in the documentation for sn.exe.


Working with Authenticode
Authenticode signatures provide more security semantics than strong name signatures. To generate an Authenticode signature, you have to obtain a code-signing certificate from a trusted certificate authority (CA). This can either be a commercial CA or a CA established by your IT department. The CA provides some level of assurance that the person or organization with the certificate is who they say they are, giving you some confidence that the software really came from the publisher listed in the signature. Authenticode also supports expiration and revocation, making it more flexible than strong name signatures. However, like strong name signatures, the presence of an Authenticode signature does not guarantee that you can trust the code. Authenticode simply provides some assurance about who published the code and that the code has not been tampered with since the publisher signed it.
Authenticode signatures can co-exist with strong name signatures. However, you must strong-name sign your assembly first, before applying the Authenticode signature. Adding the Authenticode signature will not invalidate a strong name signature.
Authenticode signatures can be used to trust applications deployed using the new ClickOnce model in the .NET Framework 2.0. You can use the tools in Visual Studio 2005 or in the SDK to apply an Authenticode signature to the deployment manifest of your ClickOnce application. You then install the certificate used to create the Authenticode signature in the trusted publisher certificate store on the client computers that will run your application; this can be done using Group Policy, Systems Management Server (SMS), or any other deployment mechanism available to you. Any ClickOnce application signed with this Authenticode certificate will run without any prompting. This is a convenient way to trust your enterprise’s applications.
Strong name signatures serve a valuable role in providing a form of identity for managed assemblies. Just remember that private key security is critical for strong names. If you do decide strong name signatures are appropriate for your organization, be sure to use some of the techniques discussed in this column to manage and protect the security of your private key.


Send your questions and comments to clrinout@microsoft.com.


Mike Downen is the program manager for security on the CLR team. He works on Code Access Security, the cryptography classes, and the ClickOnce security model. You can read Mike’s blog and contact him at blogs.msdn.com/CLRSecurity.