TAMUctf writeup - vault
For me its been a long while since I challenged in a ctf competition. So I decided to play one because we in "Quarantine" anyway. This challenge is from TAMUctf 2020 under "Reversing" named as 'vault'.
I swear anything that says vault
as a introductory challenge is a good way to brush up your basic debugging skills, because its the printf("hello, world");
of reverse engineering and binary analysis.
I'll be using radare2
using jupyter-radare2
kernel.
Recon ↩
First let's analyze the binary.
o vault
aaa
i
fd 4
file vault
size 0x4230
humansz 16.5K
mode r-x
format elf64
iorw false
blksz 0x0
block 0x100
type DYN (Shared object file)
arch x86
baddr 0x0
binsz 15020
bintype elf
bits 64
canary false
class ELF64
compiler GCC: (Debian 9.3.0-3) 9.3.0
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic true
relocs true
relro partial
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
It's ELF 64 and not stripped
. Awww yea!
Function calls ↩
Analyzing the function call stack, it's evident that it takes input from user using fgets
and displays results.
aflm
entry0:
reloc.__libc_start_main
entry.fini0:
sym..plt.got
rip
sym.deobfuscate:
sym.imp.strlen
sym.__libc_csu_init:
sym._init
rsi
main:
sym.imp.malloc
sym.deobfuscate
sym.imp.printf
sym.imp.fgets
sym.imp.strcmp
sym.imp.puts
And deobfuscate
is a defined function. Might be something related to password?
Analyze main
↩
s main
pdf
┌ 207: int main (int argc, char **argv, char **envp);
│ ; var char *s2 @ rbp-0x10
│ ; var char *s1 @ rbp-0x8
│ ; DATA XREF from entry0 @ 0x10bd
│ 0x000012c9 55 push rbp
│ 0x000012ca 4889e5 mov rbp, rsp
│ 0x000012cd 4883ec10 sub rsp, 0x10
│ 0x000012d1 bf1a000000 mov edi, 0x1a ; size_t size
│ 0x000012d6 e8a5fdffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x000012db 488945f8 mov qword [s1], rax
│ 0x000012df 488b45f8 mov rax, qword [s1]
│ 0x000012e3 48be34343238. movabs rsi, 0x7e394c2f38323434 ; '4428/L9~'
│ 0x000012ed 48bf783a787b. movabs rdi, 0x54834c1f7b783a78
│ 0x000012f7 488930 mov qword [rax], rsi
│ 0x000012fa 48897808 mov qword [rax + 8], rdi
│ 0x000012fe 48b928298484. movabs rcx, 0x2f72857884842928
│ 0x00001308 48894810 mov qword [rax + 0x10], rcx
│ 0x0000130c 66c740186776 mov word [rax + 0x18], 0x7667 ; 'gv'
│ ; [0x7667:2]=0xffff
│ 0x00001312 c6401a00 mov byte [rax + 0x1a], 0
│ 0x00001316 488b45f8 mov rax, qword [s1]
│ 0x0000131a 4889c7 mov rdi, rax
│ 0x0000131d e863feffff call sym.deobfuscate
│ 0x00001322 bf1b000000 mov edi, 0x1b ; size_t size
│ 0x00001327 e854fdffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x0000132c 488945f0 mov qword [s2], rax
│ 0x00001330 488d35d10c00. lea rsi, str.Enter_password: ; 0x2008 ; "Enter password: "
│ 0x00001337 488d3ddb0c00. lea rdi, [0x00002019] ; "%s" ; const char *format
│ 0x0000133e b800000000 mov eax, 0
│ 0x00001343 e808fdffff call sym.imp.printf ; int printf(const char *format)
│ 0x00001348 488b15112d00. mov rdx, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5
│ ; [0x4060:8]=0 ; FILE *stream
│ 0x0000134f 488b45f0 mov rax, qword [s2]
│ 0x00001353 be1b000000 mov esi, 0x1b ; int size
│ 0x00001358 4889c7 mov rdi, rax ; char *s
│ 0x0000135b e800fdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
│ 0x00001360 488b55f0 mov rdx, qword [s2]
│ 0x00001364 488b45f8 mov rax, qword [s1]
│ 0x00001368 4889d6 mov rsi, rdx ; const char *s2
│ 0x0000136b 4889c7 mov rdi, rax ; const char *s1
│ 0x0000136e e8fdfcffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ 0x00001373 85c0 test eax, eax
│ ┌─< 0x00001375 750e jne 0x1385
│ │ 0x00001377 488d3da20c00. lea rdi, str.Correct___That_s_the_password ; 0x2020 ; "Correct! That's the password!" ; const char *s
│ │ 0x0000137e e8adfcffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x00001383 eb0c jmp 0x1391
│ ││ ; CODE XREF from main @ 0x1375
│ │└─> 0x00001385 488d3db40c00. lea rdi, str.Sorry__that_isn_t_the_right_password. ; 0x2040 ; "Sorry, that isn't the right password." ; const char *s
│ │ 0x0000138c e89ffcffff call sym.imp.puts ; int puts(const char *s)
│ │ ; CODE XREF from main @ 0x1383
│ └──> 0x00001391 b800000000 mov eax, 0
│ 0x00001396 c9 leave
└ 0x00001397 c3 ret
Seems pretty trivial, the deobfuscate
functions makes some sort of string and stores in s1
, then the program asks user to "Enter Password", the input is taken and is of the size 0x1b
and stored in s2
. Note that fgets
is used. The creator doesn't want us to buffer overflow.
And then the two strings are compared, if they are equal it stays "That's the password and exits" ?!?
At this stage, I'm pretty sure the string in s1
created from deobfuscate
is the flag we need.
Let's check the disassembly of deobfuscate
s sym.deobfuscate
pdf
┌ 324: sym.deobfuscate (char *arg1);
│ ; var char *s @ rbp-0x28
│ ; var int64_t var_15h @ rbp-0x15
│ ; var size_t var_14h @ rbp-0x14
│ ; var size_t var_10h @ rbp-0x10
│ ; var int64_t var_ch @ rbp-0xc
│ ; var int64_t var_8h @ rbp-0x8
│ ; var int64_t var_4h @ rbp-0x4
│ ; arg char *arg1 @ rdi
│ ; CALL XREF from main @ 0x131d
│ 0x00001185 55 push rbp
│ 0x00001186 4889e5 mov rbp, rsp
│ 0x00001189 4883ec30 sub rsp, 0x30
│ 0x0000118d 48897dd8 mov qword [s], rdi ; arg1
│ 0x00001191 488b45d8 mov rax, qword [s]
│ 0x00001195 4889c7 mov rdi, rax ; const char *s
│ 0x00001198 e8a3feffff call sym.imp.strlen ; size_t strlen(const char *s)
│ 0x0000119d 8945ec mov dword [var_14h], eax
│ 0x000011a0 c745fc000000. mov dword [var_4h], 0
│ ┌─< 0x000011a7 eb4f jmp 0x11f8
│ │ ; CODE XREF from sym.deobfuscate @ 0x11fe
│ ┌──> 0x000011a9 8b45fc mov eax, dword [var_4h]
│ ╎│ 0x000011ac 4863d0 movsxd rdx, eax
│ ╎│ 0x000011af 488b45d8 mov rax, qword [s]
│ ╎│ 0x00001296 4863d0 movsxd rdx, eax
. . . . . . . i t s h u g e a n d w o r t h l e s s t o w o r r y a b o u t . . . . . . .
│ ╎│ 0x00001299 488b45d8 mov rax, qword [s]
│ ╎│ 0x0000129d 4801d0 add rax, rdx
│ ╎│ 0x000012a0 0fb608 movzx ecx, byte [rax]
│ ╎│ 0x000012a3 8b45f0 mov eax, dword [var_10h]
│ ╎│ 0x000012a6 4898 cdqe
│ ╎│ 0x000012a8 488d50ff lea rdx, [rax - 1]
│ ╎│ 0x000012ac 488b45d8 mov rax, qword [s]
│ ╎│ 0x000012b0 4801d0 add rax, rdx
│ ╎│ 0x000012b3 31ce xor esi, ecx
│ ╎│ 0x000012b5 89f2 mov edx, esi
│ ╎│ 0x000012b7 8810 mov byte [rax], dl
│ ╎│ 0x000012b9 836df001 sub dword [var_10h], 1
│ ╎│ ; CODE XREF from sym.deobfuscate @ 0x1278
│ ╎└─> 0x000012bd 837df000 cmp dword [var_10h], 0
│ └──< 0x000012c1 7fb7 jg 0x127a
│ 0x000012c3 488b45d8 mov rax, qword [s]
│ 0x000012c7 c9 leave
└ 0x000012c8 c3 ret
It's huge, and I'm too lazy to do it. So here is the thing, the function only takes an memory address to strcpy the computed password and it doesn't require any parameter that changes i.e it's a pure functions? I'm looking at you, fp nerds.
Why don't we just debug it.
Debugging to get the flag ↩
ood
73743
dcu main
pd
┌ 207: int main (int argc, char **argv, char **envp);
│ ; var char *s2 @ rbp-0x10
│ ; var char *s1 @ rbp-0x8
│ ; DATA XREF from entry0 @ 0x559ab2a2d0bd
│ 0x559ab2a2d2c9 55 push rbp
│ 0x559ab2a2d2ca 4889e5 mov rbp, rsp
│ 0x559ab2a2d2cd 4883ec10 sub rsp, 0x10
│ 0x559ab2a2d2d1 bf1a000000 mov edi, 0x1a ; 26 ; size_t size
│ 0x559ab2a2d2d6 e8a5fdffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x559ab2a2d2db 488945f8 mov qword [s1], rax
│ 0x559ab2a2d2df 488b45f8 mov rax, qword [s1]
│ 0x559ab2a2d2e3 48be34343238. movabs rsi, 0x7e394c2f38323434 ; '4428/L9~'
│ 0x559ab2a2d2ed 48bf783a787b. movabs rdi, 0x54834c1f7b783a78
│ 0x559ab2a2d2f7 488930 mov qword [rax], rsi
│ 0x559ab2a2d2fa 48897808 mov qword [rax + 8], rdi
│ 0x559ab2a2d2fe 48b928298484. movabs rcx, 0x2f72857884842928
│ 0x559ab2a2d308 48894810 mov qword [rax + 0x10], rcx
│ 0x559ab2a2d30c 66c740186776 mov word [rax + 0x18], 0x7667 ; 'gv'
│ ; [0x7667:2]=0xffff
│ 0x559ab2a2d312 c6401a00 mov byte [rax + 0x1a], 0
│ 0x559ab2a2d316 488b45f8 mov rax, qword [s1]
│ 0x559ab2a2d31a 4889c7 mov rdi, rax
│ 0x559ab2a2d31d e863feffff call sym.deobfuscate
│ 0x559ab2a2d322 bf1b000000 mov edi, 0x1b ; 27 ; size_t size
│ 0x559ab2a2d327 e854fdffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x559ab2a2d32c 488945f0 mov qword [s2], rax
│ 0x559ab2a2d330 488d35d10c00. lea rsi, str.Enter_password: ; 0x559ab2a2e008 ; "Enter password: "
│ 0x559ab2a2d337 488d3ddb0c00. lea rdi, [0x559ab2a2e019] ; "%s" ; const char *format
│ 0x559ab2a2d33e b800000000 mov eax, 0
│ 0x559ab2a2d343 e808fdffff call sym.imp.printf ; int printf(const char *format)
│ 0x559ab2a2d348 488b15112d00. mov rdx, qword [obj.stdin] ; obj.stdin__GLIBC_2.2.5
│ ; [0x559ab2a30060:8]=0x7f9f4702c7e0 ; FILE *stream
│ 0x559ab2a2d34f 488b45f0 mov rax, qword [s2]
│ 0x559ab2a2d353 be1b000000 mov esi, 0x1b ; 27 ; int size
│ 0x559ab2a2d358 4889c7 mov rdi, rax ; char *s
│ 0x559ab2a2d35b e800fdffff call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
│ 0x559ab2a2d360 488b55f0 mov rdx, qword [s2]
│ 0x559ab2a2d364 488b45f8 mov rax, qword [s1]
│ 0x559ab2a2d368 4889d6 mov rsi, rdx ; const char *s2
│ 0x559ab2a2d36b 4889c7 mov rdi, rax ; const char *s1
│ 0x559ab2a2d36e e8fdfcffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ 0x559ab2a2d373 85c0 test eax, eax
│ 0x559ab2a2d375 750e jne 0x559ab2a2d385
│ 0x559ab2a2d377 488d3da20c00. lea rdi, str.Correct___That_s_the_password ; 0x559ab2a2e020 ; "Correct! That's the password!" ; const char *s
│ 0x559ab2a2d37e e8adfcffff call sym.imp.puts ; int puts(const char *s)
│ 0x559ab2a2d383 eb0c jmp 0x559ab2a2d391
│ ; CODE XREF from main @ 0x559ab2a2d375
│ 0x559ab2a2d385 488d3db40c00. lea rdi, str.Sorry__that_isn_t_the_right_password. ; 0x559ab2a2e040 ; "Sorry, that isn't the right password." ; const char *s
│ 0x559ab2a2d38c e89ffcffff call sym.imp.puts ; int puts(const char *s)
│ ; CODE XREF from main @ 0x559ab2a2d383
│ 0x559ab2a2d391 b800000000 mov eax, 0
│ 0x559ab2a2d396 c9 leave
└ 0x559ab2a2d397 c3 ret
Now the program is memory as a process. We have it hit a brekpoint at beginning of main
to see the locations in memory of the program. The deobfuscation
is called at address 0x559ab2a2d31d
. We now set a breakpoint at the next instruction 0x559ab2a2d322
.
dcu 0x559ab2a2d322
Since we have hit the breakpoint, let's capture what all happened.
The function is called and it has filled the location s1
in memory with a string which is compared later on with the user input. Note that the s1
address is stored in register rdi
. Hence now the string created by the function is in the memory address which rdi
holds, check 3 instructions from 0x559ab2a2d316
.
Examining strings at the memory location in rdi
x/s @ rdi
0x559ab37002a0 gigem{p455w0rd_1n_m3m0ry1}
0x559ab37002ba A
0x559ab37002cb
And there we have the flag! It says password_in_memory :laughing: