Software attacks Software attacks .NET security
Software attacks
Software attacks
.NET security
Software attacks
The problem
• Buffer owerflow: one silly but very common bug
• Can allow a determined attacker to obtain control
• Components: building an application from third-party DLLs makes the problem even worse:– the attacker has lots of time to discover the
various gaps to be exploited in each DLL in use
– a bogus/malicious component could be used
Software attacks
What we want
A user that runs a process constructed from components, wants to be sure that he is protected from malicious software.
=> Prevent buffer overflow with verification of managed code
=> Prevent execution of malicious software: Code access security (CAS)
Software attacks
.NET
• CLR: Common Language Runtime• CTS: Common Type System• IL: Intermediate Language• Metadata:Self info about classes• Assembly: Code unit• Manifest: Description of assembly
Software attacks
...
C#
Common Language Runtime
• At the heart of the .NET platform is a common language runtime engine and a base framework
VB
VC++
Common Language Runtime
JScript
GC
JITLoaderVerifier
...Native Code
Base framework classes
Data and XML classes
MSIL
XML Web Services
WebForms
WindowsForms
Software attacks
MSIL• Every .NET language is not compiled to
machine code targeted to any specific CPU. Instead, the code generated by the compiler is a Microsoft intermediate language (MSIL)
• MSIL is much higher level than most CPU machine languages:– It understands object types– Has instructions that create and initialize objects– Calls to virtual methods on objects– Manipulate array elements directly– Instructions that raise and catch exceptions
Software attacks
JITSourcecode
ClassLibraries
(IL &Metadata)
EXE/DLL(IL &
Metadata)
ManagedNativeCode
Compiler
Class Loader
JIT compilerW/ verification
Execution
Security checks
XMLSecuritypolicies
Stack walkPermission
demand
Call to anuncompiled
method
MSIL is not a bytecode: is JITed. The JIT compiler compiles methods on the fly, verifying code in the process
Software attacks
JITfoo
call(…)foo.A
foo.B
jmpjmp
barcall(…)
bar.A
bar.B
jmpcall
Managed Code Blocks
push ebp; mov ebp, esp; …push ebp; … call bar.A
push ebp; … call bar.B
UniversalJIT Thunk
JIT compilerW/ verification
The stub code directs program execution to the JIT compiler.
When a method is called:
Once the JIT compiler has compiled the MSIL, the method's stub is replaced with the address of the compiled code.
From now, when this method is called, native code will execute
Software attacks
Metadata• The Microsoft .NET platform uses
metadata and assemblies to store information about components:– enabling cross-language programming – locate and load class types in the file– resolve method calls and field references– resolving the infamous DLL Hell problem
(SxS execution)– easy linking and loading of assemblies – easy versioning – reflection.
Software attacks
Metadata
Assembly
ModuleModule list Type list
Global methods
Foo
main
Methods
Fields
Properties
Events
MyFoo
Params
int a
The metadata hierarchy
class Foo { public void MyFoo() { ... }
}
public static void Main(string[] args) {...}
Software attacks
Assembly & Manifest• Metadata stored in assembly• Think of an assembly as a logical EXE or DLL• A manifest is a section of the assembly
which makes it self-describing• Each assembly has a 4-part name
– Friendly name– Version number– Culture setting– Public key (or public key token)
The public key forms the so called Strong Name of the assembly
MyLibrary, Version=1.0.24.0, Culture=neutral, PublicKeyToken=29989D7A39ACF230
BobsLibrary, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null
Metadata
IL
Assembly Manifest
list of dependent assemblies
PE File Format
4-part name
Resources
Software attacksPhase I: Verification and
validation
• Even before checking if an assembly has the necessary permission to access a resource, it has to pass a set of rigorous checks
• Each related set of checks is called a validation
• Three major validation are made:– PE file format validation– Metadata validation– IL validation
• These check ensure that later security checks on assemblies are valid
Software attacks
PE file format validation
• Assemblies are PE files• They contain a special slot (pointing to an
header with relevant addresses) + an unique x86 asm instruction (for old systems)– Donut virus
• Time of check: right after CLR initialization– All relevant addresses (Metadata location, IL
location, etc.) point inside the PE file– Ensures assembly isolation
Software attacks
Metadata validation
• As we saw, metadata is a set of linked tables
• Metadata is used for many crucial functions (recall for example load of types)– Structural validation (the table structure must
adhere some standards)– Ex: field table pointing to a buffer (data section)– Semantic validation (rules of the CLR: type
invariants, type relatinons)– Ex: subclass B of A cannot be A superclass
• They are done before metadata is used (i.e. before IL validation)
Software attacks
IL validation & verification
• As we saw, IL is not executed but compiled on a method basis
• Before compiling a method, IL is validated• Then, it is verified
– Native code has direct memory access and can subvert isolation
– Type contracts can be broken (C-style casts, access to private members…)
• JIT checks that IL is not used in a way it can generate unsafe native code
Software attacks
IL validation
• Bytes in the IL stream are all valid IL instructions
• All jumps are inside the same method
Software attacksIL verification: Ensuring
type safetyclass Account { private: long balance; }; void useAccount(Account* a) { // use pointer arithmetic to index // into the object wherever we like! ((long*)a)[1] = MAX_LONG; }
• By using pointers and unsafe casts, the attacker is able to access and modify private members of a class
• There is no runtime protection against this in unmanaged code.
• The CLR detects this sort of type system abuse during just-in-time (JIT) compilation (unsafe pointer arithmetic in this particular case) and raise an exception.
• A buffer overflow is another example of a type system violation: the CLR closes all of these holes by ensuring the type safety of all code
The CLR verify all code before running it
Software attacks
Ensuring type safetypublic SecurityInfo{ private bool fullyTrusted; public bool IsFullyTrusted() { return (fullyTrusted); } public static SecurityInfo GetSecurityInfo (Assembly a) {...} }
public SpoofSecurityInfo{ public bool fullyTrusted;}
Main() { SecurityInfo si = SecurityInfo.GetSecurityInfo(System.GetMyAssembly()); SpoofSecurityInfo sp = si; spoof.fullyTrusted = true; //do nasty things}
call class SecurityInfo SecurityInfo::GetSecurityInfo(class Assembly);stloc si //store the retval (top of stack) in sildloc si //put si on top of the stackstloc sp //put top of stack in sp TYPE UNSAFE!!
The previous code will not compile, however could be possible to write corresponding IL:
The JIT werification will not allow stloc sp to
execute!!
Software attacks
Ensuring type safetypublic SpoofSecurityInfo{ public bool fullyTrusted;}
Main() { SecurityInfo si = SecurityInfo.GetSecurityInfo(System.GetMyAssembly()); bool* pb = (si + 4); *pb = true; //do nasty things}
ldloc si //load si on top of stackldc.i4 4 //load 4 on top of stackadd //add the top 2 operandstloc pb //store top of stack back in pb UNSAFEldloc pb //load pb on top of stackldc.i4 1 //load true constant stind.i1 //*pb = true
The previous code will not compile, however could be possible to write corresponding IL:
The JIT verification will not allow stloc sp to execute!!
Type unsafe IL breaks the CLR type system
Software attacks
Type safety• The common language runtime ensures type
safety by combining strong typing in the metadata with strong typing in the MSIL (local variables and stack slots).
• This permit to automatically verifying the type safety of programs written in MSIL.
• By default, all code compiled by the JIT is validated before beign JITed.– The CLR does not allow any assembly with type unsafe IL to
run, with the exception of Full Trusted assemblies
• Not all safe code may be verifiable with existing technology => but code emitted by .NET compilers is always verifiable
Software attacks
Summary
PE file validation
Metadata validation
don’t run malicious code before validation
IL validation
correct metadata (no bogus methods/classes)
CAS
type contracts not broken
IL verification
file not corrupted
Software attacks
Security models
• Traditional OS security provides isolation and access control based on user accounts.
• This is useful for organizations, but assumes that all code is equally trustworthy.
• This assumption is broken by the increasing reliance on mobile code (Web services and components, e-mail attachments, …)
• There is a need for more granular control of application behavior.
Software attacks
User-based security model
• Programs have the same rights of the user who executes them, so they can do all the things a user can do– Often users didn’t even know what a program
can do!
• Too many users run as administrator, too many programs require admin settings to install and run– Even when a program is executed with
standard rights, the same concept holds!
Software attacksUser-based security model
exampleall the program used by these users have access to this folder
Windows explorer may have the right to access %WINDIR%\system32
But letting a spyware-filled-P2P-client access them is not very wise…
Word may have the right to access my documents folder
Software attacks
Role-based security• Evolution of User-based security• Often used in business applications/3-tier
applications (business logic)• Windows 2000 introduces role-based security
for to COM+ components, .NET and Web services
• You can:– define roles – define the tasks those roles can perform– nest roles to inherit characteristics from other roles– define application groups– use scripts to modify permissions dynamically
Software attacksRole-based security
example• A system to manage a company library• Make a list of the sensitive operations:
– Read catalog – Place hold (for self or another) – Check out book – Check in book – Add book to inventory – Remove book from inventory – Read patron history (for self or another)
• Some operations sensitive to information that you'll only have at run time (read a patron's history => the user is trying to access his own history or that of someone else?)
Software attacks
Roles and tasks
Browse catalogPlace holdRead history
Browse catalogPlace holdRead historyCheck out bookCheck in book
Browse catalogPlace holdRead historyCheck out bookCheck in bookAdd book to inventoryRemove book from inventory
Customer Clerk Manager
Software attacksCode-based security
model
• Authenticate code, not the user running it => Code identity
• Authorize code, not the user => security policies an permission sets
• Enforce code authorization => CAS
Software attacks
Evidences
• Code identity is based on evidences• Using evidences, security policy can control the
privileges granted to an application• Eight types of evidences:
– ApplicationDirectory: From what directory was this assembly executed?
– Site: From what site was this assembly downloaded?– URL: Which is the URL of origin of this assembly?– Zone: From what zone was this assembly obtained?– Hash: What’s the hash of this assembly?– StrongName: What's the strong name of this assembly?– Publisher: Who signed this assembly?
Software attacks
Code Identity
• There are only two ways for execute code: – through the class loader – through interoperability services
• Both of these are services provided by the CLR and are part of the security perimeter
• The class loader maintains information about the source of every class that it loads
• The class loader provides some of the evidence upon which code identity is based
Software attacks
Strong names
Metadata
Intermediate Language
Assembly ManifestFriendly Name: MyLibrary
Version: 1.0.24.0
Culture:neutral
Public Key: (full 128-byte value)
Reference list of dependent assemblies
Digital Signature
// AssemblyInfo.csusing System.Reflection;
[assembly: AssemblyKeyFile(@"..\..\AcmeCorp.snk")]
DLL tampering and replacement creates security hole
Public key token + digital signature (= Strong Name) guarantees tamper proof
Digital signature built from private key and assembly file hash
CLR authenticates digital signature with strong name verification
Software attacks
Strong names• sn.exe -k PublicPrivateKeyFile.snk • The compiler digitally signs an assembly:
– calculates a cryptographic digest (hash) of the assembly– encrypts the compile-time digest using the 1.024-bit private
key from your public-private key pair file. – compiler stores this encrypted compile-time digest into
assembly
• When the .NET loader loads an assembly: – calculates a cryptographic digest (hash) of the assembly– extracts the encrypted compile-time digest from the assembly– extracts the public key for the assembly– uses the public key to decrypt the compile-time digest– compares the runtime digest to the decrypted compile-time
digest– if they are not equal, something or someone has modified the
it since you compiled it; the runtime fails to load the assembly
Software attacks
Strong names
• Strong Names during developement cycle:– Build the assembly with delay signing enabled– Enable strong name verification skipping for the
assembly (sn.exe -Vr)– Debug and test the assembly– Obfuscate the assembly– Debug and test the obfuscated version– Delay sign the assembly (sn.exe -R).
Software attacks
If the strong name is not present, or is not correct…
…the main method is not fired!
StrongNameSignatureVerification
__int32 STDMETHODCALLTYPE _CorExeMain2(PBYTE pUnmappedPE, DWORD cUnmappedPE, LPWSTR pImageNameIn, LPWSTR pLoadersFileName, LPWSTR pCmdLine) { PEFile *pFile = NULL; HRESULT hr = E_FAIL;
if (!StrongNameSignatureVerification(pImageNameIn, SN_INFLAG_INSTALL|SN_INFLAG_ALL_ACCESS|SN_INFLAG_RUNTIME, NULL) && StrongNameErrorInfo() != (DWORD) CORSEC_E_MISSING_STRONGNAME) { LOG((LF_ALL, LL_INFO10, "Program exiting due to strong name verification failure\n")); return -1; }
//...
if (SUCCEEDED(hr)) { hr = SystemDomain::ExecuteMainMethod(pFile, pImageNameIn); }
//exit EEShutDown(FALSE); SafeExitProcess(GetLatchedExitCode()); return (GetLatchedExitCode());}
Software attacksAuthorization &
Enforcement
• They are highly coupled• Security decisions
– Coded in software (dynamic, hardcoded)
– Declared in assemblies (declarative security)
– Inserted in Security Policy (static, configurable)
Software attacks
Setting permission
• The CLR discover permissions by gathering evidences from the assembly and giving the answers to the security policy manager, which converts them into permissions.
• However, often we may want to restrict the set of permissions based on policy at runtime. Let’s see an example.
Software attacks
Runtime security request
• Imagine an highly trusted assemblies calling to a user-provided script. The assembly invoking the script might want to restrict the effective permissions before making the call
• Need for a dynamic permission set management: – PermissionSet– Demand– Deny and RevertDeny, – PermitOnly and RevertPermitOnly – Assert
Software attacks
Demanding permission
using System.Security.Permissions; public class MyFileAccessor { public MyFileAccessor(String path, bool readOnly) { path = MakeFullPath(path); // helper fcn FileIOPermissionAccess desiredAccess = readOnly ? FileIOPermissionAccess.Read : FileIOPermissionAccess.AllAccess; FileIOPermission p = new FileIOPermission(desiredAccess, path); p.Demand(); // ••• open the file } // ••• }
•Create an object that represents the permission (in this case, access to a file)
•Demand that permission. •The system look at the permissions of the caller• If the caller doesn't have this particular permission, Demand
throws a SecurityException.
Software attacks
Luring attack
Mscorlib.dll
FileStream
Assembly2.dll
FileReader
Assembly4
BadComponent
Assembly3.exe
MyComponent
Local drive
Local drive
InternetIf Assembly2 is fully trusted, Assembly4 could use it to circumvent protection
Making someone other do the dirty work for you.
Rather than forcing each middleman component to do its own access checks to avoid this situation, the CLR uses a technique called Stack Walk
Software attacks
Finds that the Internet permission set does not contain FileIO permission,
Internet.Contains(FileIO) = falseFullTrust.Contains(FileIO) = true
Finds that FileReader::read() has this permission
Stack walkThe CLR verifies that every caller in the call chain has the permissions demanded.
Call Stack Grant Requires===================================================================================
Assembly4.exe
BadComponent::Main()
Assembly4.exe!BadComponent::Main() Internet Execution
Assembly2.dll
FileReader::read()
Assembly2.dll!FileReader::read() FullTrust Environment, FileIO
Mscorlib.dll
FileStream::.ctor()
mscorlib.dll!System.IO.FileStream::.ctor() FullTrust FileIO
Raise a SecurityException.
The FileStream constructor does its demand for FileIOPermission
Software attacks
Stack walkWhen the local component Assembly3.exe
uses FileReader to read a file, it will be given access because all asseblies in the call chain are local.
However, when BadComponent attempts to use FileReader, FileStream calls Demand, the CLR walks up the stack and discover that one of the callers in the call chain don't have the requisite permissions: Demand will throw a SecurityException.
Software attacks
If Calculate tries to:-access files anywhere in the two sensitive directories, -access an environment variable,-access the contents of the clipboard,(or if any components that Calculate uses internally tries to do any of these things)
when the CLR walks the stack to check access, it notes that a stack frame denies these permissions and so denies the request
Setting permissions (Deny)
using System.Security.Permissions; using System.Security;
public interface IUserPluggableAlgorithm { int Calculate(int a, int b); }
// code snippet that uses the algorithm PermissionSet ps = new PermissionSet(); ps.AddPermission(new FileIOPermission( FileIOPermissionAccess.AllAccess, "c:\\sensitiveStuff")); ps.AddPermission(new FileIOPermission( FileIOPermissionAccess.AllAccess, "c:\\moreSensitiveStuff")); ps.AddPermission(new EnvironmentPermission( PermissionState.Unrestricted)); ps.AddPermission(new UIPermission( PermissionState.Unrestricted)); ps.Deny(); // deny these permissions
int result = a.Calculate(42, 64); CodeAccessPermission.RevertDeny(); // ••• continue doing work
Recall the case of a trusted assembly calling to a user-provided script.This code places extra restrictions in the current stack frame.
Software attacks
Denies.Contains(FileIO) = true
Finds that this permission is in the Deny set
Permission set and Stack walk
Call Stack Grant Requires Denies========================================================================================
MyApp.exe
MyApp::Main()
MyApp.exe!MyApp::Main() FullTrust -
custom.dll
IUserPluggableAlgorithm::calculate()
custom.dll! IUserPluggableAlgorithm::calculate() FullTrust - FileIO, ...
Mscorlib.dll
FileStream::.ctor()
mscorlib.dll!System.IO.FileStream::.ctor() FullTrust FileIO FileIO, ...
Raise a SecurityException.
The FileStream constructor does its demand for FileIOPermission
ps.Deny()
FileIO, ...
Software attacks
Assert
using System; using System.IO; public class ErrorLogger { public static void Log(string s) { const string fname = "c:\\temp\\errlog.txt"; FileStream logStream = new FileStream(fname, FileMode.Append, FileAccess.Write); StreamWriter logWriter = new StreamWriter(logStream); logWriter.Write(s); logWriter.Close(); logStream.Close(); } }
Suppose to have the following class
ErrorLogger is safer than MyFileAccessor when used by arbitrary components.But the stack-walking mechanism doesn't know this: when the FileStream constructor demands permissions of its callers, the demand will fail unless every caller in the chain has the FileIOPermission demanded.
Software attacks
Assert
• If ErrorLogger class was installed locally, and its assembly is granted full access to the file system by the code access security policy.
• But this class was designed to provide service to other assemblies, some of which aren't granted permissions to write to the local file system!
• This impediment can be removed by having ErrorLogger assert its own authority to write to the log file.
Software attacks
When the stack walk reaches that stack frame, it will consider the asserted permissions satisfied.
Assert a file IO permission before using the FileStream.
Assert
public class ErrorLogger2 { public static void Log(String s) { const String fname = "c:\\temp\\errlog.txt"; FileIOPermission p = new FileIOPermission( FileIOPermissionAccess.Append, fname); p.Assert(); FileStream logStream = new FileStream(fname, FileMode.Append, FileAccess.Write); StreamWriter logWriter = new StreamWriter(logStream); //... } }
You can only assert permissions that your assembly has been granted!
ErrorLogger installed as a local component is granted full access to the FileIO.
Software attacks
Assert and Stack walk
Now, when the FileStream constructor demands FileIO permission it is going to check Log(), find that it has permission, then hit the wall that the Assert put up, and goes no further up the stack. (FOR THAT permission!)
Call Stack Grant Requires===================================================================================mscorlib.dll!System.IO.FileStream::.ctor() FullTrust FileIOErrorLogger2::Log() FullTrust Environment, FileIO-------------------------Assert(Environment, FileIO)-------------------------------Assembly4.exe!BadComponent::Main() Internet Execution
Since Log() is opening a very specific file, and since I've done a full security review on its code, I've decided to enable this scenario.
I can do that by calling Assert() for Environment and FileIO permission in Log(), which will create a situation like the following:
Stack call Stack walk
Software attacks
Declarative Security• This powerful mechanism allows you to insert
code access security checks into your code by annotating classes, fields, or methods.
• The declarations are encoded in the assembly metadata and are enforced by the .NET security system.
[FileIOPermission(SecurityAction.Demand, UnmanagedCode = true)] void aMethod() { ... }
Software attacks
Security policy
• Permissions are assigned on a per-assembly basis by the Class Loader in the CLR. The steps are:– Gather evidence– Present evidence to security policy – discover assigned permission set
• It is possible to fine-tune permission set based on assembly requirements
Software attacks
Evaluating Security Policy
• To discover the permission set assigned to that assembly, a graph is traversed.
• The root node is really just a starting place for the traversal:– it matches all code – by default refers to the permission set named Nothing,
which contains no permissions.
• The traversal of the graph is governed by a couple of rules. – if a parent node doesn't match, none of its children are
tested for matches – each node can also have the Exclusive attribute, that
influence the traversal: only the permission set for that particular node will be used
Software attacks
Evaluating Security Policy
Zone:MyComputer
ps:permission1
Zone:Internet
ps:permission2
URL:http://.com/*
ps:permission3
Publisher:Microsoft
ps:permission4
Publisher:ACME Corp
ps:permission3
Publisher:ACME Corp
ps:permission4
All code
ps:nothing
Resulting permission set
permission1
permission3
Software attacks
Evaluating Security Policy
• There is more than one graph: – a machine policy level, – a user policy level, – an application domain policy level,
• They are evaluated in that order. • The resulting set is the
intersection of the sets discovered during the traversal of the three graphs
Software attacks
AppDomains• Last enforcement technique, independent from
permissions• An AppDomain is conceptually similar to a process,
but has a much lighter weight. • When a process hosts the CLR, it always has a
default AppDomain where all managed types are loaded.
• By creating multiple AppDomains, types in one AppDomain cannot liberaly access types in any other AppDomains. – Sharing references between AppDomains requires the use
of remoting. If A and B are in separate AppDomains and A want to access B objects, B has to pass a reference to A.
– A can't poke around without being invited.
Software attacks
Attacks Against CAS • Misuse of Assert • Unmanaged code security permissions:
– Permission to call unmanaged code, it can bypass all CAS
– no permission to the file system, but allowed to call into unmanaged code => call to the Win32 API
– No protection if an attacker executes unmanaged code as admin
• Permission escalation by change of location:– Assembly used from Internet => low permissions – Convince the victim to install a copy of the
assembly on his machine => permission escalation
Software attacks
Attacks Against CAS
• Fully trusted assembly– Can cross AppDomain boundaries– Can pass over IStackWalk.Deny with new PermissionSet( PermissionState.Unrestricted).Assert();
– Can access private members (via Reflection) by turning off JIT checks SecurityManager.SecurityEnabled = false;
• Remember the principle of least privilege, and design and code with it in mind.
Software attacks
The old new thing
• How an .exe assembly get turned into code? • _CorExeMain2 starts the process
– verifies the signature on the assembly (StrongNameSignatureVerification)
– gets the execution engine (EE) ready (CoInitializeEE )
• EEStartup starts up all supporting services - garbage collector,- JIT- thread pool manager.
– SystemDomain::ExecuteMainMethod makes the Class Loder load an JIT the application main method
Software attacks
The old new thing__int32 STDMETHODCALLTYPE _CorExeMain2(PBYTE pUnmappedPE, DWORD cUnmappedPE, LPWSTR pImageNameIn, LPWSTR pLoadersFileName, LPWSTR pCmdLine) { PEFile *pFile = NULL; HRESULT hr = E_FAIL;
if (!StrongNameSignatureVerification(pImageNameIn, SN_INFLAG_INSTALL|SN_INFLAG_ALL_ACCESS|SN_INFLAG_RUNTIME, NULL) && StrongNameErrorInfo() != (DWORD) CORSEC_E_MISSING_STRONGNAME) { LOG((LF_ALL, LL_INFO10, "Program exiting due to strong name verification failure\n")); return -1; }
//...
if (SUCCEEDED(hr)) { hr = SystemDomain::ExecuteMainMethod(pFile, pImageNameIn); }
//exit EEShutDown(FALSE); SafeExitProcess(GetLatchedExitCode()); return (GetLatchedExitCode());}
Remember?
Software attacks
The old new thing
• This is unmanaged, native code. Here we are going to attack: API patching of – RegQueryValueEx– RegOpenKeyEx
• Recall delay signing and sn.exe -Vr• Insert via API patch a bogus voice in HKLM\
Software\Microsoft\StrongName\Verification
• This makes the CLR skip StrongName verification for our assembly => tampering
• Microsoft did a number of things to avoid this situation, but a way can be found
Software attacks