4.7 KiB
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!