162 lines
4.7 KiB
Markdown
162 lines
4.7 KiB
Markdown
Looking in to the mirror
|
|
============================
|
|
|
|
## Introduction
|
|
|
|
This challenge was about employing reflection techniques to modify and view internal structures of the virtual machine.
|
|
|
|
Pharo is a Smalltalk environment, most widely known for its metaprogramming and reflection capabilities. Everything in Smalltalk is an object, and every object is an instance of a class, even a class itself is an instance of something (a metaclass).
|
|
|
|
The syntax of a Smalltalk program is relatively simple, the most important part is how to send messages (in other object oriented languages also called: invoking a method).
|
|
|
|
Lets say we want to send a message `newWithName` to a class `Person`, this can be achieved as follows:
|
|
|
|
```
|
|
p := Person newWithName:'Bram'.
|
|
```
|
|
|
|
Now the variable `p` contains a reference to an instance of the class `Person`.
|
|
|
|
Messages that can be send to classes themselves are defined on the metaclass of a class. In this case the metaclass is the `Person class` class. We can obtain a reference to a method itself by using the `>>` (lookup) operator:
|
|
|
|
```
|
|
(Person class)>>#newWithName
|
|
```
|
|
|
|
## The Challenge
|
|
|
|
The challenge consisted of connecting to a remote endpoint using netcat, which provided a Smalltalk REPL where Smalltalk expressions
|
|
could be executed.
|
|
|
|
The Pharo VM contained serveral user-defined classes:
|
|
|
|
* Challenge
|
|
* Squeak
|
|
|
|
The REPL suggested that you could obtain the flag by using:
|
|
|
|
```
|
|
Challenge new flag
|
|
```
|
|
|
|
however, when trying to do this, the REPL would yield that `only a Squeak could to that`, which means that the method `flag` could only be called from an instance of the class `Squeak`.
|
|
|
|
Luckily, the `Squeak` class contained a method named `fetchFlag`. So lets try to do this:
|
|
|
|
```
|
|
Squeak new fetchFlag.
|
|
```
|
|
|
|
This however, yields an initialisation error, suggesting that no instance of a `Squeak` could be created.
|
|
|
|
## Solution(s)
|
|
|
|
As Pharo has many ways to interact with classes and objects, the challenge has many solutions. Here, I will only present two possible solutions. One involves inspecting the method dictionnary of the `Challenge` class, the other involves overwriting the behaviour in the `Squeak` class that prevents its instance creation.
|
|
|
|
### Inspecting the Challenge class
|
|
|
|
Its is clear that we need to obtain some representation of the source of the `flag` method of the `Challenge` class.
|
|
|
|
We can try to obtain a reference to this method by using the lookup operator:
|
|
|
|
```
|
|
Challenge>>#flag
|
|
```
|
|
|
|
however, this results in a `LookupError`, the `Challenge` class seems to be too well protected.
|
|
|
|
However, Pharo has many ways to get references to methods, so we can try another one:
|
|
|
|
```
|
|
Challenge methodDict at:#flag
|
|
```
|
|
|
|
This seems to work well. Now we only need to print its source code.
|
|
|
|
```
|
|
(Challenge methodDict at:#flag) ast nodesDo: [:n | Transcript show: n; cr. ]
|
|
```
|
|
|
|
Which yields:
|
|
|
|
```
|
|
RBTemporaryNode(caller)
|
|
RBAssignmentNode(caller := thisContext client)
|
|
RBMessageNode(thisContext client)
|
|
RBThisContextNode(thisContext)
|
|
RBTemporaryNode(caller)
|
|
RBMessagifTrue: [ ^ 'IG{ImSoMetaEvenThisAcronym}' ])
|
|
RBMessageNode(caller class = Squeak)
|
|
RBMessageNode(caller class)
|
|
RBTemporaryNode(caller)
|
|
RBGlobalNode(Squeak)
|
|
RBBlockNode([ ^ 'IG{ImSoMetaEvenThisAcronym}' ])
|
|
RBSequenceNode(^ 'IG{ImSoMetaEvenThisAcronym}')
|
|
RBReturnNode(^ 'IG{ImSoMetaEvenThisAcronym}')
|
|
RBLiteralValueNode('IG{ImSoMetaEvenThisAcronym}')
|
|
RBReturnNode(^ 'Only a Squeak can do that (see Squeak>>#fetchFlag)')
|
|
RBLiteralValueNode('Only a Squeak can do that (see Squeak>>#fetchFlag)')
|
|
|
|
```
|
|
|
|
And there is our flag.
|
|
|
|
### Recompiling the Squeak class
|
|
|
|
Another way to solve this challenge is to allow the `Squeak` class to be initialised.
|
|
|
|
At first, we are not entirely sure why the `Squeak` class fails to initialise.
|
|
It could be that its `initialize` method has been overridden.
|
|
|
|
We can find out which methods have been overriden by retrieving the methods of the `Squeak` class.
|
|
|
|
```
|
|
Squeak methods
|
|
```
|
|
|
|
which only yields `Squeak>>#fetchFlag`. We can conclude that the `InitialisationError` is signaled elsewhere.
|
|
|
|
Maybe it is at the class side?
|
|
|
|
```
|
|
(Squeak class) methods
|
|
```
|
|
|
|
which yields `(Squeak class)>>#basicNew`.
|
|
|
|
Obtaining a reference to this method using `(Squeak class)>>#basicNew` yields:
|
|
|
|
```
|
|
basicNew InitialisationError signal.
|
|
```
|
|
|
|
Hence, we found the culprit. We can disable this behaviour by recompiling that method to something that just
|
|
delegates the `basicNew` message to its parent:
|
|
|
|
```
|
|
(Squeak class) compile: 'basicNew ^ super basicNew'
|
|
```
|
|
|
|
Now, we can try to get an instance of our `Squeak` again.
|
|
|
|
```
|
|
Squeak new.
|
|
```
|
|
|
|
Which, indeed, yields `a Squeak`, mission accomplished.
|
|
|
|
The last part of this solution consisted of calling the `fetchFlag` method on that instance:
|
|
|
|
```
|
|
Squeak new fetchFlag.
|
|
```
|
|
|
|
Which yields:
|
|
|
|
```
|
|
IG{ImSoMetaEvenThisAcronym}
|
|
Challenge
|
|
```
|
|
|
|
Success!
|