NiNi's Den

2022::BalsnCTF::ProjectO Write-up

Word count: 679Reading time: 4 min
2022/09/12

Hi, I’m the author of ProjectO. To solve the challenge, player must reversing the game server to understand the protocol and mechanism.
A semi-auto game client is sufficient to gain the flag.

MSVC STL

Though the binary is stripped, it’s not difficult to recognize it’s using some STLs in C++. But one should quickly realize that the layout of structures are differ from their equivalent in libc++. The concept is similar, it’s should not be a problem to figure out the correct layout. Here is the source code of MSVC STL. In case you have no idea about the libc++ STL, this is the tutorial made by flagbog.

Protocol Buffers

Both of the code of server logic and the protobuf library were chosen as Release version at compile stage, but the information of protocol buffers wasn’t stripped. It helps to understand the functionality of the binary. The problem is how to send a valid request.

First step is to find out the vtable of protobuf, e.g the balsn::Request::vftable is the vtable of Request message in package balsn. One of the functions in vtable contains the implementation of serialization. By following the function at 0x000140015A30, we can target the function responsible for serialization stored at vtable+0x48, which is sub_1400020D0:

0x00140015A30
...
else
{
v10 = 16;
v8 = &a2[v5 - 16];
v7 = v8;
v9 = (__int64)v11 + 8;
}
if ( !(*(__int64 (__fastcall **)(__int64, char *, char **))(*(_QWORD *)a1 + 0x48i64))(a1, a2, &v7) || (_DWORD)v14 )
return 0;
else
return sub_140015440((__int64)&v7, a1, 1);
}

A number in protobuf is represent by Variant Length Integer, the pattern can be observed in function sub_140012B80, which also being called in sub_1400020D0.

sub_1400020D0
...
if ( v8 >> 3 == 1 )
{
if ( (_BYTE)v8 != 8 )
goto LABEL_35;
v12 = *a2;
v3 |= 2u;
if ( (v12 & 0x80u) != 0 )
{
v12 = (a2[1] << 7) + v12 - 128;
if ( (a2[1] & 0x80u) != 0 )
{
sub_140012B80(&v22, a2, v12);
v12 = v23;
a2 = v22;
}
else
{
a2 += 2;
}
}
...

But it’s time consuming to review each of such functions of every types of message. Actually, after parsing a member of a message, the protobuf set a bit in the structure to indicate the present/absent of a member:

sub_1400020D0
...
if ( v8 >> 3 == 1 )
{
if ( (_BYTE)v8 != 8 )
goto LABEL_35;
v12 = *a2;
v3 |= 2u; //set the bit corresponding bit
if ( (v12 & 0x80u) != 0 )
{
...
else if ( v8 >> 3 == 2 )
{
if ( (_BYTE)v8 != 16 )
goto LABEL_35;
v11 = *a2;
v3 |= 4u; //set the bit corresponding bit
if ( (v11 & 0x80u) != 0 )
...
*(_DWORD *)(a1 + 16) |= v3; // assign the value
return a2;
}

So, by following the usage of each message, we can construct the message now. Though the name of members are unimportant, you can find them in binary. The source code of message:

message.proto
syntax = "proto2";

package balsn;

message Request {
required int32 module = 1;
required uint32 command = 2;
optional bytes data = 3;
}

message Auth{
optional string username=1;
optional string password=2;
}

message Control{
required int32 direction = 1;
}

message Info{
required int32 which = 1;
required int32 what = 2;
}

message Point{
required int32 x = 1;
required int32 y = 2;
}

message Response {
required int32 status = 1;
optional int32 iData = 2;
repeated Point pData = 3;
optional bytes bData = 4;
}

Besides the protobuf itself, the binary required client to send the length of the incoming protobuf, it looks like:

| (4 bytes) length of message | message |

The Game

The game is:

  • 10 levels of random generated MAZE with 10 enemies and a portal to next level
  • Have to kill 70 enemies to claim the flag after level 10
  • Enemies won’t move and only slash it’s adjacent blocks every 3 turns
  • Player only has 1 HP
  • Connection timeout after 12 minutes

That’s it, just write a semi-auto client to reach the goal.
It’s possible! Check out the #writeups channel on Discord.
And this github repo contains the source code of the challenge.

avatar
Terrynini
逆逆逆逆
CATALOG
  1. 1. MSVC STL
  2. 2. Protocol Buffers
  3. 3. The Game