4

Ethereum's Solidity documentation states:

The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum. It is not only sandboxed but actually completely isolated, which means that code running inside the EVM has no access to network, filesystem or other processes.

Is limiting access to network, filesystem, and other processes enough to prevent a Turing complete language from sandbox escape?

Deer Hunter
  • 5,297
  • 5
  • 33
  • 50
Steve Ellis
  • 215
  • 1
  • 4
  • 1
    For the uninitiated Ethereum is similar to Bitcoin, as a blockchain, but the internal programming language is Turing complete and has more support for storing state. Miners run other people's code, and commit the results as transactions to the global state. Miners are paid for the amount of computation a transaction they've committed required. A simplification would be to call it a cross of AWS Lambda and Bitcoin. – Steve Ellis Mar 22 '16 at 16:38
  • 1
    It's not about completeness but about bugs. Some bugs will allow escape. – Deer Hunter Mar 22 '16 at 17:29
  • @DeerHunter so if there are implementations written in multiple languages, it would be less likely to affect people across implementations? – Steve Ellis Mar 22 '16 at 18:55
  • not really. There will be a dominant implementation. – Deer Hunter Mar 22 '16 at 19:12

2 Answers2

4

Escape from sandboxes is done not on languages but on implementations. I have seen the following VM implementations:

Each implementation relies on the correctness of the compiler/interpreter and the correctness of the implementation itself. An attacker would want to escape from the Ethereum VM and to achieve this he'd have to exploit a combination of bugs in the compiler/interpreter/standard libraries and in the implementation itself. While a difficult task, it's by no means impossible (most likely, involving zero-days). I'd say libethereum is the most vulnerable one being implemented in C++...

In a few years, as Ethereum gains acceptance, there will come out a dominant implementation which will make the attacker's task easier (webthree-umbrella has the canonical Solidity compiler).

To provide defense-in-depth from such attacks, one would recommend running Ethereum sandboxes inside general-purpose VMs. Even then, the main method of preventing attacks will IMHO rely on checking contracts manually:

  1. Go to EtherChain.org, get the contract's source code.
  2. Get the bytecode you're being fed. Disassemble it yourself.
  3. Compile the code and compare with the received bytecode. (See this question at Ethereum SE for details - turns out to be a PITA).
  4. ???
  5. Avoid losses.
  6. Start the hue and cry if you see a malicious contract...

Note: existence of two Turing-complete languages (Solidity and Serpent) and dozens of compiler versions (60+ Solidity legacy incarnations) at this stage makes it more difficult for the defender to detect malicious contracts.

Deer Hunter
  • 5,297
  • 5
  • 33
  • 50
2

No, no it is not and it's quite complex to prevent.

Sandbox's usually hook internal APIs and execute code at an lowest level in ring 3. Let's talk about file API to begin with. If your familiar with lower level programming languages such as C, C++ and so on you'll know about the ReadFile API. So, let's check out ReadFile in an disassembly.

ReadFile is situated as in the EAT (Export Address table) in Kernel32.

KERNEL32.ReadFile - FF 25 8803D674        - jmp dword ptr [KERNEL32.PssWalkSnapshot+9588] { ->KERNELBASE.ReadFile }

So, it jump to KernelBase.dll to an ReadFile. Don't worry about not understanding all the opcodes but just be interested in the CALLs.

KERNELBASE.ReadFile - 8B FF                 - mov edi,edi
KERNELBASE.ReadFile+2- 55                    - push ebp
KERNELBASE.ReadFile+3- 8B EC                 - mov ebp,esp
KERNELBASE.ReadFile+5- 6A FE                 - push -02 { 254 }
KERNELBASE.ReadFile+7- 68 A806D576           - push KERNELBASE.ReadFile+B8 { [FFFFFFFE] }
KERNELBASE.ReadFile+C- 68 5098D576           - push KERNELBASE.OutputDebugStringA+C0 { [8B55FF8B] }
KERNELBASE.ReadFile+11- 64 A1 00000000        - mov eax,fs:[00000000] { 0 }
KERNELBASE.ReadFile+17- 50                    - push eax
KERNELBASE.ReadFile+18- 83 EC 18              - sub esp,18 { 24 }
KERNELBASE.ReadFile+1B- 53                    - push ebx
KERNELBASE.ReadFile+1C- 56                    - push esi
KERNELBASE.ReadFile+1D- 57                    - push edi
KERNELBASE.ReadFile+1E- A1 683BE076           - mov eax,[KERNELBASE.dll+C3B68] { [1EB677D9] }
KERNELBASE.ReadFile+23- 31 45 F8              - xor [ebp-08],eax
KERNELBASE.ReadFile+26- 33 C5                 - xor eax,ebp
KERNELBASE.ReadFile+28- 50                    - push eax
KERNELBASE.ReadFile+29- 8D 45 F0              - lea eax,[ebp-10]
KERNELBASE.ReadFile+2C- 64 A3 00000000        - mov fs:[00000000],eax { 0 }
KERNELBASE.ReadFile+32- 89 65 E8              - mov [ebp-18],esp
KERNELBASE.ReadFile+35- C7 45 E0 00000000     - mov [ebp-20],00000000 { 0 }
KERNELBASE.ReadFile+3C- C7 45 E4 00000000     - mov [ebp-1C],00000000 { 0 }
KERNELBASE.ReadFile+43- 8B 75 14              - mov esi,[ebp+14]
KERNELBASE.ReadFile+46- 85 F6                 - test esi,esi
KERNELBASE.ReadFile+48- 74 06                 - je KERNELBASE.ReadFile+50
KERNELBASE.ReadFile+4A- C7 06 00000000        - mov [esi],00000000 { 0 }
KERNELBASE.ReadFile+50- 8B 5D 08              - mov ebx,[ebp+08]
KERNELBASE.ReadFile+53- 83 FB F4              - cmp ebx,-0C { 244 }
KERNELBASE.ReadFile+56- 0F83 60D30400         - jae KERNELBASE.InterlockedExchangeAdd+528C
KERNELBASE.ReadFile+5C- 8B 7D 18              - mov edi,[ebp+18]
KERNELBASE.ReadFile+5F- 85 FF                 - test edi,edi
KERNELBASE.ReadFile+61- 75 71                 - jne KERNELBASE.ReadFile+D4
KERNELBASE.ReadFile+63- 57                    - push edi
KERNELBASE.ReadFile+64- 57                    - push edi
KERNELBASE.ReadFile+65- FF 75 10              - push [ebp+10]
KERNELBASE.ReadFile+68- FF 75 0C              - push [ebp+0C]
KERNELBASE.ReadFile+6B- 8D 45 E0              - lea eax,[ebp-20]
KERNELBASE.ReadFile+6E- 50                    - push eax
KERNELBASE.ReadFile+6F- 57                    - push edi
KERNELBASE.ReadFile+70- 57                    - push edi
KERNELBASE.ReadFile+71- 57                    - push edi
KERNELBASE.ReadFile+72- 53                    - push ebx
KERNELBASE.ReadFile+73- FF 15 5C66E076        - call dword ptr [KERNELBASE.dll+C665C] { ->ntdll.NtReadFile }
KERNELBASE.ReadFile+79- 8B C8                 - mov ecx,eax
KERNELBASE.ReadFile+7B- 81 F9 03010000        - cmp ecx,00000103 { 259 }
KERNELBASE.ReadFile+81- 0F84 B9D30400         - je KERNELBASE.InterlockedExchangeAdd+5310
KERNELBASE.ReadFile+87- 85 C9                 - test ecx,ecx
KERNELBASE.ReadFile+89- 0F88 380A0000         - js KERNELBASE.GetModuleHandleExW+277
KERNELBASE.ReadFile+8F- 85 F6                 - test esi,esi
KERNELBASE.ReadFile+91- 74 05                 - je KERNELBASE.ReadFile+98
KERNELBASE.ReadFile+93- 8B 45 E4              - mov eax,[ebp-1C]
KERNELBASE.ReadFile+96- 89 06                 - mov [esi],eax
KERNELBASE.ReadFile+98- B8 01000000           - mov eax,00000001 { 1 }
KERNELBASE.ReadFile+9D- 8B 4D F0              - mov ecx,[ebp-10]
KERNELBASE.ReadFile+A0- 64 89 0D 00000000     - mov fs:[00000000],ecx { 0 }
KERNELBASE.ReadFile+A7- 59                    - pop ecx
KERNELBASE.ReadFile+A8- 5F                    - pop edi
KERNELBASE.ReadFile+A9- 5E                    - pop esi
KERNELBASE.ReadFile+AA- 5B                    - pop ebx
KERNELBASE.ReadFile+AB- 8B E5                 - mov esp,ebp
KERNELBASE.ReadFile+AD- 5D                    - pop ebp
KERNELBASE.ReadFile+AE- C2 1400               - ret 0014 { 20 }

So, the next level API which is lower down is NtReadFile which is located in ntdll.dll.

ntdll.NtReadFile - B8 05001A00           - mov eax,001A0005 { [0] }
ntdll.ZwReadFile+5- 64 FF 15 C0000000     - call fs:[000000C0]
ntdll.ZwReadFile+C- C2 2400               - ret 0024 { 36 }
ntdll.ZwReadFile+F- 90                    - nop 

This is an special type of call named syscall which then goes to the SSDT (System Service Descriptor Table) which has the low level APIs which situated in ring 0 which is not accessible directly via ring 3 other than syscall which are exposed.

So, sandboxes can use an range of hooking methods but most common are:

  • Memory detours (JMP, PUSH RET and so on)
  • Hooking IAT tables
  • VEH (Unlikely, since big impact on performance)

So, just to know what going on is we would hook the NtReadFile which would jump to our code and we have complete control over what is executed. For example, let's say your going to filter out any file named SteveEnix. In our trampoline function (Where the NtReadFile has been hooked and jumped to) we can read the parameters and decide to call it or not. So, it will look like this:

NTSYSAPI NTSTATUS NTAPI t_NtReadFile(
                  IN HANDLE               FileHandle,
                  IN HANDLE               Event OPTIONAL,
                  IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
                  IN PVOID                ApcContext OPTIONAL,
                  OUT PIO_STATUS_BLOCK    IoStatusBlock,
                  OUT PVOID               Buffer,
                  IN ULONG                Length,
                  IN PLARGE_INTEGER       ByteOffset OPTIONAL,
                  IN PULONG               Key OPTIONAL )
{

// Your check on buffer to filter files or whatever you want and return an error or call the original function

}

If the internal application has access to using VirtualProtect API or the memory region is read and write then these hooks can easily be removed.

However, if you step down an level even deeper to the SSDT which on x64 has PatchGuard to prevent these hooks so generally sandboxes don't touch SSDT then you would need an driver to be loaded into system to be able to unpatch the SSDT or patch it with your own hook.

techraf
  • 9,141
  • 11
  • 44
  • 62
Paul
  • 1,552
  • 11
  • 11
  • Just to be sure - are you talking about VM escapes in general, or about Ethereum contract sandboxes in particular? – Deer Hunter Mar 23 '16 at 09:50
  • I was just talking in general but I'd assume Ethereum would follow into same criteria. This can easily be tested by using HookShark to detect if any hooks are made in r3. – Paul Mar 23 '16 at 10:08