A high-level1 VM is in many ways comparable to a scripting engine; however, with a few exceptions:
Note 1: The term high-level refers to the VM execution of bytecode which originates from the source code (the highest level), in contrast to Low-level VM Protection that uses a virtual machine to emulate a section (typically a single function) of low-level machine code.
A high-level VM is considered even more secure than a low-level one. This is because it has all the virtues of the low-level VM, but almost none of the problems. The high-level VM can execute a big program with many classes and functions as one single protected entity - not suffering from jumping in and out of protected areas. A high-level VM will also implement a much bigger and more sophisticated virtual machine.
The compiled bytecode is strongly protected by design, whereas the VM engine for executing the bytecode is not.
It is a key trait of this security scheme that strong protection can be accomplished without having to hide the machine code being executed. The VM binary contains no valuable secrets to be disclosed and the internal parts are constructed in a very general manner with no prior knowledge of the bytecode to execute. Seen from the crackers point of view, there is a big gap between the very basic nature of what the binary contains (revealed using statical analysis) and the complexity of what it does (using memory analysis and execution interpretation) while executing some bytecode.
One might think it is an easy task to crack a certain part of the native binary just like any other program, but the consequences can be very difficult to control as described in How to Attack and Defend. Furthermore the developer can implement a defensive programming strategy using bytecode to detect whether or not the VM binary has been altered (even the loaded image in memory).
The importance of this is threefold:
Using a high-level VM provides strong protection without having to hide the native instructions being executed.
Each VM can protect many classes and functions, that can't be attacked individually, requiring the cracker to invest a lot of time and effort for every application being attacked. Analysis will require complex interpretations of the memory events which makes the executed bytecode extremely difficult to understand and virtually impossible to change.
Furthermore there is an extremely low risk of failing badly as the VM can be unique for each project, and the produced bytecode may differ for every compilation. The interpretation of the memory events may also depend on variable factors, which the programmer can control and even randomize on each execution.
Finally the level of security for the native binary can be raised simply by implementing a defensive programming strategy for the source being compiled into bytecode.
Ergo: The cost for the cracker is very high.