귀뚜라미보일러 IoT 온도 조절기

작년 즈음 귀뚜라미보일러에서 Wi-Fi를 통해 스마트폰 제어가 가능한 온도 조절기를 발매했다는 소식을 들었습니다만,
아쉽게도 최신형 보일러에서만 사용할 수 있어 써볼 수가 없었습니다.

어느새 기억에서 잊혀져가고 있다가, 최근 클리앙의 한 사용기를 보고 구형 보일러에서도 사용이 가능하다는 것을 알게 되었죠.

그런데 해당 사용기에서 MITM (Man in the middle, 중간자 공격)에 취약하다는 언급이 클리앙 새로운 소식의 뉴스로 등장하게 되면서, 유저들의 수 많은 질타를 받게 됩니다.

현 시점에서는 안드로이드 사용자에 대해서만 HTTPS를 사용하도록 API가 새로 만들어지고, App이 업데이트 되었습니다.

Philips의 Hue, Xiaomi의 Home Gateway를 사용하고 있는 중인데, 국내 IoT 제품들의 완성도를 구경해보고도 싶고, 무엇보다 최근에 발견한 Homebridge라는 Node.js 기반 Homekit API 에뮬레이션 서버에 붙여보고 싶은 욕심이 들더라고요.

그래서.. 질렀고, 어제 수령해서 설치했습니다.

개봉기

패키지

패키지는 A4 크기의 소설책 한권 수준의 크기입니다.

패키지 옆면

보일러 및 온도 조절기에 따라 신형 (NCTR-10WIFI) / 순간식 (CTR-15WIFI) / 저탕식 (CTR-15WIFI) 로 구분됩니다.
CTR-15WIFI의 경우 순간식/저탕식 구분이 있습니다만, 기기 내 별도 설정 메뉴에 접근해서 변경이 가능합니다.

저는 순간식 모델입니다.

패키지 구성

패키지 구성은 온도 조절기 본체, 백플레이트, 백플레이트 고정 나사, 설명서가 전부입니다.
저는 패키지에 포함된 백플레이트 고정 나사가 짧아서 기존 나사를 재활용했습니다.

기존 온도 조절기가 설치되어 있는 모습

기존 온도 조절기가 설치되어 있는 모습니다. (좌하단)

CTR-5700PLUS 조절기

기존에 설치된 온도 조절기는 CTR-5700PLUS 모델인데, 이 모델은 보일러 모델에 따라 순간식/저탕식이 나뉩니다.

CTR-5700PLUS 조절기 후면

백플레이트는 기본적으로 장착된 상태인데, 탈거한 모습입니다. 캐패시터가 꽤 눈에띄네요.

CTR-15WIFI 옆면

옆을 보면 본체 하우징을 탈거할수 있는 홈이 보입니다.
탑 (액정측)하우징에는 걸쇠가, 바텀 하우징에는 홀이 있고 걸쇠가 홀에 걸치는 방식으로 본체 하우징이 고정되는 방식입니다.

CTR-15WIFI 기판

궁금한걸 못참는 공돌이라 설치 전에 후다닥 뜯어봅니다.
역시나 캐패시터가 크고 아릅답..지는 않습니다.

CTR-15WIFI 기판 옆면

궁금헸던 MCU와 Wi-FI 칩셋은 디스플레이 아래에 위치하고 있습니다.
아쉽게도 디스플레이 패널 상단이 납땜되어 있어 들어내서 확인할 수는 없었습니다.

CTR-15WIFI 기판 상단부

좌상단에 두번째 통신 채널 혹은 디버깅용 포트로 추정되는 홀이 보이네요.

CTR-15WIFI 기판 하단부

역시나 캐패시터가 눈에 띕니다.
좌하단엔 온도 측정을 위해 사용되는 것으로 추정되는 서미스터 (thermistor)가 보입니다.

설치기

전원 끄기

안전을 위해 절연장갑을 착용하고, 기존에 사용중이던 온도 조절기는 전원버튼을 눌러서 끕니다.

온도조절기 탈거

온도조절기를 위쪽으로 밀어 백플레이트에서 본체를 탈거합니다.

탈거된 온도조절기
탈거된 온도조절기 - 2

본체에 표시된 극성을 잘 메모해둡니다. (근데 저는 메모했는데도 실수했습니다 ㅋㅋㅋ)

신기하게도 두 온도조절기에 적혀있는 QA 담당자로 추정되는 분의 이름이 똑같습니다 ㅎㅎ

제 것은 얇은 단심으로 된 전선인데, 많이 꼬아진 상태로 스트레스를 많이 받았는지 살짝 움직이니 끊어져버렸습니다.

온도 조절기에서 사용하는 두가닥의 전선

나사를 풀어 기존 온도조절기에 고정된 전선을 빼냅니다.

기존 백플레이트와의 비교샷

새 온도조절기와 기존 온도조절기의 크기가 다르기 때문에 백플레이트도 교체해야합니다.
다행히 스크류 홀의 위치는 똑같아서 특별한 이슈 없이 교체할 수 있습니다.

기존 백플레이트 탈거

기존 백플레이트 탈거 후 새로운 백플레이트를 설치합니다.
(새 백플레이트 설치 사진은 깜빡하고 안찍었네요.. ㅠㅠ)

와이어 스트리퍼로 피복 벗겨내기

새 온도조절기와 결선을 하기 전에, 기존 전선의 길이가 애매해서 정리하기로 합니다.
일단 여러 전선을 감싸고 있는 첫 피복을 와이어 스트리퍼로 벗겨냅니다.

전선 정리

두가닥 전선의 길이를 니퍼로 깔끔하게 맞춰줍니다.

와이어 스트리퍼로 피복 벗겨내기

각 전선은 연결을 위해 와이어 스트리퍼로 피복을 다시 벗겨냅니다.

접속부 쉽게 꺼내기

드라이버를 사용하면 쉽게 빠지지 않는 접속부를 쉽게 꺼낼 수 있습니다.

온도 조절기와 기존 전선 연결

온도 조절기와 기존 전선을 연결합니다.

쨔잔

쨔잔! 연결 즉시 전원이 잘 들어오는걸 확인할 수 있습니다.
이 상태에서 전원버튼을 눌러서 잠시 꺼두고 (온도조절기가 실제로 꺼지는 건 아니였습니다..)

합체

백플레이트와 결합합니다.

끝

설치가 잘 되었습니다.

(이때까지만 해도 연동에서 말썽을 일으킬 줄은 상상도 못했습니다….)

연동기

연동을 위한 AP 모드 진입은 쉽습니다.

AP모드 진입 방법

전원을 끈 상태에서 예약설정/수온설정 버튼을 5초간 누르고 있으면…

AP모드 진입중
AP모드 진입중 - 2

설정을 위한 AP 모드로 진입합니다. 현재 상태에서는 검색은 되나 접속이 되지 않습니다.

AP모드 활성화

30초정도 기다리면 AP모드 활성화를 의미하는 파란 안테나 아이콘이 LCD 우상단에 표시됩니다.
이 상태에서 앱에서 초기 연동 관련 설정을 진행하면 됩니다.

물론 호기심이 발동해 바로 연동 관련 설정을 진행하지 않았습니다 (…)

호기심 발동

일단 AP 모드에서 어떤 포트가 열려있는지 궁금했습니다.

설정을 위한 AP Mode라 하더라도 AP Mode는 이름 그대로 Access Point Mode이므로 Wi-Fi 되는 디바이스면 다 붙을수 있지요.
맥북으로 붙어봅니다.

DHCP로 192.168.200.2 ~ 192.168.200.254 대역을 할당해서 쓰네요.
게이트웨이는 192.168.200.1 이므로 192.168.200.1 호스트에 대해 스캔을 해보면…

스캔 결과

80번이 열려있네요. 아마 시스템 관련 설정페이지겠지요.

curl으로 빠르게 훑어보면…

80번 포트의 컨텐츠

역시나 디바이스 관련 설정이 있습니다.
펌웨어 버전, MAC Address, 디바이스 재부팅 버튼, 펌웨어 업데이트 버튼이 보이네요.

어드민 페이지

브라우저에서 보면 이렇습니다.

lwIP로 구현된 웹서버

lwIP를 서버로 쓰고 있네요. lwIP는 BSD 라이선스인데 별도로 저작권자 표기를 발견하지는 못했습니다.
아쉬운 부분입니다.

더 볼 것이 없어서 스킵을 하려고 하는데….

문제 발생

다운

호스트, 그러니까 온도 조절기가 먹통이 되는 사태가 발생했습니다. 다량의 패킷을 쏴서 그런지 먹통이 된 것 같네요.

전원버튼을 눌러서 전원을 끄고 다시 AP 모드에 진입을 수십번 반복했지만, 파란색 안테나 아이콘이 뜨지 않습니다.
AP 모드 진입 후 파란색 안테나 아이콘이 뜨기까지 십분 이상 기다렸는데도 활성화가 되지 않습니다 -_-;;

아무리 생각해봐도 기기에 붙어있는 ‘전원 버튼’ 은 실제로 온도 제어기의 전원을 제어하는 것은 아닌 것 같아서,
리셋 버튼이나 리셋 핀을 찾아보았습니다만 그 어디에도 없었습니다.

이 상황에서 확실히 온도 제어기를 껐다가 다시 켜는 방법은 연결된 보일러를 끄는 것이라 유일하다고 판단을 했는데…
장식장 뒤편에 보일러실이 있어서 보일러 전원 코드를 뽑을수도 없는 상황이었습니다.

백플레이트 뜯어내서 연결된 전선을 끊고 다시 연결할까 생각해봤지만…
귀찮아서 결국 보일러랑 연결된 차단기를 찾아서 차단기를 내렸다가 다시 올렸습니다.

그랬더니 예상대로 AP모드가 잘 활성화가 되었습니다.

근데 이번에는 아이폰에서 공유기 연결을 위한 초기 설정이 안됩니다.
iPhone 6 Plus / iOS 10.0.2 를 사용하고 있는데, 아이폰에서 AP모드를 붙고 나서 설정을 시도하면 또 먹통이 됩니다… -_-;;;;

차단기 내리고 다시 설정… 을 몇번 반복하다가 그냥 노는 안드로이드 단말 찾아서 설정하니 한방에 붙네요.

허탈했습니다….

정상적으로 붙은 상태
정상적으로 붙은 상태

공유기에 정상적으로 붙으면 이렇게 PASS 텍스트가 찍힙니다.

연동 완료

회원가입후 기기 등록까지 완료되면 최종적으로 이런 화면이 표시됩니다.

패킷 훔쳐보기

개인적으로 insecure/suspicious 하다고 판단되는 장비들은 별도의 Bridged AP로 고립시키고,
필요시 tcpdump를 통해 언제든지 패킷스니핑이 가능하도록 내부망을 운용하고 있습니다.

Internal wireless infrastructure

대강 요런 모습입니다.

산딸기

저렇게 Raspberry pi (무려 1세대 Model B 모델!)에 iptime N300UA를 붙여서 Soft AP를 만든것이죠.

App에서 오고 가는 HTTP/HTTPS 패킷들은 주로 Charles Proxy를 통해 쉽고 간편하게 훔쳐볼 수 있고,
breakpoint를 걸어 request/response를 마음대로 제어할 수 있기 때문에 앱에서 사용하는 HTTP/HTTPS
기반 API를 뜯어보기에 좋습니다.

AP 모드에서 초기 연동 설정을 하기 이전에
tcpdump over ssh + wireshark 조합으로 온도 조절기에서 오고 가는 패킷들을 캡쳐하고,
Chalres Proxy를 통해 App에서 오고 가는 패킷들을 캡쳐하도록 설정했습니다.

온도 제어기 – 서버간 패킷 훔쳐보기

초기 연결 이후 패킷 흐름

(공유기 연결 이전에 캡쳐를 시작했기 때문에, DHCP로 IP를 받아오고, ARP 응답을 하는 것이 보입니다.)

초기 공유기와 연결이 되면, TCP를 통해 서버에 기기 등록 관련 메세지를 즉시 보냅니다. 재미있는 점은, 별도로 DNS 질의를 하지 않는다는 것입니다.
즉, 도메인이 아닌 IP가 펌웨어에 하드코딩 되어 있습니다. 물론 IP가 변경될 일은 없겠지만요.
Round-robin DNS로 load balancing을 할 수도 있을거란 생각을 해보지만, 사용자가 소규모라 따로 분산처리가 필요치 않다고 판단했겠지요. (Xiaomi의 경우에는 Round robin DNS를 위해 A 레코드에 18개 호스트가 등록되어 있습니다)

TCP 데이터 패킷 흐름

온도 제어기와 서버간 TCP 패킷은 암호화 되어있지 않습니다.

(다시 Xiaomi와 비교해보자면, Xiaomi는 Message Header는 암호화 되어 있지 않지만, message body는 AES-128-CBC로 암호화되어 오고갑니다.)

인터넷 상태 확인을 위한 DNS Query

또 재미있는것은, 인터넷 연결 상태를 확인하기 위해 2분마다 google.com 의 A 레코드를 조회하는 DNS Query를 보낸다는 것입니다.

어쩄거나 패킷이 암호화 되어 있지 않은 상태이기 때문에, 패킷의 구조나 명령 종류에 대해서는 패킷을 분석해보면 쉽게 알아낼 수 있을것입니다.

다르게 생각해보자면, 내부망에 침입이 가능한 경우 API를 통해서가 아니라 직접 디바이스로 명령 패킷을 보낼 수도 있다는 것이겠지요 🙂

패킷 수준의 분석은 일단 충분한 정보를 얻었다고 판단해 여기까지 진행하고, HTTP(S) API를 살펴보기로 합니다.

애플리케이션 – 서버간 HTTP(S) 요청/응답 훔쳐보기

애플리케이션 – 서버간 사용하는 API는 크게 두가지로 나뉩니다.

첫번째는, iOS App과 구버전 Android App에서 사용하는 HTTP API
두번째는, 업데이트된 최신 Android App에서 사용하는 HTTPS API
가 있습니다.

업데이트된 안드로이드 앱 – HTTPS API 뜯어보기

위에서 초기 연동에 문제가 있어서 안드로이드 앱을 사용했기 때문에,
안드로이드에서 사용하는 업데이트된 HTTPS API를 살펴보겠습니다.

HTTPS Request Overview

Charles Proxy로 가로챈 Request입니다. HTTPS Protocol을 확인할 수 있습니다.

SSLv3 protocol 사용 가능

HTTP에서 HTTPS로 변경은 되었으나, POODLE Attack 위험이 있는 SSLv3을 지원하고 있습니다.

Missing HSTS Header

또한, HSTS 헤더가 존재하지 않기 때문에 별도의 루트 인증서 설치 없이도 SSL MITM이 쉽게 가능합니다.

여전히 존재하는 일부 HTTP API

회원가입에서 사용하는 주소검색 API는 여전히 HTTP를 사용합니다.
이와 별개로, 우편번호 검색 API에서 질의가 exact match이기 때문에 질의에 공백이 들어가면 검색 결과가 나타나지 않고,
건물명이 포함된 우편번호를 검색하는 경우, 일부만 입력하면 검색결과가 나타나지 않아 불편합니다.

예: 귀뚜라미빌딩 으로 등록된 우편번호 검색시 귀뚜라미 만 입력하는 경우 검색 불가

아이디와 비밀번호가 세번 날아간다!

좀 아쉬운건, 아이디/비밀번호를 보내는 의미를 알 수 없는 분리된 API 3개가 존재한다는 것입니다.

위 이미지는 회원가입 이후 로그인시 날아가는 요청들인데, 각 역할은 다음과 같습니다.

  1. 회원가입 요청 / 단순 성공여부 응답
  2. API Access Key 생성 / 생성된 API Key 응답: UUID 형태이고, 본 Access Key는 첫 발급시 Cookie처럼 동작합니다 (!!!)
  3. 로그인 요청 (2. 에서 얻은 Token을 HTTP Header로 실어서 인증 성공시 해당 Access Token에 권한 부여, 이후에는 Access Key 발급 없고 Login API의 Response body의 JSON payload내 key field로 제공됨)

개인적으로는 1~3을 굳이 분리할 필요가 있나 생각이 듭니다. 단일 API로 통일해도 충분했을 것 같은데 말이죠 🙂
인증 매커니즘은 많이 아쉬운 부분입니다. 이건 그냥 Cookie 대신 Custom HTTP Header를 통해 세션키를 쓰겠다는 것과 다름없어 보이는데요.

명령 패킷

디바이스로 보낼 명령은 단일 API를 사용하는 것 같습니다.
실내온도 21도로 설정했을때의 요청입니다.

JSON payload 내 message field에 직접 Packet payload (in hex string) 를 실어 보내네요.
어디서 본 것 같다고 생각했는데 바로 위 TCP Packet Payload에서 봤던 그것이었습니다.

HTTPS 요청과 응답 역시 시간을 들여 분석해보면 더 자세한 것을 알 수 있을것입니다.
그래도 HTTP 대비 나아진 부분이 보이네요. 더이상 Response payload에 password가 포함되어 있지 않습니다.

iOS 앱 – HTTP API 뜯어보기

iOS 앱에서 사용하는 HTTP API를 위와 같은 방법으로 살펴보았으나,

  1. 도메인이 아닌 특정 IP로 접속을 요청하는 점
  2. API Protocol이 HTTPS가 아닌 HTTP인 점

이외에는 큰 차이가 없어 자세히 기술하지는 않습니다.

다음 할일

어느정도 내부를 뜯어보았으니, 이 다음은 Homebridge plugin을 만들어 Homekit에 붙여볼 차례입니다.

이 부분은 진행인 작업이고, 완료되면 별도 포스트로 찾아뵙도록 하겠습니다!