Our interns put together a little test program for us. It seems they all might have patched together their separate projects. Could you test it out for me?
Este desafio fornece apenas o executável, que é bem simples. Ao executá-lo, ele entra em um loop que pede para digitarmos um número de 1 a 3, cada um correspondendo a uma operação diferente:
Welcometoourintern's test project!The following are your options: 1. Say hi 2. Print the flag 3. Create an accountEnter option (1-3). Press Enter to submit:1Hi!The following are your options: 1. Say hi 2. Print the flag 3. Create an accountEnter option (1-3). Press Enter to submit:2Error: Option 2 requires root privileges HAHAThe following are your options: 1. Say hi 2. Print the flag 3. Create an accountEnter option (1-3). Press Enter to submit:
A opção 2 parece ser o objetivo do desafio, mas quando a selecionamos, o programa informa que não temos permissão. A partir daí, partimos para a análise do código utilizando o Ghidra.
main.cpp
void main(void)
{
ostream *poVar1;
poVar1 = std::operator<<((ostream *)std::cout,"Welcome to our intern\'s test project!");
std::ostream::operator<<(poVar1,std::endl<>);
do {
std::ostream::operator<<((ostream *)std::cout,std::endl<>);
poVar1 = std::operator<<((ostream *)std::cout,"The following are your options:");
std::ostream::operator<<(poVar1,std::endl<>);
poVar1 = std::operator<<((ostream *)std::cout," 1. Say hi");
std::ostream::operator<<(poVar1,std::endl<>);
poVar1 = std::operator<<((ostream *)std::cout," 2. Print the flag");
std::ostream::operator<<(poVar1,std::endl<>);
poVar1 = std::operator<<((ostream *)std::cout," 3. Create an account");
std::ostream::operator<<(poVar1,std::endl<>);
poVar1 = std::operator<<((ostream *)std::cout,"Enter option (1-3). Press Enter to submit:");
std::ostream::operator<<(poVar1,std::endl<>);
handleOption();
} while( true );
}
A função main() pode ser vista acima, e basicamente confirma o que já observamos no terminal: um loop que exibe o menu e pede uma entrada de 1 a 3. O destaque vai para a função handleOption(), que provavelmente é responsável por lidar com essa entrada do usuário.
handleOption.cpp
void handleOption(void)
{
bool bVar1;
__uid_t _Var2;
long *plVar3;
ostream *poVar4;
long in_FS_OFFSET;
int local_5d4;
int local_5d0;
int local_5cc;
string local_5c8 [32];
istringstream local_5a8 [384];
int local_428 [258];
long local_20;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
local_5d0 = 0;
std::string::string(local_5c8);
/* try { // try from 0010171b to 0010173d has its CatchHandler @ 0010192b */
std::getline<>((istream *)std::cin,local_5c8);
std::istringstream::istringstream(local_5a8,local_5c8,8);
while( true ) {
plVar3 = (long *)std::istream::operator>>((istream *)local_5a8,&local_5d4);
bVar1 = std::ios::operator.cast.to.bool((ios *)((long)plVar3 + *(long *)(*plVar3 + -0x18)));
if ((bVar1) && (local_5d0 < 0x100)) {
bVar1 = true;
}
else {
bVar1 = false;
}
if (!bVar1) break;
if ((local_5d4 < 1) || (3 < local_5d4)) {
/* try { // try from 00101789 to 001018b5 has its CatchHandler @ 00101913 */
poVar4 = std::operator<<((ostream *)std::cout,"Ignoring invalid option: ");
poVar4 = (ostream *)std::ostream::operator<<(poVar4,local_5d4);
std::ostream::operator<<(poVar4,std::endl<>);
}
else {
local_428[local_5d0] = local_5d4;
local_5d0 = local_5d0 + 1;
}
}
if ((local_428[0] == 2) && (_Var2 = geteuid(), _Var2 != 0)) {
bVar1 = true;
}
else {
bVar1 = false;
}
if (bVar1) {
poVar4 = std::operator<<((ostream *)std::cout,"Error: Option 2 requires root privileges HAHA");
std::ostream::operator<<(poVar4,std::endl<>);
}
else {
for (local_5cc = 0; local_5cc < local_5d0; local_5cc = local_5cc + 1) {
if (local_428[local_5cc] == 1) {
sayHello();
}
else if (local_428[local_5cc] == 2) {
printFlag();
}
else if (local_428[local_5cc] == 3) {
login();
}
}
}
std::istringstream::~istringstream(local_5a8);
std::string::~string(local_5c8);
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
O código acima mostra como a função handleOption() funciona. Ela lê o que foi digitado no terminal e salva os valores no vetor local_428. Um detalhe importante é que ela lê a linha inteira e salva todos os números digitados (desde que estejam separados por espaço), tratando todos eles um por um.
A parte mais curiosa é a verificação de permissão para a opção 2:
option2.cpp
if ((local_428[0] == 2) && (geteuid() != 0))
Ou seja, ela só verifica se a primeira opção digitada foi 2 e se o usuário não é root. Se isso for verdade, ela impede o acesso e mostra a mensagem de erro. Porém, se digitarmos qualquer outro número primeiro, e colocarmos o 2 depois, a função printFlag() será executada normalmente, pois o restante do vetor não passa pela verificação de privilégio.
2. Exploit
Seguindo o que foi visto acima, basta digitarmos qualquer opção antes da opção 2 para que ela seja executada, revelando assim a flag.
Welcome to our intern's test project!
The following are your options:
1. Say hi
2. Print the flag
3. Create an account
Enter option (1-3). Press Enter to submit:
1 2
Hi!
Here is your flag: DawgCTF{B@d_P3rm1ssi0ns}
The following are your options:
1. Say hi
2. Print the flag
3. Create an account
Enter option (1-3). Press Enter to submit: