Linux PAM had too much configuration, so I made my own. It even comes with all sorts of performance optimizations!

They additionally provided the better-pam binary.

From initial execution, we see the following hint:

**** Welcome User! ****

**** If you ever forget the password for the "admin" account, it's just the flag ;) ****

The goal is to acquire the information for the admin account, which is one of the options:

**** Please select an option below: ****

1. Help
2. Authenticate
3. Add new account
4. Print info for current account
5. Exit

Unfortunately, this requires us to authenticate first, as we don't have a "current account". Bonus round: we'd need the password to actually log into the account, but the flag is the password. Chicken, meet egg.

To resolve this, we'll have to bypass authentication somehow. Something tells me it has to do with adding a new account.

Hopping to Ghidra, we discover that the account creation flow involves a call to pthread_create:

pthread_create call to verifyNoDuplicatesExist

Interesting; the verification that no duplicates are present is spawned to another thread. It's timing attack time, baby.

Looking into verifyNoDuplicatesExist a little closer, we see that they actually use a rather expensive string operation:

two calls to strlen in the verifyNoDuplicatesExist function

Why is strlen expensive, you ask? In C, strings do not store their runtime length anywhere. As strings are terminated by null bytes, the string is simply searched until a null byte is discovered. In other words, strlen's time to compute is proportional to the size of the input string.

The next question is: how long can we get it? Answer:

very, very long usernames are permitted

The max length of the string is 0x3fffff, 4194303 bytes or just under 4 megabytes (!). So let's whip up a quick script:


(for i in {1..6}; do 
  echo 3; 
  printf "%4194303.s" $i; 
  echo lmao; 
echo 3; 
echo admin; 
echo 2; 
echo admin; 
echo 4) | nc 10994

In this attack, we:

  1. Create 6 users with names that are 4194303 bytes long.
  2. Create an additional admin account with an empty password.
  3. Log in as that admin user.
  4. Request user info.

This works because, foolishly, the binary checks whether or not the credentials would work for any user:

authentication iterates through each user/pass combo

Because strcmp is a very efficient operation (it ends at the first match) and strlen is expensive (scans the input string to its end), we are successfully able to log in as the administrator account and acquire the account details before the verifyNoDuplicatesExist method discovers that we've done something naughty.

flag indicates that we successfully dumped the info of the correct account

Flag: flag{y0uR3_n0w_4n_aN_4Uth3Nt1c_hAck3r!}