PDA

View Full Version : {smartassembly} protection analysis + unpacker (with source)


arc_
July 10th, 2010, 11:07
A while ago I decided to get my hands dirty on .NET protections, and {smartassembly} by redgate seemed like a nice start. There already exist unpackers for this, but they seem to be mostly out of date (don't support the newer protections), and more importantly I was unable to find an explanation on how the protection actually works.

So I set out and explored it by myself. It turns out there is really not that much to this "first-rate obfuscator" as redgate calls it. The protection consists of these layers:

The names of non-public namespaces, classes, methods and fields are removed and replaced by junk. Not much to do against that.
All the string constants are encrypted. ldstr instructions get replaced by a call to a method that fetches the string by index.
All calls to imported methods (methods from externally referenced types) are obfuscated. They get replaced by a call to a delegate that passes control to the original imported method. This delegate is set up at runtime.
Some weak control flow obfuscation is performed. It's a bit like the strategic code splicing as seen in Armadillo: small code sequences of one or two instructions get moved to the end of the function, and a branch is placed at their original location.
Finally, all the resources are encrypted as well (but their names are left alone, e.g. in instantiations of ResourceManager).


That's it. Now let's look at these in a bit more detail.

String encryption
Every string literal is encoded as UTF-8 and then base64. It also gets a length prefix (which is variable-length encoded). All these length-prefixed base64 strings are then concatenated - the resulting block is then compressed using regular deflate(), and encrypted using regular DES.

The encrypted block is stored in the binary as a resource stream (with a GUID as name) and is decrypted and decompressed at runtime. This process consists of a loop which performs a deobfuscation pass on every iteration, passing the output of each pass as input to the next. It identifies the next layer to remove by a 4-byte header that is prepended to the data:

"{z}\0x02": decrypt the data using DES (key and IV are in plaintext in the binary)
"{z}\0x01": decompress the data using raw, headerless inflate


Every class that uses the ldstr instruction somewhere has a static field added which contains a delegate. Every ldstr instruction gets replaced by a call to this delegate. It accepts an int and returns the corresponding string through the following algorithm:

Subtracts its own metadata field index from the given number (i.e. the lower 24 bits of the metadata token)
Subtracts a fixed constant, which is stored as plaintext in the #US stream
The resulting number is an offset into the base64 block. It goes to that offset, reads the variable-length encoded string length, extracts the base64 string, decodes from base64, and decodes from UTF-8. Optionally the int -> string mapping is cached.


Imported methods
The actual imports (MemberRef metatable) are left completely intact, only the calls to them are obfuscated.

For every import method signature, a new delegate class is added to the binary (with no namespace). For every imported method, a static field is added to its corresponding delegate class, containing an instance of that class. Every call to an imported method is replaced with a call to that static delegate instance.

The delegate instances are initialized at runtime in the delegate class's static constructor. They get assigned a method body that is generated using reflection, which does nothing more but pass through the parameters to the original imported method. The index of the imported method to call (in the MemberRef table) is calculated from the name of the delegate instance field. The name also indicates whether the call is virtual or not.

Code flow obfuscation
Not much more to say about this than has already been said. The chosen code sequences get moved to the end of the innermost exception handler, or, if there isn't one, to the end of the method. This is not done very often in each method (most of the code is left unmodified) and no further junk code is added.

I solved this by building a basic block tree of the method and from there searching for "redundant" branches: unconditional branches to a basic block that is not reachable from anywhere else. I then remove the branch and "pull in" the target basic block to after the basic block that jumps to it.

Resources
All resource streams are moved out of the main file into a separate, new assembly that has a GUID as name. This assembly is then compressed and encrypted in exactly the same way and with the same keys as the strings block, and is also added as a resource stream with a GUID name (the same as the assembly name).

The <Method> .cctor is extended with code that registers two event handlers: ResourceResolve and AssemblyResolve.

Since the original resources were removed, any attempt to access them will result in the ResourceResolve event being triggered. The handler for this will do Assembly.Load with the name of the resource assembly (the GUID), and checks if this can serve the resource request. If so, it returns the assembly.

Of course, the resource assembly can't be loaded just like that since it doesn't actually exist as a .dll on disk. This is where the AssemblyResolve handler comes in. It compares the name of the requested assembly to the name of the resource assembly, and if this matches, it decrypts, decompresses, loads, and returns the resource assembly, all in-memory.

The unpacker
I couldn't come up with a clever name for my {smartassembly} unpacking tool so I just called it dumbassembly . It removes all code protections mentioned here except the destroyed names. It also decrypts and extracts the resources. I do not claim that it will work on all {smartassembly} protected applications. In fact, I only tested it on one (another redgate product). In the end this project was more about analyzing the protection and demonstrating how it works.

The unpacker is completely free and open source and also comes with the description of the different protection layers. So if you find a binary which the unpacker doesn't work on, you have the possibility of analysing and fixing the problem yourself.

Since I generally can't stand .NET applications because of their typical sluggishness, it's written in (optimized) C++ . A Windows binary is included with the download. If you want to compile it yourself, be aware that 1) it is Windows-only and 2) will only compile with VS2010 as it uses some C++0x features.

Download: http://www.mediafire.com/?m6mlrdj3xp56af7

RebelDotNET
The unpacker comes with RebelDotNET by Daniel Pistelli (http://ntcore.com/), an excellent .NET PE rebuilder. It uses it for replacing the #US stream with a new one containing all the decrypted strings.

Kurapica
July 10th, 2010, 15:38
this is the best work on this protector since version 2.0 !!

really impressive analysis and code.

Edit :

I have a little question, does it only support assemblies protected with version 5.0 and above ?

please specify version.

arc_
July 10th, 2010, 16:54
To be frank I have no idea what version of SmartAssembly the program I reversed was protected with... But since it's from redgate itself and quite recent (2010), I guess it's safe to assume it's the latest.

Will it work on previous versions? Probably not, I'm e.g. using hardcoded method offsets to find the string decryption keys, which will not be the same in older versions. Also, in the string decryption routine I saw code for zip decompression (with a check for a PK header) which is probably for backwards compatibility with older versions, but since it's not used anymore I didn't add support for it.

Kurapica
July 11th, 2010, 03:29
I've tried this Project against a simple exe protected by version 5.0

It looks like it's not supported or maybe I'm doing everything wrong

I've attached the file if you want to have a look.

arc_
July 11th, 2010, 07:34
There were two reasons why it was going wrong on that file:

The smartassembly meta stream is not called "{smartassembly}" but "SmartAssembly", so the unpacker thought there was no SA protection at all. I added this second name as well.
As feared, the code offsets in the methods for retrieving the subtraction constant and the string decryption keys were all different. (In fact, your string decryption function contained a whole new feature: it verifies that the assembly calling it is signed by RedGate .) So I put some more effort into it and made the parameter retrieval code much more generic.


With these changes the tool can now successfully unpack your binary. Updated download link is in the main post.

Kurapica
July 11th, 2010, 12:21
very nice, now it can deal with ver 5.x assemblies.

all that is left, is to have a running fixed file, your fixed files seem ok but maybe the

some type initialization is messed up and causes a crash :\

I used my tracer to find this error.

Respect

arc_
July 11th, 2010, 13:52
Yes, I noticed that the unpacked files don't actually run. It's probably down to the resources, a quick glance shows that these are also decrypted at runtime, on-demand on the "AssemblyResolve" event. This might be failing due to the strong name signature no longer being valid after the unpack.

I'll have to look into it. At least you can already properly decompile and analyze targets now.

Edit: looks like it isn't due to the resources, the obfuscation process is way too simple to cause damage. The resource assembly doesn't contain any kind of code. I added information about the resource obfuscation process anyway in the main post, and also updated the unpacker to support it. Still can't run unpacked files.

yfki
August 17th, 2010, 14:23
_arc Any suggestions here?


C:\Documents and Settings\Vmware\Desktop\dumbassembly-0.3>dumbassembly.exe Comma
nds.exe Commands_Out.exe
{smartassembly} unpacking tool by arc_
--------------------------------------

Loading input file...
Stripping 1009 methods...
Scanning for indirect imports...
Assertion failed: index >= 0 && index < m_Data.size (), file d:\vs2010\projects\
dumbassembly\shared\lazyvector.h, line 17

toyzruz
August 17th, 2010, 16:00
hey all (:

i hope you can help and say me what protection this file use i think this smartassembly but i cannot unpack this.

Quote:
Scanning -> ...\Protected.exe
File Type : 32-Bit Exe (Subsystem : Win GUI / 2), Size : 3929088 (03BF400h) Byte(s)
[File Heuristics] -> Flag : 00000000000001001101000000110001 (0x0004D031)
[!] {smartassembly} DotNet Obfuscator detected !
[CompilerDetect] -> .NET
- Scan Took : 0.673 Second(s)


http://www.abload.de/img/unbenanntbmi9.jpg

http://filebeam.com/aaff2280cd5c051e920ac855f3588519 ("http://filebeam.com/aaff2280cd5c051e920ac855f3588519")

you have any tips?

sry for my bad english

best regards

maci2
August 27th, 2010, 14:19
those 2 guys before are right. The tool just crashes and unpacks nothing. Pity I don't know C++ and cannot repair it...

hanz
September 2nd, 2010, 03:31
Yes! The tool crashes!
Kurapica can you help us?

I've this error:
Quote:
"Assertion failed: index >= 0 && index < m_Data.size (), file d:\vs2010\projects\ dumbassembly\shared\lazyvector.h, line 17"


I don't undestand C++ too....i'm a .NET programmer!

yfki
September 2nd, 2010, 16:30
This would really be incredible if we could get a a fully working SA unpacker.

DeSmart is amazing, but it leaves you with an inoperable executable, so any desired changes still need to be cone to the obfuscated version -- not a pleasant task

_arc or Kurapica - Can you guys help here?

__H
November 10th, 2010, 17:14
Trying dumbassembly 0.3 against two files which are definitely protected with a recent version of smartassembly I get:

Code:
Loading input file...
Input file is not protected with {smartassembly}.


desmart crashes / runs out of memory on these. Seems smartassembly tweak things in every update...

Is any update for dumbassembly (which looked like the most hopeful tool I found) forthcoming, or can anyone recommend any other tools that might work?

__H
November 16th, 2010, 17:35
I have patched out the return in main.cpp which causes dumbassembly to quit on my file, just to see where it fails:

Code:
C:\tools\dumbassembly>dumbassembly-patch.exe input.dll output.dll
{smartassembly} unpacking tool by arc_
--------------------------------------

Loading input file...
Input file is not protected with {smartassembly}.
Stripping 19426 methods...
Assertion failed: outputInfo.m_iOffset >= 0 && iBBEndOffset <= iSize, file c:\tools\dumbassembly\dotnetpe\basicblockpool.cpp, line 679


Seems that the PE file parsing is failing on this file for some reason.
The lines above the failing assert are:

Code:

BasicBlock::OutputInfo& outputInfo = pBB->GetOutputInfo ();
int iBBEndOffset = outputInfo.m_iOffset + pBB->GetLength () + outputInfo.m_iBranchLength;
assert ( outputInfo.m_iOffset >= 0 && iBBEndOffset <= iSize );


The files I have are definitely {smartassembly} protected, I'm guessing with the latest version... Any help would be appreciated.

MadCoder
December 4th, 2010, 15:27
Hi

Here I have another sample where unpacker fails. See the attached files.

See if you can solve this.

Thx

arc_
May 22nd, 2011, 06:04
Almost a year after the initial release, I'm happy to present dumbassembly 0.4 . Many bugs have been fixed, the unpacker is now much more robust and also produces better results.

Improved SmartAssembly detection, now also the SA version number is displayed.
Better code splice repairing algorithm
Better string encryption key finding algorithm
Overall bugfixes and stability improvements
dumbassembly now supports SmartAssembly 6!
dumbassembly now produces working assemblies! Unpacked files run just like the original packed file.


New protection measures in SmartAssembly 6
As mentioned in the list above, dumbassembly can now also unpack assemblies protected with SA 6. For a major version number, very little has changed.

The unconditional branches with which code splicing is done are now sometimes replaced by pairs of (ldc.i4, brtrue/false) instructions. Additionally, similar pairs of instructions are inserted to produce nops.
The string encryption key and IV are now passed to the decryptor using reflection (to make them harder to find I guess?)
Many static constructors now check the assembly's public key token against a fixed string to make sure it wasn't altered; if it was, an exception is thrown to crash the program.

dumbassembly 0.4 deals with all of these.

Apart from that, everything is pretty much the same as SA 5. String obfuscation and import hiding haven't changed at all.

Download
It appears the forum doesn't let me edit the main post anymore, so please use the following link (the main post still points to the previous version).

dumbassembly 0.4 (binary + source): http://www.mediafire.com/?ghr1cqkeu750h3p

rendari
May 24th, 2011, 15:39
Much thanks arc. Will need your tool on a weekly basis

arc_
May 28th, 2011, 13:47
Version 0.5.1 is here!

Further improved anti-code splicing.
Fixed an assert in anti-import hiding (as reported on Black Storm forum).
Improved algorithm for finding the string/resource encryption key.
Decrypted resources are now no longer just extracted to a folder, but merged back into the unpacked file. The program will then use these instead of the encrypted resources.
If the target was signed, the unpacked file is now re-signed with a random keypair (you can also provide one yourself). In addition, every occurrence of the original public key token in the program's strings and resources is replaced by the new public key token. This is not only an alternative fix for SA's tamper detection, but also helps with WPF applications: these have XAML resources that point back to the assembly itself, with public key token. So now WPF applications will run right after unpacking without further changes.


Edit 2011/06/01: 0.5.2 is released with bugfixes and improvements in string decryption and anti-import hiding.

Enjoy: http://www.mediafire.com/?stry9ud2ep67v5e

fbtg666dtc
June 3rd, 2011, 18:24
great job arc_ , especially the part for the SA tamper detection
many thanks

Silkut
June 5th, 2011, 15:44
Hey arc_,

Thanks for sharing your tool with us.
I created a page on the CRCETL for your tool.
Feel free to update/complete it as you wish!

http://www.woodmann.com/collaborative/tools/Dumbassembly

arc_
June 7th, 2011, 16:12
0.5.4 release is here: http://www.mediafire.com/?otr597g8gxs2qaj


Fixed a bug in variable-length integer reading that caused a crash when decrypting very large strings.
Added support for 64-bit PE files.
Embedded assemblies are now extracted.
Code flow restoration bugfix: exception filters were not being fixed up. They were left at their original offset as in the packed file, which in the unpacked file of course becomes invalid since the instructions have been moved around.


Silkut: Thanks for adding the entry, I added some more information to it .

rendari
June 11th, 2011, 03:12
Much thanks for the 64 bit support ^^

arc_
June 11th, 2011, 08:56
Another small release with a couple of fixes, 0.5.5: http://www.mediafire.com/?jt4hd3uey4hfieb


Fixed an infinite loop when cleaning up code splicing in an infinite loop in the packed program.
Added support for another slight variant of string encryption in older SA versions.
Improved detection of string encryption method.
If the original program was signed with a 2048-bit key, re-signing is now done with a 2048-bit key as well (instead of always 1024).

pebbles
June 19th, 2011, 13:42
Nevermind figured it out.

revbones
July 21st, 2011, 14:52
Just to clarify - once I run a SmartAssembly protected dll through, I should be able to take the generated .snk file and resign all the other assemblies that used the original one. Then I should be able to replace the orig with the dumbassembly one and have the same functionality right?

Assuming that works, I can then use something like Reflector & Reflexil to modify the new dll?

Just asking as a sanity check, since I went through it once before (and there are a lot of related dll's) and I must've missed something - so I thought I'd ask before going through it again and running up against a wall.

arc_
July 22nd, 2011, 13:42
Re-signing an assembly changes its public key token, so you need to update any assemblies that reference it to use that new token. You can use "sn -T assem.exe" to find the current public key token of an assembly. So you would:

Find the assembly's original public key token
Run it though dumbassembly
Get the assembly's new public key token (from autogenerated keypair)
Search and replace the original token by the new token in any assemblies that reference it
If necessary, re-sign those assemblies (as patching public key tokens invalidates their signature of course)


Once you have the whole thing running again, you can indeed use Reflexil to make changes and re-sign using the autogenerated snk.

As an alternative, you can try *removing* the signature on the unprotected assembly and again updating any referencing assemblies. The advantage to this is that here, you have tools to automate the job, e.g. AdmiralDebilitate.

Kurapica
July 22nd, 2011, 13:47
I also recommend this tool : http://portal.b-at-s.net/download.php?view.415

trasua99do
July 25th, 2011, 09:57
Another small release with a couple of fixes, 0.5.5: http://www.mediafire.com/?jt4hd3uey4hfieb
--------------------------------------------------------------------------------------------------------------

Dear Arc_
I have tested but have an error: "The procedure entry point_invalid_parameter_noinfo_noreturn could not be located in the dynamic link library MSVCR100.dll"

Though, My computer has the file MSVCR100.dll


Thanks.

0x90
July 25th, 2011, 17:19
Dear arc_,

thank you very very very much for this great tool! It works great!

@trasua99do: you have to install Microsoft Visual C++ 2010 Redistributable Package (x86) ("http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=5555")

trasua99do
July 26th, 2011, 04:15
Quote:
[Originally Posted by 0x90;90732]Dear arc_,

thank you very very very much for this great tool! It works great!

@trasua99do: you have to install Microsoft Visual C++ 2010 Redistributable Package (x86) ("http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=5555")


Hi @0x90
I have to install Microsoft Visual C++ 2010 Redistributable Package (x86), but still error.

Thanks.

arc_
September 13th, 2011, 13:01
0.5.7 release: http://www.mediafire.com/?mp2k257ipwyq88i

Contains a few more crashfixes.

Rastajan
October 21st, 2011, 11:54
Quote:
[Originally Posted by arc_;91047]0.5.7 release: http://www.mediafire.com/?mp2k257ipwyq88i

Contains a few more crashfixes.


Well done bro, very useful Tool! Keep it up dude

Greatz, Rastajan.

arc_
January 14th, 2012, 10:06
Another update (happy new year), 0.5.8: http://www.mediafire.com/?lunow30hx22wao1 (CRCETL updated as well)

This version counteracts updates in SmartAssembly 6.5.1, featuring improved locating of the methods responsible for handling string and import obfuscation.

In addition, it sets NumberOfRvasAndSizes to 16 in the unpacked file. Some packed files have it set to a lower number, causing the .NET data directory entry to become invalid in theory. This results in some tools not recognizing the file as a .NET binary.

user2
February 6th, 2012, 14:17
Awesome tool, man!!

dadaji
June 25th, 2012, 23:49
Just amazing!

wrunning
November 11th, 2012, 12:11
Hello! First of all, thank you for writing this tool.
Secondly, I'd like to ask if you could have a look at the latest SmartAssembly version and, if you got the time, maybe you can update dumbassembly for it.
I'm not 100% sure it's SmartAssembly's work, but I have come across a .net assembly packed with SmartAssembly 6.7.0.239 and if I unpack it with dumbassembly, the unpacked executable does not work well.
In case you need a link for the assembly in question, it is publicly available here: http://updates.buddyauth.com/?dev=yerp .
Thank you in advance!

Silkut
November 21st, 2012, 15:30
wrunning > can you develop your problem and tell us what is wrong with the dump, and what did you tried to solve your problem ?

timeerror
July 30th, 2013, 10:02
please help me dumbassembly soft protec with version 6.7.2.44