2022::BalsnCTF::ProjectO Write-up
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
:
... 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
.
...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:
... 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:
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.
Original Author: terrynini38514
Original Link: https://blog.terrynini.tw/posts/2022-BalsnCTF-ProjectO-Write-up/
Publish at: September 12, 2022 at 08:00:00 (Taiwan Time)
Copyright: This article is licensed under CC BY-NC 4.0