Proxyman vs mitmproxy: Protobuf WebSocket 메시지 디코딩 방법
WebSocket 연결을 캡처했지만, 메시지가 임의의 바이너리 데이터처럼 보입니다. 앱이 유용한 정보를 주고받고 있다는 것은 알지만, 디버깅 도구에서는 그 값을 읽을 수 없습니다.
이 메시지는 Protocol Buffers 형식을 사용하고 있을 수 있으며, 보통 Protobuf라고 부릅니다. Protobuf는 빠르고 크기가 작지만, JSON처럼 사람이 읽기 쉽도록 설계된 형식은 아닙니다.
mitmproxy와 Proxyman 모두 Protobuf WebSocket 메시지를 디코딩하는 데 도움을 줄 수 있습니다. mitmproxy는 Protobuf 콘텐츠 뷰를 제공하며, .proto 정의 파일을 받을 수 있습니다. Proxyman은 macOS 앱 안에서 시각적인 메시지 목록, 빠른 Raw Mode, 디스크립터 기반 디코딩을 제공합니다.
이 가이드에서는 다음 내용을 배웁니다:
- WebSocket과 Protobuf의 개념
- Protobuf 메시지가 읽기 어려워 보이는 이유
- 현재 mitmproxy 방식이 동작하는 원리
- 스키마가 있거나 없는 상태에서 Proxyman으로 메시지를 디코딩하는 방법
- 자주 발생하는 디코딩 문제를 해결하는 방법
WebSocket이란 무엇인가요?
일반적인 HTTP는 보통 다음과 같은 단순한 흐름을 따릅니다:
- 앱이 요청을 보냅니다.
- 서버가 응답을 보냅니다.
- 통신이 끝납니다.
WebSocket은 하나의 연결을 계속 열어 둡니다. 앱과 서버는 언제든지 서로에게 메시지를 보낼 수 있습니다.
WebSocket은 다음과 같은 용도에 유용합니다:
- 채팅 메시지
- 실시간 가격
- 게임 이벤트
- 배달 및 차량 호출 상태 업데이트
- 알림
- 공동 편집
- 실시간 대시보드
WebSocket을 디버깅할 때는 보통 연결을 먼저 확인한 다음, 주고받은 메시지를 순서대로 하나씩 읽고 싶어집니다.
Protobuf 형식이란 무엇인가요?
Protobuf 형식은 Google이 만든 바이너리 데이터 형식입니다. JSON 같은 일반 텍스트 형식보다 더 적은 바이트로 구조화된 데이터를 저장합니다.
JSON 메시지는 다음과 같이 보일 수 있습니다:
{
"userId": 123,
"name": "Taylor",
"online": true
}
같은 데이터를 Protobuf 형식으로 인코딩하면 바이너리가 됩니다. 이를 텍스트로 열면 네모 상자, 알 수 없는 기호, 또는 긴 바이트 목록이 보일 수 있습니다.
이는 메시지가 깨졌다는 뜻이 아닙니다. 디버깅 도구는 Protobuf 규칙에 따라 이를 디코딩해야 합니다.
Protobuf 메시지는 왜 읽기 어려운가요?
Protobuf 메시지는 필드 번호와 값을 저장합니다. 사람이 읽기 쉬운 필드 이름은 보통 별도의 스키마에 들어 있습니다.
예를 들어 스키마는 다음과 같을 수 있습니다:
message UserStatus {
int64 user_id = 1;
string name = 2;
bool online = 3;
}
바이너리 메시지에는 1, 2, 3 필드가 들어 있을 수 있지만, user_id, name, online 같은 단어를 매번 반복해서 담을 필요는 없습니다.
이런 설계 덕분에 메시지 크기가 작아집니다. 하지만 디버깅에서는 문제가 생깁니다. 올바른 스키마가 없으면 도구는 대개 필드 번호와 가능한 값 타입은 보여 줄 수 있지만, 원래 필드 이름은 안정적으로 알 수 없습니다.
mitmproxy를 이용한 현재 해결 방법
mitmproxy는 WebSocket 트래픽을 지원합니다. 또한 바이너리 데이터를 보기 좋게 정리해 주는 Protobuf 콘텐츠 뷰도 포함하고 있습니다.
이름이 있는 필드를 표시하려면 mitmproxy의 protobuf_definitions 옵션을 사용합니다. 이 옵션에 .proto 파일 경로를 지정합니다:
mitmweb --set protobuf_definitions=/path/to/messages.proto
트래픽을 캡처한 뒤 WebSocket 플로우를 찾고, 메시지를 열어 Protobuf 콘텐츠 뷰가 자동으로 선택되지 않았다면 직접 선택합니다.
이는 실제로 내장된 기능입니다. 일반적인 Protobuf 보기를 위해 직접 Python 애드온을 작성할 필요는 없습니다.
mitmproxy는 다음과 같은 경우에 잘 맞습니다:
- 이미 트래픽 확인에 mitmproxy를 사용하고 있는 경우
- 터미널 옵션에 익숙한 경우
- 개발 프로젝트에
.proto파일을 보관하고 있는 경우 - 워크플로를 더 많이 자동화하거나 스크립트로 처리하고 싶은 경우
- 무료 오픈소스 프록시가 필요한 경우
mitmproxy 워크플로에서 어려울 수 있는 점은 무엇인가요?
초보자에게 가장 큰 어려움은 여러 요소를 하나로 연결하는 일입니다.
1. WebSocket 메시지가 스키마와 분리되어 있음
바이트를 캡처하는 것은 첫 단계일 뿐입니다. 실제 필드 이름을 보려면 올바른 .proto 정의도 필요합니다.
대규모 프로젝트에는 .proto 파일, import, 패키지, 메시지 타입이 많을 수 있습니다. 또한 이전 앱 버전이 여전히 예전 형식으로 메시지를 보내는 동안 스키마가 바뀔 수도 있습니다.
2. 설정에 옵션과 콘텐츠 뷰를 사용함
옵션 이름을 알고, 올바른 파일 경로를 지정하고, WebSocket 메시지를 찾은 다음, 적절한 콘텐츠 뷰를 선택해야 합니다.
익숙해지면 효율적입니다. 하지만 네트워크 디버깅을 처음 하는 개발자라면, 지금 보고 있는 메시지에서 바로 시작할 수 있는 눈에 보이는 메뉴가 더 편할 수 있습니다.
3. 메시지 하나를 찾는 데 시간이 걸림
활발한 WebSocket은 수백, 수천 개의 메시지를 보낼 수 있습니다. 유용한 데이터는 하트비트, 상태 업데이트, 그 밖의 이벤트 타입과 섞여 있습니다.
송수신 메시지를 명확한 타임라인으로 보면, 앱에서 한 가지 동작이 어떤 바이너리 메시지에 해당하는지 쉽게 연결할 수 있습니다.
4. 스키마가 없을 수 있음
앱을 디버깅할 수는 있지만 .proto 소스 파일이 가까이 없는 경우도 있습니다. 이때도 기본 필드 번호와 값을 안전하게 확인할 방법이 필요합니다.
Raw 디코딩으로 원래 이름을 복원할 수는 없지만, 페이로드가 Protobuf일 가능성이 높은지 알려 주고 필요한 값을 찾는 데 도움을 줄 수 있습니다.
Proxyman을 이용한 시각적 해결 방법
Proxyman macOS 6.5.0 이상에서는 WebSocket 메시지의 Protobuf 페이로드를 디코딩할 수 있습니다.
유용한 모드는 두 가지입니다:
- Raw Mode는 디스크립터 파일 없이 동작합니다. 필드 번호와 추론된 값을 보여 줍니다.
- Message Type은 가져온
.desc디스크립터 파일을 사용합니다. 스키마에 정의된 필드 이름과 타입을 보여 줄 수 있습니다.
캡처한 메시지로 바로 시작한 다음, 가지고 있는 스키마 정보의 양에 따라 방법을 고를 수 있습니다.
1단계: WebSocket 연결 캡처하기
macOS용 Proxyman을 다운로드하고 설치합니다. 보안 wss:// 연결을 복호화해야 할 때는 인증서를 설치하고 신뢰하도록 설정합니다.
Google Chrome에서 연 WebSocket이라면 Proxyman을 먼저 실행한 다음 웹사이트를 엽니다. Proxyman은 Chrome에서 발생하는 일반 운영 환경의 WebSocket 트래픽을 자동으로 캡처할 수 있습니다.
메시지를 보내는 동작을 수행합니다. 예를 들어 채팅 메시지 하나를 보내거나 실시간 설정 하나를 변경합니다. 테스트를 작게 유지하면 해당 프레임을 더 쉽게 찾을 수 있습니다.

WebSocket 플로우를 선택합니다. WebSocket 탭에서 클라이언트가 보낸 메시지와 서버에서 받은 메시지를 확인할 수 있습니다.
테스트가 허용된 WebSocket 트래픽만 검사하세요. 메시지에는 계정 세부 정보, 세션 토큰, 개인 정보 또는 비공개 비즈니스 정보가 포함되어 있을 수 있습니다.
2단계: 스키마 없이 Raw Mode 사용해 보기
Protobuf 디스크립터가 없을 때 메시지를 가장 빠르게 확인하는 방법은 Raw Mode입니다.
- WebSocket 메시지를 선택합니다.
- 메시지를 마우스 오른쪽 버튼으로 클릭합니다.
Protobuf→Decode를 선택합니다.Raw Mode를 선택합니다.

Proxyman은 바이너리 페이로드를 읽고 추론할 수 있는 필드를 표시합니다.

Raw Mode로 다음을 확인할 수 있습니다:
- 필드 번호
- 숫자
- 텍스트 값
- 불리언과 비슷한 값
- 중첩된 바이너리 값
Raw Mode는 필드의 원래 이름이 user_id나 message_text였는지 알 수 없습니다. 이런 이름을 알려면 스키마가 필요합니다.
3단계: 전체 필드 이름을 위한 디스크립터 준비하기
Proxyman은 .desc 확장자를 가진 Protobuf 파일 디스크립터를 사용합니다. 디스크립터에는 디코더가 필요로 하는 컴파일된 스키마 정보가 들어 있습니다.
가장 쉬운 방법은 백엔드 또는 앱 팀에 최신 디스크립터 파일을 요청하는 것입니다.
.proto 소스 파일이 있다면 protoc으로 디스크립터 하나를 만들 수 있습니다. 이 예시에는 import된 정의도 포함됩니다:
protoc \
--descriptor_set_out=output.desc \
--include_imports \
-I=/path/to/proto/files \
/path/to/proto/files/*.proto
macOS에 protoc이 설치되어 있지 않다면, 먼저 Protobuf 명령줄 도구를 설치하세요:
brew install protobuf
디스크립터는 안전한 개발 폴더에 보관하세요. 스키마에는 내부 메시지 이름과 데이터 구조가 드러날 수 있으므로, 팀에서 허용하지 않는 한 공개하지 마세요.
4단계: Proxyman에 디스크립터 가져오기
다음 메뉴를 엽니다:
Tools → Protobuf Schema
+ 버튼을 클릭하고 .desc 파일을 선택합니다. Proxyman은 사용 가능한 패키지 이름과 메시지 타입을 읽어 옵니다.
WebSocket 메시지에서 시작해도 됩니다. Protobuf → Decode → Message Type을 선택한 다음, 예상한 타입이 없으면 가져오기 버튼을 사용하세요.

5단계: 올바른 메시지 타입 선택하기
페이로드에서 사용하는 루트 메시지 타입을 선택합니다. 스키마에 패키지 이름이 정의되어 있다면 패키지 이름도 포함하세요.
예를 들면 다음과 같습니다:
chat.events.NewMessage
올바른 타입을 선택하면 Proxyman이 필드 이름과 함께 페이로드를 디코딩합니다.

이제 바이너리 값을 앱의 동작과 연결해 볼 수 있습니다. 원시 바이트에 숨어 있던 ID, 타임스탬프, enum 값, 중첩 메시지, 텍스트 필드를 확인할 수 있습니다.
Protobuf WebSocket 메시지를 위한 Proxyman vs mitmproxy
| 항목 | mitmproxy | Proxyman |
|---|---|---|
| WebSocket 트래픽 캡처 | 지원 | 지원 |
| 내장 Protobuf 뷰 | 지원 | 지원 |
| 스키마 없이 확인 | Protobuf 콘텐츠 뷰에서 Raw 필드 표시 가능 | 메시지 메뉴의 Raw Mode |
| 스키마 형식 | .proto 파일 | 컴파일된 .desc 디스크립터 |
| 설정 방식 | 옵션과 콘텐츠 뷰 워크플로 | 시각적 메뉴와 스키마 관리자 |
| 메시지 확인 | 콘솔 또는 mitmweb 인터페이스 | 네이티브 송수신 메시지 목록 |
| 적합한 용도 | 터미널, 오픈소스, 자동화 워크플로 | macOS에서 대화형 디버깅 |
두 도구 중 하나만 Protobuf를 지원하는 것이 아닙니다. 둘 다 지원합니다. 차이는 워크플로에 있습니다.
mitmproxy는 이미 터미널이나 웹 인터페이스에서 작업하는 개발자에게 좋은 선택입니다. Proxyman은 캡처한 프레임을 클릭해 Raw Mode를 먼저 시도한 다음, 메시지 타임라인을 벗어나지 않고 디스크립터를 추가하고 싶을 때 유용합니다.
자주 발생하는 문제와 해결 방법
연결은 보이지만 메시지를 읽을 수 없어요
먼저 WebSocket 호스트에 대해 SSL Proxying이 켜져 있는지 확인하세요. 기존 보안 연결은 여전히 암호화되어 있을 수 있으므로, 설정을 켠 뒤에는 다시 연결하세요.
그다음 페이로드가 실제로 Protobuf를 사용하는지 확인하세요. 바이너리 WebSocket 데이터는 압축되거나 암호화되어 있을 수도 있고, MessagePack 같은 다른 형식일 수도 있습니다.
Raw Mode에 숫자만 보이고 필드 이름이 없어요
이는 예상된 동작입니다. Protobuf는 모든 바이너리 메시지에 원래 필드 이름을 포함하지 않습니다. 최신 .desc 파일을 가져오고 올바른 Message Type을 선택하세요.
디코딩된 값이 이상해요
선택한 루트 메시지 타입이 잘못되었을 수 있습니다. 해당 이벤트에서 사용하는 타입을 시도해 보세요. 클라이언트에서 서버로 보내는 메시지와 서버에서 클라이언트로 보내는 메시지는 서로 다른 타입을 사용할 수도 있습니다.
페이로드의 Protobuf 바이트 앞에 커스텀 래퍼가 붙어 있을 수도 있습니다. 처음 몇 바이트가 메시지 길이, 이벤트 타입, 또는 압축을 나타내는 경우 앱 팀에 확인하세요.
일부 필드가 보이지 않아요
디스크립터가 앱이나 서버보다 오래되었을 수 있습니다. 이전 스키마를 삭제하고, 현재 .proto 파일에서 새 .desc 파일을 생성한 다음 다시 가져오세요.
Protobuf는 기본값을 가진 필드를 전송하지 않는다는 점을 기억하세요. 선택 필드가 보이지 않는 것은 정상일 수 있습니다.
iOS 앱의 WebSocket을 캡처할 수 없어요
일부 iOS WebSocket API는 일반 HTTP 프록시 설정을 따르지 않습니다. 앱에서 URLSessionWebSocketTask를 사용한다면 Proxyman SOCKS Proxy 가이드를 따르거나, 직접 개발하는 앱에 한해 디버그 전용 Atlantis 프레임워크를 사용하세요.
메시지 목록이 너무 복잡해요
세션을 지우고 동작 하나를 반복하세요. 동작한 시각을 기록한 다음, 근처의 송수신 메시지를 확인하세요. 몇 분 동안 실시간 트래픽을 기록하는 것보다 작은 테스트가 훨씬 쉽습니다.
마치며
WebSocket은 메시지를 실시간으로 계속 주고받을 수 있게 하고, Protobuf는 그 메시지를 작게 유지합니다. 두 기술은 함께 앱에 훌륭하지만, 캡처한 바이너리 데이터는 이해하기 어려울 수 있습니다.
mitmproxy는 콘텐츠 뷰와 .proto 정의를 사용해 Protobuf를 디코딩할 수 있습니다. 터미널 옵션과 자동화된 워크플로를 선호하는 개발자에게 강력한 도구입니다.
Proxyman은 macOS 개발자에게 실시간 WebSocket 메시지에서 디코딩된 값까지 이어지는 시각적인 경로를 제공합니다. 스키마가 없다면 Raw Mode로 시작하세요. 전체 필드 이름이 필요하다면 .desc 디스크립터를 가져오고 올바른 Message Type을 선택하세요.
다음으로 볼 내용
- Proxyman WebSocket 문서를 읽어 보세요
- Proxyman Protobuf 문서를 읽어 보세요
- 자세한 Protobuf WebSocket 메시지 디코딩 튜토리얼을 따라 해 보세요
- iOS 시뮬레이터와 실제 기기에서 WebSocket 트래픽을 캡처하는 방법을 알아보세요
- mitmproxy Protobuf 옵션을 확인하세요
- mitmproxy WebSocket 제한 사항을 확인하세요

