BuckeyeCTF 2022 - pwn: ronin
References
https://git.mbund.org/mbund/buckeyectf-2022/src/branch/main/writeups/ronin/ronin.md
Decompiled code
After decompiling the binary, I was able to take a look at those major funtions that make up this program.
main()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
char shellcode_buffer[80]; // [rsp+0h] [rbp-50h] BYREF
setvbuf(_bss_start, 0LL, 2, 0LL);
scroll(txt);
fgets(shellcode_buffer, 80, stdin);
if ( !strncmp("Chase after it.", shellcode_buffer, 15uLL) )
{
scroll(off_4028);
chase();
}
scroll(off_4030);
return 0;
}
scroll()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ize_t __fastcall scroll(const char *addr_to_some_buffer)
{
__useconds_t v1; // eax
size_t result; // rax
char single_char; // [rsp+1Fh] [rbp-11h]
size_t v4; // [rsp+20h] [rbp-10h]
size_t i; // [rsp+28h] [rbp-8h]
v4 = strlen(addr_to_some_buffer);
for ( i = 0LL; ; ++i )
{
result = i;
if ( i >= v4 )
break;
single_char = addr_to_some_buffer[i]; // printing a single char from the string array
putchar(single_char);
if ( single_char == 10 )
v1 = 1000000;
else
v1 = 50000;
usleep(v1);
}
return result;
}
encounter()
:
1
2
3
4
5
6
7
8
9
10
size_t encounter()
{
char s[32]; // [rsp+0h] [rbp-20h] BYREF
while ( getchar() != 10 )
;
scroll(off_4040);
fgets(s, 49, stdin);
return scroll(off_4048);
}
chase()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
oid __noreturn chase()
{
int user_input_direction; // [rsp+Ch] [rbp-24h] BYREF
__int64 text_options[4]; // [rsp+10h] [rbp-20h]
text_options[0] = (__int64)"The treeline ends, and you see beautiful mountains in the distance. No monkey here.\n";
text_options[1] = (__int64)"Tall, thick trees surround you. You can't see a thing. Best to go back.\n";
text_options[2] = (__int64)"You found the monkey! You continue your pursuit.\n";
text_options[3] = (__int64)"You find a clearing with a cute lake, but nothing else. Turning around.\n";
scroll(off_4038);
while ( 1 )
{
__isoc99_scanf("%d", &user_input_direction);
if ( user_input_direction <= 3 )
search((const char *)text_options[user_input_direction], user_input_direction);
else
puts("Nice try, punk");
}
}
How to?
I noticed there were two possible buffers that I could store the shellcode in the main function and encounter()
. But, if I want to use the buffer in encounter()
, the size of the shellcode should be small (smaller than 40 bytes possibly). If we use a shellcode that is crafted by pwntools’ shellcraft utility, the amd64 linux shellcode is 48 bytes big so we must use the buffer that is in the main function.
What I didn’t realize was if I wanted to use the buffer in the main function (or even the buffer in encounter()
), since PIE is enabled, we need to find an offset to the buffer because we don’t know the exact memory address to the buffer.
As I just mentioned it is important to find a spot where you can leak stack address to calculate the offset to the buffer and in chase()
, there is this line of code search((const char *)text_options[user_input_direction], user_input_direction);
that passes the pointer to a char array where stores some strings that need to be printed to the users. And by providing an index value that is abnormal (meaning a negative value), the reference says that we are able to leak a memory address on stack.
In chase()
, we can see that as long as the index value that we enter is less than 4, it will pass the memory address of text_options[some_val]
to search()
.
And, in search()
, it basically prints out a character that is pointed by that memory address. This is how we are able to see the leaked address in GDB and use that (by examining things around the leaked address) to find the buffer in the main function and the offset to the shellcode.
Of course, the attack should happen in encounter()
because the buffer in that function also can be controlled by us and we can replace the return address with the offset we calculate. The reference describes how to do all this in detail.
Things I Learned
Using a negative index value to expose stack address.
Good understanding of a target application is important.
Became more familiar with pwntools’ shellcode generation.