write-ups-challenges-2019-2020/pharo-challenge/writeup/README.md
2022-11-24 22:43:03 +01:00

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!