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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
ds3-nrssr-rce — CVE-2022-24125 및 CVE-2022-24126에 대한 문서 및 개념 증명 코드 | Kitploit
도구/GitHubGitHub/tremwil/ds3-nrssr-rce
Exploit FrameworksVulnerability AnalysisExploitationReverse EngineeringShellcodePenetration TestingLearning & EducationRed TeamingShellcode GenerationPayload DevelopmentBinary Exploitation
169854년 전Kitploit 검토 완료
GitHub
tremwil/ds3-nrssr-rce

ds3-nrssr-rce

CVE-2022-24125 및 CVE-2022-24126에 대한 문서 및 개념 증명 코드

저장소 보기

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

업데이트: 다크 소울 III 1.15.1

2022/08/25에 다크 소울 III의 새로운 게임 업데이트 1.15.1이 출시되었으며, 온라인 서비스가 복구되었습니다. 이 업데이트는 CVE-2022-24125 및 CVE-2022-24126을 수정했으며, 게임 P2P 네트워킹에 존재하는 다양한 기타 잠재적 보안 취약점(OOB 읽기/쓰기)도 함께 수정되었습니다. 또한, 다른 플레이어의 세이브를 손상시킬 수 있는 알려진 모든 익스플로잇이 수정되었습니다. 온라인 멀티플레이에서 자주 마주칠 수 있는 많은 사소한 치트(예: "저주 칼")도 패치되었습니다.

ds3-nrssr-rce

이 저장소는 FROM SOFTWARE 게임에 영향을 미치는 가장 최근 RCE 익스플로잇인 CVE-2022-24126에 대한 개념 증명 코드와 문서를 포함하고 있습니다. 이론적으로는 다른 게임에도 가능하지만, 제 연구가 수행된 게임인 다크 소울 III에 초점을 맞추고 있습니다. 현재로서는 다크 소울 III에 대한 개념 증명 코드만 존재하며, 취약점이 존재하는 것으로 확인된 게임은 다음과 같습니다:

  • 다크 소울 1 PTDE (크레딧: LukeYui)
  • 다크 소울 리마스터드 (크레딧: metal-crow)
  • 다크 소울 2 (Scholar 포함) (크레딧: LukeYui)
  • 다크 소울 3 (1.15.0까지) (크레딧: tremwil)

취약한 코드는 세키로에도 존재하지만(크레딧: LukeYui), 이를 트리거할 방법은 없습니다. 데몬즈 소울에서의 존재 여부는 확인되지 않았지만 가능성이 매우 높습니다. 비공개 네트워크 테스트는 이 취약점의 영향을 받았지만, 출시 버전의 엘든 링은 그렇지 않습니다. 사실, 다크 소울 III에 존재했던 방대한 목록의 네트워크 크래시, 경계를 벗어난 읽기/쓰기, 그리고 플레이어가 동료의 게임 데이터를 수정할 수 있는 익스플로잇이 엘든 링에서는 패치되었습니다. 이 목록을 정리해준 LukeYui와 신속하게 대응한 FROM SOFTWARE에 감사드립니다! 엘든 링은 해커가 입힐 수 있는 피해 범위 측면에서 FROM SOFTWARE 타이틀 중 가장 안전하다고 단언할 수 있어 기쁩니다.

오해 해소

대중의 믿음과 달리, 이것은 P2P 네트워킹 익스플로잇이 아닙니다. 매치메이킹 서버와 관련이 있으므로 훨씬 더 심각합니다. 다른 매치메이킹 서버 취약점(CVE-2022-24125)으로 인해 멀티플레이 활동에 참여하지 않아도 취약해질 수 있기 때문입니다.

다크 소울 III에서, 이를 악용하는 악의적인 공격자는 수 초 내에 온라인 플레이어의 기기에서 최대 1.3MiB 크기의 셸코드를 안정적으로 실행할 수 있었을 것입니다.

1

서버 종료 전 몇 달 동안 평균 동시 플레이어 수가 약 20,000명인 게임 특성상, 특히 엘든 링에도 존재할 가능성이 있으므로 즉시 수정이 필요한 문제임이 분명했습니다. FROM SOFTWARE가 제가 개념 증명 영상과 상세한 익스플로잇 문서(이 리드미의 상당 부분이 기반함)를 포함한 초기 보고 후 40일 이상 조치를 취하지 않았기 때문에, 개발자들이 해결하도록 주의를 끌기 위해 익스플로잇의 존재를 무해한 방식으로 공개적으로 시연하기로 결정했고, 효과가 있었습니다.

목차

  • 익스플로잇 요약 (CVE-2022-24126)
  • 배포 벡터 (CVE-2022-24125)
  • 모든 게임에 대한 일반적인 익스플로잇 전술
    • 버그 #1: 항목 목록 파서의 범위 검사 부재
    • 버그 #2: NRSessionSearchResult 파서의 버퍼 오버런
    • ROP 체인을 위한 길 닦기
  • 다크 소울 III 개념 증명 코드
    • PoC 코드 실행
    • 공격 벡터
    • 가상 호출 리디렉션 체인
    • 추가 정보

익스플로잇 요약 (CVE-2022-24126)

NRSessionSearchResult 매치메이킹 데이터를 파싱하는 동안 스택 버퍼와 데이터 크기 필드에 대한 부적절한 범위 검사로 인해 공격자가 임의 코드를 실행할 수 있습니다. 스택 오버플로를 통해 스트림 리더가 내부적으로 사용하는 DLMemoryInputStream 객체의 vftable_ptr 하위 2바이트를 덮어쓸 수 있으며, 이로 인해 실행이 신중하게 선택된 인접 코드로 리디렉션됩니다. DLMemoryInputStream 객체의 구조와 데이터 크기 필드를 교묘하게 익스플로잇하면 RCX가 우리 패킷의 주소를 가리키는 상태에서 임의 코드 리디렉션을 달성할 수 있습니다. 그런 다음 가상 호출(다른 오프셋에서 실행되며, 이제 패킷 버퍼에 쓴 주소로 점프함)을 통한 일련의 코드 리디렉션을 사용하여 임의 코드 실행을 달성할 수 있습니다.

배포 벡터 (CVE-2022-24125)

배포 벡터는 이 특정 RCE를 (이미 RCE임에도 불구하고) 특히 심각하게 만듭니다. 익스플로잇은 NRSessionSearchResult 정보를 포함하는 매치메이킹 푸시 요청을 통해 전송됩니다. 즉, 공격자는 자신의 온라인 세션에 참여하는 모든 사람을 대상으로 삼을 수 있습니다. 특히 DS3의 경우:

  • 소환 (PushRequestSummonSign)
  • 암령 침입자 (PushRequestAllowBreakInTarget)
  • 계약을 통해 참여하는 플레이어 (PushRequestVisit)
  • 아레나 전투원 (PushRequestAcceptQuickMatch)

이것만으로도 상당히 나쁘지만, RequestSendMessageToPlayers 요청에 의해 진정한 잠재력이 발휘됩니다:

root@kitploit:~
message RequestSendMessageToPlayers { 
    repeated uint32 player_ids = 1; 
    required bytes push_message = 2;
}

호스트는 이 요청을 사용하여 침입자에게 PushRequestAllowBreakInTarget 푸시 메시지를 직접 전송하여 스폰 좌표를 얻고 P2P 세션에 참여할 수 있도록 합니다. 그게 다입니다. 게임에서 이 요청이 사용되는 유일한 방법입니다.

그럼에도 불구하고 모든 클라이언트가 수십만 명의 특정 플레이어에게 임의의 푸시 메시지를 보낼 수 있습니다.

이것이 얼마나 끔찍하게 안전하지 않은지 강조해도 지나치지 않습니다. 모든 플레이어는 기본적으로 매치메이킹 서버를 사칭할 수 있습니다. 이 요청을 사용하여 PushRequestVisit을 통해 익스플로잇을 보내면, 공격자는 플레이어 ID만 알면 모든 온라인 플레이어를 원격으로 대상으로 삼을 수 있습니다. 또한 공격자는 여러 요청을 보내 각각 가능한 플레이어 ID의 큰 조각을 포함시킴으로써 전체 온라인 플레이어 기반에 매우 빠르게 익스플로잇을 전송할 수 있습니다.

모든 게임에 대한 일반적인 익스플로잇 전술

RCE가 모든 게임에 정확히 이식되지는 않지만, 공격자에게 임의 코드 리디렉션을 제공하는 익스플로잇의 핵심 아이디어는 동일합니다. 이를 달성할 수 있다면 게임별 가상 호출 체인 또는 ROP 체인을 찾을 가능성이 매우 높습니다. 이 "첫 번째 단계"는 다음 취약점을 사용합니다:

버그 #1: 항목 목록 파서의 범위 검사 부재

세션 참여 정보를 포함하는 매치메이킹 푸시 요청은 해당 정보를 길이로 구분된 데이터 항목 체인으로 구성된 사용자 정의 바이너리 형식으로 저장합니다. 각 항목의 형식은 다음과 같습니다:

root@kitploit:~
struct Entry
{
    uint32_t type_or_id; // 확실하지 않지만, 아마도 유형(고정 길이 = 2, 가변 길이 = 1 ?)
    uint32_t size;
    uint8_t data[size];
}

이 항목의 데이터를 복사하는 게임 함수는 크기 필드를 맹목적으로 신뢰하여 경계를 벗어난 읽기를 발생시킵니다. 악의적인 클라이언트는 크기 필드를 0x7FFFFFFF와 같은 값으로 설정하여 메모리 할당에 실패하게 만들고 피해자의 게임을 충돌시킬 수 있습니다. 나중에 이 크기는 DLMemoryInputSteam의 생성자에도 전달되며, 이는 익스플로잇의 중요한 부분입니다.

버그 #2: NRSessionSearchResult 파서의 버퍼 오버런

위에서 설명한 데이터 구조의 항목 중 하나는 직렬화된 NRSessionSearchResult 객체입니다. 이 데이터의 파서는 먼저 속성 목록을 파싱합니다. 이러한 속성은 4바이트 정수, 8바이트 정수 또는 null 종료 와이드 문자열일 수 있습니다. 이 속성 목록 다음에는 null 종료 와이드 문자열로 된 호스트 Steam 페르소나 이름과 익스플로잇에 중요하지 않은 추가 데이터가 옵니다. 이 함수와 속성 목록 파서 모두 고정 크기 스택 버퍼를 사용하여 문자열을 읽으며, 두 경우 모두 버퍼에 대한 범위 검사가 수행되지 않습니다. 다음은 호스트 이름을 복사하는 게임 코드입니다 (Ghidra 디컴파일러를 사용하여 생성한 후 정리):

root@kitploit:~
size_t idx = 0;
wchar_t wchr = 0;
do {
  // DLEndianStreamReader의 vftable 인덱스 17에 있는 read_wchar() 함수
  wchr = stream_reader->read_wchar();
  player_name_buff[idx] = wchr;
  idx++;
} while (wchr != 0);

이로 인해 버퍼 오버런 익스플로잇이 발생하여 공격자가 스택을 손상시킬 수 있습니다.

가상 호출 / ROP 체인을 위한 길 닦기

임의 코드 리디렉션을 달성하기 위해, 우리는 이 버그와 파서를 호출하는 함수가 스택에 인스턴스화하는 DLMemoryInputStream 객체의 메모리 레이아웃을 사용합니다. 이 객체는 스트림 리더가 내부적으로 사용합니다:

root@kitploit:~
struct DLMemoryInputStream {
    uintptr_t* vftable_ptr; // 오프셋 0
    size_t data_size;       // 오프셋 4 (32비트) / 8 (64비트)
    uint8_t* data_buffer;   // 오프셋 8 (32비트) / 16 (64비트)
    // 버퍼 이후의 항목은 익스플로잇에 중요하지 않음
}

우리는 data_size 필드(버그 #1)를 제어할 수 있으므로, 이를 data_buffer 필드의 스택 메모리 주소로 설정할 수 있습니다. 주소가 일정하고 너무 크지 않으면(DS3는 이 요구 사항을 충족) 성공합니다. 컴파일러가 스택 버퍼를 프레임 상단에 배치하므로, 공격자는 버그 #2를 사용하여 DLMemoryInputStream의 vftable_ptr 하위 2바이트를 덮어쓸 수 있습니다. 따라서 DLEndianStreamReader가 다음 문자를 읽을 때 내부적으로 DLMemoryInputStream을 호출하고 코드가 리디렉션됩니다. 2바이트는 DLEndianStreamReader vftable의 22번째 함수로 점프하기에 충분한 여유를 제공하며, 이 함수는 첫 번째 필드가 가리키는 객체의 6번째 가상 메서드를 호출합니다. 64비트 프로세스(예: 다크 소울 III)에서는 다음 명령어가 실행됩니다:

root@kitploit:~
MOV       RCX,qword ptr [RCX + 0x8]
MOV       RAX,qword ptr [RCX]
JMP       qword ptr [RAX + 0x40]

RCX는 DLMemoryInputStream 객체에 대한 포인터이므로, 첫 번째 명령어는 data_size 필드(버그 #1을 사용하여 공격자가 data_buffer 필드를 가리키는 스택 주소로 설정한 값)를 RCX에 씁니다. 다음 두 명령어는 따라서 실행을 공격자가 데이터 버퍼의 오프셋 0x40에 쓴 메모리 주소로 리디렉션합니다. 이제 임의 코드 리디렉션이 달성되었습니다! 여기서부터 공격자는 페이로드를 적절한 메모리 영역에 복사하고 실행하는 코드 리디렉션 체인을 설정할 수 있습니다. 버퍼가 이제 가상 메서드 테이블 역할을 하므로, 다른 오프셋에서 가상 호출에 가까운 코드를 선택하면 됩니다. 다크 소울 III 개념 증명의 경우 RCE를 달성하는 데 3개의 가젯만 필요한 설정을 찾았습니다:

  • 0x18: 140e97700
  • 0x40: 1422be020
  • 0x68: 140e40f15

이 3개의 가젯에 대한 자세한 내용은 여기를 참조하십시오. 다른 게임에서 이 가상 호출 방법이 실현 가능한 접근 방식이 아닌 경우, 임의 코드 리디렉션을 사용하여 보다 전통적인 ROP 익스플로잇을 설정할 수도 있습니다.

다크 소울 III 개념 증명 코드

PoC 코드 실행

개념 증명 코드를 실행하려면 먼저 연결할 서버가 필요합니다. 공식 서버는 익스플로잇으로 인해 비활성화되었지만, ds3os를 사용하여 개인 서버를 설정할 수 있습니다. ds3os는 소매 서버 동작을 최대한 가깝게 모방하도록 설계되었지만, 이 익스플로잇을 수정하기 위해 이미 보안 패치가 이 프로젝트에 배포되었습니다. 그러나 BuildConfig.h에서 SEND_MESSAGE_TO_PLAYERS_SANITY_CHECKS 및 NRSSR_SANITY_CHECKS 상수를 false로 설정하여 프로젝트를 직접 빌드함으로써 테스트 환경을 설정할 수 있습니다. 이는 안전하지 않은 소매 서버 동작을 모방합니다. ds3os에서 제공하는 지침에 따라 게임을 시작하고 서버에 연결하십시오.

이 작업이 완료되고 게임이 서버에 연결되면 PoC 코드를 빌드하고 Injector.exe 실행 파일을 시작합니다. 이 실행 파일은 다크 소울 III 프로세스에 익스플로잇 코드가 포함된 DLL을 주입합니다. 이 DLL은 FRPG 메시지를 서버로 보내는 게임 함수를 사용하여 자신의 클라이언트에 익스플로잇을 전달합니다.

공격 벡터

개념 증명을 위해 RequestSendMessageToPlayers를 사용하여 전송된 PushRequestVisit 메시지를 사용하기로 결정했습니다. 이는 대상의 게임이 모든 상황(메인 메뉴에서도)에서 취약한 데이터를 수신한 후 즉시 파싱한다는 점에서 가장 강력한 버전의 익스플로잇입니다.

가상 호출 리디렉션 체인

오프셋 0x18: 140e97700

root@kitploit:~
LEA       RAX,[DAT_144786150]
RET

이 가젯은 0x68의 가젯에서 사용됩니다. RAX에 144786998보다 낮지만 상당히 가까운 주소를 넣어야 합니다. 이것이 가장 가까운 주소입니다.

오프셋 0x40: 1422be020

root@kitploit:~
MOV       RDX,RAX
MOV       R8,qword ptr [RCX]
CALL      qword ptr [R8 + 0x68]

오프셋 0x68의 가젯을 사용하려면 데이터 버퍼 주소가 RDX에 저장되고 RCX가 동일하게 유지되어야 합니다. 이 가젯이 정확히 그 작업을 수행합니다.

오프셋 0x68: 140e40f15

root@kitploit:~
; 오프셋 0x40의 가젯에서 여기로 점프
MOV       RBX,RDX
CMP       R9,R8

 ; 절대 점프하지 않음, R9 != R8
JZ        LAB_140e40f7a
MOV       RAX,qword ptr [RCX]
MOV       R8,qword ptr [RSP + 0x50]
MOV       RDX,R9
MOV       qword ptr [RSP + 0x30],RSI

; 오프셋 18의 가젯 호출 (140e97700). RAX에 144786150을 로드
CALL      qword ptr [RAX + 0x18]
MOV       RSI,RAX
TEST      RAX,RAX

; 절대 점프하지 않음, RAX는 데이터 버퍼 주소
JZ        LAB_140e40f4d 
CMP       RBP,RDI
MOV       RDX,RBX
MOV       RCX,RAX
CMOVC     RDI,RBP
MOV       R8,RDI ; RDI는 14F3B0에 가까운 스택 주소이므로 memcpy 성공
CALL      memcpy

LAB_140e40f4d:
TEST      RBX,RBX
; 절대 점프하지 않음, RBX == RDX == 데이터 버퍼 주소, 0이 아님
JZ        LAB_140e40f62 

; 이제 144786150에서의 memcpy로 인해 이 RWE 메모리 영역을 완전히 제어합니다. RCE가 달성되었습니다!
MOV       RCX,qword ptr [DAT_144786998]
MOV       RDX,RBX
MOV       RAX,qword ptr [RCX]
CALL      qword ptr [RAX + 0x68]

이 가젯은 거의 모든 작업을 대신 수행합니다. 오프셋 0x18을 호출하여 memcpy 대상 포인터를 얻고, 패킷을 거기에 복사한 다음 144786998의 정적 객체에 대해 오프셋 0x68의 가상 함수를 호출합니다. 이제 memcpy 호출로 인해 해당 객체를 완전히 제어합니다. memcpy로 손상된 메모리 양이 많고 일부 영역은 다른 게임 스레드에 의해 지속적으로 쓰여지기 때문에, 익스플로잇은 먼저 안전한 위치에 복사되는 "설정" 페이로드를 로드하고, 다른 모든 스레드를 일시 중단한 후 실제 페이로드를 다시 복사한 다음 점프합니다. 자세한 내용은 rce.h를 참조하십시오.

추가 정보

개념 증명 코드의 소스 코드를 확인하는 것을 추천합니다. 패킷 구조를 자세히 설명하는 많은 주석이 있습니다. 각 단계에서 실시간으로 무슨 일이 일어나는지 보고 싶다면(정말 멋집니다!), 게임을 디버거에서 실행하면서 다음 관심 주소에 중단점을 설정한 상태에서 개념 증명 DLL을 주입할 수 있습니다:

140ca5960

기본적으로 익스플로잇이 시작되는 곳입니다. 이 함수는 PushRequestVisit 메시지의 크기 구분 항목 목록 데이터를 파싱합니다. 먼저 목록의 각 항목을 다른 벡터로 추출합니다:

root@kitploit:~
0x140ca59f8:
    player_data_cpy_ptr = (std_vector *)VectorCopy2_140ca4ef0(&player_data_cpy,player_data);
    FUN_140ca5010(player_data_cpy_ptr,&spawn_data,0x1c);
    FUN_140ca5010(player_data_cpy_ptr,&unk,4);
    FUN_140ca4fa0(player_data_cpy_ptr,&nrssr_data);

함수 140ca5010은 항목 크기를 확인하지만, 140ca4fa0은 가변 크기 항목용이며 크기 필드에 대한 정합성 검사를 수행하지 않습니다(버그 #1). 위에서 설명한 임의 코드 리디렉션 익스플로잇을 달성하려면 이를 14F3B0으로 설정해야 합니다. 이로 인해 약 1.3MiB의 경계를 벗어난 읽기가 발생하지만 메모리 페이지는 액세스 위반을 피할 수 있을 만큼 커야 합니다.

140ca56b0

이 함수는 이전 함수에 의해 nrssr_data를 인수로 호출됩니다. 스택에 DLMemoryInputStream 객체를 생성한 다음 NRSSR 파서에 인수로 전달합니다.

141955f50: ParseNRSessionSeachResult

NRSessionSearchResult 파서입니다. NRSSR 서명 및 버전 번호를 확인하고(14196a0f0), 속성 목록(14196a260), 호스트 이름(14195603a) 및 추가 정보를 파싱합니다(rce.h 참조).

14195603a

위 함수에서 호스트 이름을 안전하지 않게 복사하는 루프입니다(버그 #2). 버퍼 오버플로 중에 무슨 일이 일어나는지 추적하는 데 도움이 되는 몇 가지 주소는 다음과 같습니다:

  • 파서 스택 버퍼 주소: 14F128
  • DLMemoryInputStream 스택 주소: 14F3A0
  • 덮어쓰기 후 DLMemoryInputStream vtable 포인터: 1439e8b30
  • DLInputStreamReader에서 사용하는 DLMemoryInputStream의 가상 함수 오프셋: 0x18

1439e8b48

root@kitploit:~
MOV       RCX,qword ptr [RCX + 0x8]
MOV       RAX,qword ptr [RCX]
JMP       qword ptr [RAX + 0x40]

덮어쓴 메모리 스트림 vftable로 인한 첫 번째 코드 리디렉션 후 도달하는 곳입니다. 여기서 가상 호출 리디렉션 체인이 시작됩니다.

Footnotes

  1. 다크 소울 III Ver. 1.15 기준. 최대 이론적 페이로드 크기는 스택 레이아웃에 따라 달라지므로 게임 및 버전에 따라 다릅니다. ↩

도구 다운로드