- 🇰🇷 번역상태: 2차 검토/수정 중
이 글은 저자의 허락을 받아 영문으로 된 원문을 한국어로 번역한 글입니다.
번역 오류 및 잘못된 내용에 대한 수정은 Pull Request를 부탁드려요.
다음 글도 번역에 참여하실 분은 코멘트 남겨주세요. (혼자 다 하려니 쉽지 않네요 ^^)
-
아래 글 번역에 참여한 이:
-
원본 문서: TouchEn nxKey: The keylogging anti-keylogger solution (2023-01-09)
-
번역 문서 최초 작성일: 2023-01-15 (PR을 통해 계속 업데이트 중)
일주일 전에 한국에서 필수적인 소위 보안 프로그램이라 불리는 것에 대해 글을 썼다. 이 여정은 라온시큐어에서 만든 TouchEn nxKey에 관심을 가지면서 시작되었는데, 왜냐하면 이 브라우저 확장 프로그램은 1000만 명 이상의 사람이 사용하고 있어서였다. 사실 크롬 웹 스토어에서 보여주는 다운로드 수는 최대 1000만까지지만, 이건 한국에서 사용되는 대다수의 컴퓨터에 설치되기 때문에 실제 사용자 수는 훨씬 더 많을 것으로 추정된다.
이렇게나 사용자가 많은 것은 사람들이 이 제품을 너무 좋아해서가 아니다. 실제 평가는 매우 좋지 않아서 별점 5점 만점에 평균 1.3점을 받았고, 많은 사용자들이 제발 이걸 없애달라고 요청하고 있다. 하지만 한국에서 온라인 뱅킹 등을 하려면 해당 제품의 사용이 필수적이다.
이 소프트웨어를 설치하도록 하는 은행에서는 이걸 통해 보안이 향상된다고 주장한다. 하지만 사용자들은 이것을 악성 프로그램 혹은 키로거라고 부른다. 내가 시간을 투자해 이 제품의 내부 동작을 분석해 봤는데, 후자의 주장이 진실에 더 가깝다고 판단된다. 이 프로그램에는 실제로 설계상 키로깅 기능이 포함되어 있으며, 외부에서 이 기능에 접근하는 것을 제대로 막고 있지 않다. 또한 단순한 서비스 거부부터 원격 코드 실행에 이르기까지 다양한 범위에 걸친 버그가 있다. 나는 이 제품에서 전부 7가지 보안 취약점을 신고했다.
- 배경
- TouchEn nxKey는 실제 어떻게 동작하는가?
- 웹사이트는 어떻게 TouchEn nxKey와 통신하는가?
- TouchEn 확장 프로그램을 악용하여 은행 웹사이트 공격하기
- 웹사이트에서 키로깅 기능 사용하기
- 애플리케이션 자체를 공격하기
- 도우미 (helper) 애플리케이션 악용하기
- 드라이버의 키로깅 기능에 직접 접근하기
- 과연 문제가 수정될까?
- nxKey에 적용된 개념이 제대로 동작할까?
한국의 현재 상황에 대해서 요약한 글을 쓴 후, 다양한 한국 웹사이트에서 내 글에 대해서 토론하기 시작했다. 특히 댓글 하나에서 내가 놓쳤던 꽤 중요한 정보를 발견했는데, 2005년에 한국 외환은행에서 발생했던 해킹 사건이었다. [1] [2] 이 기사는 기술적인 내용에 대해서 자세히 서술하지 않았지만, 이 사건에 대해 내가 이해한 바를 설명하도록 하겠다.
이 사건은 2005년 당시 한국에선 상당히 큰 사건이었던 것으로 보인다. 사이버 범죄조직이 원격접속 트로이 목마(Remote Access Trojan)를 사용해서 고객의 은행계좌에서 5000만원(당시 기준으로 약 5만 달러)을 빼돌렸다. 그들은 이 방법으로 고객의 로그인 정보뿐만 아니라 보안카드에 대한 정보도 같이 얻을 수 있었다. 내가 알기로 보안카드는 EU(유럽연합)에서 2차 인증 수단으로 사용하던 indexed TAN과 유사한데, EU에서는 트로이 목마를 통해 쉽게 뚫린다는 정확히 그 이유로 2012년에 폐기되었다.
고객의 컴퓨터는 어떤 경로를 통해 악성 프로그램에 감염되었을까? 설명된 내용을 보면, 브라우저의 보안 취약점을 공략하는 악성 웹사이트에 방문했을 때에 감염되는 드라이브 바이 다운로드(drive-by download) 공격이 사용된 것으로 보인다. 아니면 사용자가 직접 어떤 프로그램을 설치하도록 속였을 수도 있다. 사용된 브라우저의 이름은 명시되어 있지 않지만, 인터넷 익스플로러일 것이 뻔하다. 당시 한국에서 다른 브라우저는 거의 사용되지 않았기 때문이다.
위 기사는 고객이 온라인 뱅킹 로그인 정보를 분실하거나 누구에게 준 적이 없으므로, 고객은 잘못한 것이 없다고 강조하고 있다. 대신 온라인 뱅킹 자체의 전반적인 안전성에 대해서 의문을 제시하고, 은행이 충분한 보안 조치를 구현하지 않았다고 비판하였다.
2005년 당시에는 다른 나라에서도 유사한 이야기들이 많이 나돌았다. 오늘날에도 이러한 문제가 완전히 사라졌다고 말하기는 어렵지만, 이제는 훨씬 찾아보기 어렵다. 웹 브라우저의 보안이 많이 향상되기도 했지만, 한편으로는 은행에서 더 나은 2차 인증 방법을 도입했기 때문이다. 적어도 유럽에서는 2차 인증 장치가 있어야 거래를 완료할 수 있으며, 거래 승인 시에 거래의 세부내역을 볼 수 있기 때문에 실수로 악성 거래를 승인하기는 힘들다.
하지만 분노한 사람들이 빠른 해결책을 요구했고, 한국은 다른 길을 선택했다. 위의 두번째 기사가 당시의 문제점을 조명하고 있는데, 보안 애플리케이션을 통해 공격을 막을 수는 있지만 사용자는 이를 필수적으로 실행할 필요는 없었고 은행은 이를 허용했다. 결과적으로 은행은 "해킹 방지" 애플리케이션을 사용자에게 제공하고, 고객들에게 이를 필수적으로 실행하도록 하기로 하였다.
그렇기 때문에 TouchEn Key에 대한 첫번째 정보가 2006~2007년도부터 나오기 시작하는 것은 결코 우연이 아닌 것으로 보인다. TouchEn Key는 웹 페이지에 취급에 주의가 필요한 정보를 입력할 때 이를 보호해 줄 수 있다고 주장한다. 시간이 지나 마이크로소프트 유래의 브라우저가 아닌 타사 브라우저에 대한 지원이 추가되었고, 내가 살펴본 제품도 이것이다.
대중에 공개된 TouchEn nxKey에 대한 모든 자료에서는 이 제품이 키로거를 방지하기 위해 모종의 방법으로 키보드 입력을 암호화한다고 주장한다. 내가 찾을 수 있는 기술적인 내용은 이게 다였다. 그래서 동작 방식을 스스로 알아내야 했다.
TouchEn nxKey에 의존하는 웹사이트는 nxKey SDK를 사용하는데, 이는 웹사이트에서 실행되는 자바스크립트 코드와 서버에서 실행되는 코드 2가지로 나뉘어져 있다. 동작 방식은 다음과 같다.
- 사용자가 nxKey SDK를 사용하는 웹사이트에서 패스워드 필드에 패스워드를 입력한다.
- nxKey SDK 내의 자바스크립트 코드가 이를 인식하고 로컬에서 실행 중인 nxKey 애플리케이션에 알려준다.
- nxKey 애플리케이션이 윈도우 커널 상의 디바이스 드라이버를 활성화한다.
- 이제부터 디바이스 드라이버가 모든 키보드 입력을 가로챈다. 키보드 입력은 시스템에서 처리되는 대신 nxKey 애플리케이션으로 전송된다.
- nxKey 애플리케이션은 키보드 입력을 암호화하여 nxKey SDK의 자바스크립트 코드로 보낸다.
- 자바스크립트 코드는 암호화된 데이터를 숨겨진 폼의 필드에 입력한다. 실제 암호 입력란에는 더미 텍스트만이 입력된다.
- 사용자가 로그인 정보 입력을 마치고 "로그인" 버튼을 클릭한다.
- 암호화된 키보드 입력 정보가 다른 데이터와 함께 서버로 보내진다.
- 서버 상에서 동작하는 nxKey SDK는 입력 정보를 복호화해 평문으로 된 패스워드를 얻는다. 이제 일반적인 로그인 절차로 넘어간다.
그러니까 이론적으로는, 웹사이트에 입력된 데이터를 가로채려는 키로거는 암호화된 데이터만 볼 수 있다는 것이다. 웹사이트가 사용하는 공개키를 볼 수는 있지만, 거기에 상응하는 비공개키를 얻을 수는 없다. 그러니까 복호화는 불가능하고 암호는 안전할 것이다.
그렇다. 이론적으로는 꽤 괜찮은 아이디어이다.
웹사이트는 어떻게 특정 애플리케이션이 컴퓨터에 설치가 되어 있는지 알 수 있을까? 그리고 어떻게 그것과 통신을 하는 것일까?
이와 관련되서는 패러다임(역자 주: 구현 혹은 동작방식)의 변화가 일어나고 있는 것처럼 보인다. 원래 TouchEn nxKey는 자체 브라우저 확장 프로그램을 설치하는 것이 필수였다. 브라우저 확장 프로그램이 웹사이트의 요청 내용을 native messaging을 사용해서 애플리케이션에 전달하고, 그 응답 내용을 다시 웹사이트로 되돌려 주었다.
하지만 중간 매개체로 브라우저 확장 프로그램을 사용하는 것은 더 이상 최신 방식이 아니다. 요즘 방식은 웹사이트가 웹소켓 API(WebSockets API)를 사용해 애플리케이션과 직접 통신하는 것이다. 더 이상 브라우저 확장 프로그램이 필요하지 않다.
이런 동작 방식의 변화가 언제 발생했는지 모르겠지만, 아직 완성과는 거리가 멀다. 한국씨티은행 같은 몇몇 웹사이트의 경우 웹소켓을 사용한 방식만 쓰고 있지만, 부산은행과 같은 다른 웹사이트는 아직 브라우저 확장 프로그램에 의존하는 예전 방식을 유지하고 있다.
이것은 사용자가 여전히 브라우저 확장 프로그램을 설치해야 한다는 것만을 의미하지 않는다. 소프트웨어를 설치해도 제대로 인식이 되지 않는다는 잦은 불만에 대해서도 설명하고 있는 것이다. 이러한 사용자들은 웹소켓 통신을 지원하지 않는 옛날 버전의 소프트웨어를 설치한 것이다. 자동 업데이트 기능도 없다. 일부 은행은 아직도 옛날 버전을 다운로드하도록 하고 있는데, 나도 처음에 잘 모르고 옛날 버전을 다운로드했다.
TouchEn 브라우저 확장 프로그램은 정말 작고 최소한의 기능만 있다. 그렇기 때문에 악용할 소지가 별로 없어야 하지만, 코드를 보면 다음과 같이 주석 처리된 내용이 있다.
result = JSON.parse(result);
var cbfunction = result.callback;
var reply = JSON.stringify(result.reply);
var script_str = cbfunction + "(" + reply + ");";
//eval(script_str);
if(typeof window[cbfunction] == 'function')
{
window[cbfunction](reply);
}
어떤 사람이 무언가를 하기 위해 아주 끔찍한 방식(사실 위험하다는 뜻)으로 설계했다. 그리고 나서 eval()
을 사용하지 않고도 기능 구현이 가능한 것을 깨달았거나, 혹은 누군가 그 부분을 지적한 것처럼 추정된다. 하지만 나쁜 코드를 삭제하는 대신에, 혹시 모른다는 이유로 주석 처리만 했다. 내가 보기에, 이건 솔직히 말해서 자바스크립트, 보안, 버전 관리에 관해 아주 잘못 이해했다고밖에 해석할 수가 없다. 나만 그런 건지도 모르겠지만, 나라면 엄격한 감독 없이는 이 코드를 작성한 사람이 보안 관련 제품을 만들지 못하도록 할 것이다.
어쨌든 위험한 eval()
호출은 이미 브라우저 확장 프로그램에서 제거되었다. 은행 웹사이트에서 사용하는 nxKey SDK의 자바스크립트 부분은 그리 많지 않아서, 아직 큰 문제가 발생하지는 않았다. 하지만 이렇게 낮은 품질의 코드를 보면, 다른 문제들도 더 많을 것으로 예상된다.
그리고 역시나 유사한 문제를 콜백 처리 방식에서도 발견하였다. 웹사이트에서는 이벤트 등록을 위해 애플리케이션에게 setcallback
요청을 호출할 수 있다. 이벤트가 발생하면 애플리케이션은 확장 프로그램에 해당 페이지에 등록된 콜백 함수를 호출하도록 지시한다. 간단히 말하면 해당 페이지에 있는 어떤 글로벌 함수도 이름으로 호출할 수 있다.
그러면 악성 웹사이트가 다른 웹사이트를 대신해 콜백을 등록할 수 있을까? 이것을 위해서는 2가지 장애물이 있다.
- 목표 웹페이지가
id="setcallback"
을 포함한 엘리먼트(element)를 가지고 있어야 한다. - 콜백은 특정 탭으로만 전달된다.
첫번째 장애물은 기본적으로 nxKey SDK를 사용하는 웹사이트만 공격받을 수 있다는 의미이다. 브라우저 확장 프로그램을 통해서 통신할 때는 이것을 통해 필요한 엘리먼트를 생성한다. 웹소켓을 사용하여 통신할 때는 이 엘리먼트가 생성되지 않으므로, 최신 nxKey SDK를 사용하는 웹사이트는 영향을 받지 않는다.
두번째 장애물은 현재 브라우저 탭에서 로딩된 페이지(예를 들면 프레임 안에 로딩된 페이지)만 공격할 수 있다는 뜻이다. 만약에 nxKey 애플리케이션을 속여서 응답에 잘못된 tabid
값을 설정하지 않는다면 말이다.
이 공격방식은 놀라울 정도로 쉬웠다. 애플리케이션은 제대로 된 JSON 파서를 사용해 입력되는 데이터를 처리하지만, 응답은 sprintf_s()
를 호출해서 생성한다. 이스케이프 처리 같은 건 전혀 없다. 그렇기 때문에 응답에 포함된 일부 속성(property)을 조작해서 따옴표를 추가하면 임의의 JSON 속성을 삽입할 수 있다.
touchenex_nativecall({
…
id: 'something","x":"y'
…
});
이 id
속성은 애플리케이션의 응답 내용에 복사된다. 즉, 응답 내용에 원래 없던 JSON 속성인 x
가 갑자기 추가된다는 뜻이다. 이 취약점을 통해서 tabid
에 아무 값이나 넣을 수 있다.
그렇다면 악성 웹페이지는 은행 웹사이트를 접속한 브라우저 탭을 어떻게 알 수 있을까? 자신의 tab ID(TouchEn 확장 프로그램에서 노출해 줌)를 사용해서 다른 tab ID를 추측해볼 수 있다. 아니면 그냥 공란으로 남길 수도 있다. 이럴 경우 브라우저 확장 프로그램이 의도치 않은 도움을 준다.
tabid = response.response.tabid;
if (tabid == "")
{
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, response, function(res) {});
});
}
따라서 tabid
의 값이 비어 있다면 현재 활성화된 탭에 메시지를 전달한다.
이것으로 다음과 같은 공격이 가능할 수 있다.
- 은행 웹사이트를 새 탭에서 연다. 그럼 그것이 활성화된 탭이 된다.
- 해당 페이지가 로딩될 때까지 기다리면,
id="setcallback"
엘리먼트가 생겨난다. - TouchEn 확장 프로그램을 통해
setcallback
메시지를 보내어 어떤 함수를 콜백으로 등록하면서, 동시에"tabid":""
와"reply":"malicious payload"
라는 JSON 속성을 덮어쓴다.
첫번째 콜백 호출은 즉시 실행된다. 따라서 은행 웹사이트가 콜백 함수를 호출하면서 그 매개변수로 악성 페이로드가 포함된 reply
속성을 사용하게 된다.
거의 끝까지 왔으니 조금만 더 따라오시라. 실행 가능한 콜백 함수 중에 eval
이 있지만, 마지막 장애물이 있다. TouchEn은 reply
속성을 콜백으로 넘기기 전에 해당 속성을 JSON.stringify()
로 처리한다. 그래서 실제로는 eval("\"malicious payload\"")
이 실행되니까 아무런 동작도 하지 않는다.
그런데 만약에 목표 페이지가 jQuery를 사용한다면? $('"<img src=x onerror=alert(\'Hi,_this_is_JavaScript_code_running_on_\'+document.domain)>"')
을 호출하면 원하는 결과를 얻을 수 있다.
공격이 성공하려면 jQuery가 있어야 한다는 가정은 비현실적일까? 그렇지 않다. TouchEn nxKey를 사용하는 웹사이트는 TouchEn Transkey(화상 키보드)도 같이 사용할 가능성이 높은데, 이 물건은 jQuery를 사용하고 있다. 전반적으로 한국의 모든 은행 사이트들은 jQuery에 대한 의존도가 매우 높아 보이는데, 이는 좋지 않다(bad idea).
하지만 nxKey SDK에서 사용하는 지정 콜백인 update_callback
또한 JSON 문자열로 변환된 임의의 자바스크립트 코드를 실행하는 데 악용할 수 있다. 다음과 같이 update_callback('{"FaqMove":"javascript:alert(\'Hi, this is JavaScript code running on \'+document.domain)"}')
코드를 호출할 경우 javascript:
링크로 리다이렉트 시도를 하며, 그 부수효과로 임의의 코드를 실행할 수 있다.
이 공격으로 악성 웹사이트가 TouchEn 확장 프로그램을 사용하는 임의의 웹사이트를 탈취(compromise)할 수 있다. 그리고 한국의 은행에서 필수적으로 설치하도록 하는 어떤 "보안" 애플리케이션도 이 공격을 감지하거나 막을 수 없다.
내가 테스트를 시작했을 무렵, 크롬 웹스토어에는 TouchEn 확장 프로그램이 2개가 있었다. 대체적으로 유사하지만, 사용자가 더 적은 확장기능은 그 뒤 삭제되었다.
이것이 이 이야기의 끝은 아니다. 나는 이것과 거의 동일한 확장 프로그램 3개를 더 발견했다. 이니세이프(INISAFE)에서 만든 CrossWeb EX와 Smart Manager EX, 그리고 이니라인(iniLINE)이 만든 CrossWarpEX가 있다. 이 중에서 가장 인기 있는 것은 CrossWeb EX로, 현재 무려 400만 명 이상의 사용자가 있는 것으로 나타난다. 이러한 확장 프로그램들도 유사하게 웹사이트를 공격에 노출시킨다.
나는 처음에 라온시큐어와 이니세이프가 같은 그룹에 속한 계열사인 줄 알았다. 하지만 그런 것 같아 보이지는 않는다.
그리고 나서 iniLINE 소프트웨어 개발사의 이 페이지를 보게 되었다.
이 목록을 보면 이니테크와 라온시큐어가 파트너로 명시되어 있다. 이걸 봐서는, 문제가 되는 브라우저 확장 프로그램의 개발사는 이니라인으로 보인다. 또 다른 흥미로운 사실은, 맨 위의 "주요고객사" 중 첫번째가 바로 국방부라는 것이다. 적어도 국방과 관련된 업무의 결과물은 다른 파트너들에 건네주는 코드보다는 더 높은 품질이길 바랄 뿐이다.
만약 악성 웹사이트가 하나 있다고 치자. 그리고 그 웹사이트에서 TouchEn nxKey에 다음과 같이 이야기한다고 치자. "안녕? 지금 사용자가 패스워드 필드에 뭘 쓰고 있네. 거기에 입력되는 데이터 좀 알려줘." 그렇다면 그 웹사이트가 모든 키보드 입력을 가로챌 수 있을까?
당연히 그렇다! 지금 어떤 브라우저 탭이 활성화되어 있는지, 아니면 아예 브라우저 자체가 활성화되어 있는지 여부에 관계 없이 사용자가 입력하는 모든 타이핑 내용을 가져갈 수 있다. nxKey 애플리케이션은 단순히 요청을 그대로 들어줄 뿐, 그 요청이 말이 되는지는 전혀 확인하지 않는다. 사실 UAC 프롬프트에 입력된 관리자 패스워드까지 고스란히 넘겨줄 수 있다.
하지만 넘어야 할 장애물이 있긴 하다. 먼저, 해당 웹사이트는 유효한 라이선스가 필요하다. 애플리케이션의 기능을 사용하기 전에 먼저 get_versions
를 호출하여 그 라이선스를 전달해야 한다.
socket.send(JSON.stringify({
"tabid": "whatever",
"init": "get_versions",
"m": "nxkey",
"origin": "https://www.example.com",
"lic": "eyJ2ZXJzaW9uIjoiMS4wIiwiaXNzdWVfZGF0ZSI6IjIwMzAwMTAxMTIwMDAwIiwicHJvdG9jb2xfbmFtZSI6InRvdWNoZW5leCIsInV1aWQiOiIwMTIzNDU2Nzg5YWJjZGVmIiwibGljZW5zZSI6IldlMkVtUDZjajhOUVIvTk81L3VNQXRVd0EwQzB1RXFzRnRsTVQ1Y29FVkJpSTlYdXZCL1VCVVlHWlY2MVBGdnYvVUJlb1N6ZitSY285Q1d6UUZWSFlCcXhOcGxiZDI3Z2d0bFJNOUhETzdzPSJ9"
}));
이 라이선스는 www.example.com
에서만 유효하다. 그러므로 www.example.com
웹사이트에서만 사용이 가능하다. 아니면 www.example.com
이라고 주장하는 웹사이트이거나.
위 코드에서 origin
속성이 보이는가? TouchEn nxKey는 HTTP 헤더의 Origin
을 확인하는 대신, 위에서처럼 주어진 값을 그대로 받아들인다. 그래서 nxKey를 사용하는 어떤 웹사이트에서 라이선스를 가져와서 마치 그 웹사이트인 것처럼 행세할 수 있다. 가짜 라이선스를 만들 필요조차 없다.
또 다른 장애물 하나. 악성 웹사이트가 받게 되는 데이터는 암호화되어 있지 않나? 그걸 어떻게 복호화 할 수 있을까? 공격자 자신이 비공개 키를 가지고 있는 별도의 공개 키를 사용할 수 있을 것이다. 그렇다면 암호화 알고리즘만 알면 데이터를 복호화할 수 있다.
하지만 사실 이 중 어떤 것도 필요하지 않다. TouchEn nxKey는 공개 키를 받지 못하면 그냥 암호화 자체를 전혀 하지 않는다. 그러면 악성 웹사이트는 키보드 입력 내용을 암호화되지 않은 평문(clear text)으로 받을 수 있다.
내가 만든 개념증명(PoC: Proof of Concept) 페이지를 보라. (HTML 상용구 코드를 포함해도 3 kB 미만)
이 취약점의 심각성을 줄이는 세번째 장애물이 있다. 악성 웹페이지에서 키보드 입력을 가로채면 그 내용은 원래 목적지에 도달하지 못한다. 사용자가 패스워드를 입력하기 시작했는데 텍스트 필드에 아무것도 표시되지 않으면 뭔가 의심을 할 것이다. 내가 nxKey 애플리케이션을 분석한 결과 키보드 입력은 악성 웹페이지로 가거나 아니면 원래 목적지로 전달되지만, 절대로 둘 다 동시에 전달되지는 않는다.
이 제품의 자바스크립트 코드를 작성한 사람이 누군지는 몰라도, 이 언어에 별로 능숙하지 않다는 것을 앞에서 이미 살펴봤다. 하지만 이게 코드 작성자의 전문 분야가 C++이기 때문이라면? 개발자가 자바스크립트를 최대한 빨리 벗어나서 모든 동작을 C++ 코드에 맡기려는 이러한 모습은 예전에도 본 적이 있다.
아쉽게도 이건 내가 확인할 수 없는 추측일 뿐이다. 나는 바이너리 코드보다는 자바스크립트를 분석하는 것에 훨씬 익숙하다. 하지만 애플리케이션 자체도 유사한 문제들이 많아 보인다. 사실 C++보다는 C 언어에서 많이 사용하는 기법들을 쓰고 있다. 그래서 수동으로 메모리 관리를 하는 부분이 많다.
앞에서 이미 sprintf_s()
사용에 관하여 언급했다. sprintf_s()
나 strcpy_s()
같은 함수의 흥미로운 점은 이것들이 sprintf()
또는 strcpy()
의 “메모리 안전” 버전이라 버퍼 오버플로우를 방지하긴 하지만, 그럼에도 불구하고 여전히 사용하기 까다롭다는 점이다. 만약 충분히 큰 버퍼를 할당하지 않는다면, 이 함수는 잘못된 매개변수 처리기(invalid parameter handler)를 호출한다. 그리고 이 처리기의 기본 동작은 애플리케이션을 죽여버리는(crash) 것이다.
그거 아는가? nxKey 애플리케이션은 버퍼 크기가 충분한지 거의 대부분 확인하지 않는다. 그리고 기본 동작을 변경하지도 않는다. 그래서 매우 큰 값을 집어넣으면 대개 애플리케이션이 그냥 죽어버린다. 버퍼 오버플로우가 생기는 것보다는 애플리케이션이 죽는 것이 그나마 낫지만, 애플리케이션이 죽어버리면 원래 하고자 했던 일을 하지 못한다. 그 결과, 인터넷 뱅킹 로그인 창은 정상 동작하는 것처럼 보이지만 패스워드를 암호화되지 않은 상태로 받게 된다. 그리고 사용자는 입력한 양식을 제출하고 나서 오류 메시지가 뜨기 전까지는 뭔가 잘못되었다는 것을 전혀 눈치챌 수 없다. 이 취약점을 활용하면 서비스 거부 공격(Denial-of-Service attacks)을 할 수 있다.
다른 사례를 보자. 여러 JSON 파서가 있음에도 불구하고, nxKey 애플리케이션 개발자는 C 언어로 작성된 파서를 채택했다. 그뿐만 아니라, 2014년 1월경의 특정한 리포지토리 상태를 임의로 가져온 뒤 업데이트를 한 적이 전혀 없다. 이 때문에 2014년 6월에 수정된 null 포인터 역참조 문제가 그대로 남아 있다. 그래서 정상적인 JSON 데이터 대신 ]
(닫는 대괄호 하나)를 보내는 것만으로도 애플리케이션이 죽는다. 서비스 거부 공격을 허용하는 또 하나의 취약점인 셈이다.
웹사이트와 연결하기 위한 웹소켓 서버는 어떤가? 이건 OpenSSL을 사용한다. 어떤 버전일까? OpenSSL 1.0.2c 버전이다. 그렇다. 여기 있는 모든 보안 전문가들이 다들 하나같이 한숨을 쉬는 소리가 들리는 듯 하다. OpenSSL 1.0.2c는 출시된 지 7년이나 지났다. 사실 1.0.2 브랜치에 대한 지원은 이미 3년 전인 2020년 1월 1일부로 종료되었다. 마지막 릴리즈는 OpenSSL 1.0.2u인데, 이건 그 뒤로 버그와 보안 문제를 수정하는 패치가 18번이나 더 있었다는 뜻이다. 이러한 수정 사항이 nxKey 애플리케이션에는 모두 빠져 있다.
제품이 죽는 것보다 좀 더 흥미로운 내용을 살펴보자. 위에서 언급한 애플리케이션 라이선스는 base64로 인코딩된 데이터이다. 애플리케이션에서는 이것을 디코딩해야 한다. 디코딩 함수는 아래와 유사하다.
size_t base64_decode(char *input, size_t input_len, char **result)
{
size_t result_len = input_len / 4 * 3;
if (str[input_len - 1] == '=')
result_len--;
if (str[input_len - 2] == '=')
result_len--;
*result = malloc(result_len + 1);
// Decoding input in series of 4 characters here
}
이 함수를 어디서 가져왔는지는 모르겠지만, CycloneCRYPTO 라이브러리의 base64 디코더와 상당히 유사하다. 하지만 CycloneCRYPTO는 결과를 미리 할당해둔 버퍼에 쓴다. 그러니 여기의 버퍼 할당 로직은 nxKey 개발자가 직접 추가했을 수도 있다.
그리고 그 로직에 문제가 있다. input_len
이 항상 4의 배수라고 가정하고 있다. 따라서 abcd==
와 같은 입력값의 경우 3바이트 크기의 결과가 출력되지만, 위 코드에서는 2바이트의 버퍼만 할당하고 만다.
겨우 1바이트의 힙 오버플로우(heap overflow)를 악용할 수 있는가? 그렇다. Project Zero의 이 블로그 글이나 Javier Jimenez의 이 글에서 설명하고 있다. 하지만 그 취약점을 공략하는 코드를 작성하는 것은 내 능력치 밖의 일이다.
그 대신에, 내 개념증명 페이지에서는 그냥 무작위로 생성한 라이선스 문자열을 nxKey 애플리케이션에 보내기만 했다. 이것만으로도 애플리케이션을 몇 초만에 죽일 수 있었다. 디버거를 연결해 보면 메모리 손상(corruption)의 증거가 명백히 드러났다. 애플리케이션이 죽은 이유는 가짜 메모리 주소에서 데이터를 읽거나 쓰려고 시도했기 때문이다. 어떤 경우, 이 메모리 주소들은 내 웹사이트가 보낸 데이터로부터 오기도 했다. 따라서 충분한 실력이 있는 사람이 시간을 투자하면 분명 이 취약점을 원격 코드 실행에 악용할 수 있을 것이다.
최신 운영체제는 이러한 버퍼 오버플로우를 악용하여 임의 코드를 실행하기 어렵게 만드는 기능을 갖추고 있다. 하지만 이런 기능도 실제로 쓰지 않으면 아무 소용이 없다. 그런데도 nxKey의 개발자는 이 애플리케이션이 불러오는 DLL 중 2개에서는 ASLR(Address space layout randomization)을 비활성화해 두었고, 4개의 DLL에서는 DEP(Data Execution Prevention)를 꺼 두었다.
여기까지는 웹 기반 공격에 관한 내용이었다. 하지만 악성 프로그램이 시스템에 이미 침투했고, 거기서 좀 더 높은 권한을 얻고자 시도하고 있는 경우에는 과연 어떨까? TouchEn nxKey는 이런 악성 프로그램을 막기 위한 애플리케이션치고는 놀라울 정도로 자신이 존재하는 목적에 도움이 안 된다.
예를 들어, nxKey가 키보드 입력을 가로챌 때마다 CKAgentNXE.exe
라는 도우미 애플리케이션이 실행된다. 이것의 목적은 nxKey가 키 입력을 처리하지 않을 때 목표 애플리케이션에 그 입력 내용이 전달되게끔 하는 것이다. 메인 애플리케이션에서 사용하는 TKAppm.dll
라이브러리의 로직은 대략 다음과 같다.
if (IsAdmin())
keybd_event(virtualKey, scanCode, flags, extraInfo);
else
{
AgentConnector connector;
// An attempt to open the helper’s IPC objects
connector.connect();
if (!connector.connected)
{
// Application isn’t running, start it now
RunApplication("CKAgentNXE.exe");
while (!connector.connected)
{
Sleep(10);
connector.connect();
}
}
// Some IPC dance involving a mutex, shared memory and events
connector.sendData(2, virtualKey, scanCode, flags, extraInfo);
}
nxKey 애플리케이션은 사용자 권한으로 실행되므로, 거의 대부분의 경우 CKAgentNXE.exe
를 실행하게 될 것이다. 그리고 그 도우미 애플리케이션은 명령 코드 2를 받으면 SendInput()
을 호출한다.
왜 이런 식으로 만들었는지 이해하는 데는 시간이 좀 필요했다. nxKey나 CKAgentNXE.exe
나 똑같은 권한 수준으로 실행되고 있는데, 어째서 그냥 직접 SendInput()
을 호출하지 않는가? 왜 이렇게 우회하는 게 필요한가?
그러다가 CKAgentNXE.exe
가 IPC 객체의 보안 설명자(security descriptor) 설정을 바꾸어서 무결성 수준(integrity level)이 낮음(Low)인 프로세스의 접근을 허용하게끔 한다는 것을 알게 되었다. 또한 설치 프로그램이 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy
밑에 레지스트리 항목을 추가하여 CKAgentNXE.exe
의 권한을 자동으로 높이게끔 한다는 것도 알게 되었다. 그제서야 깨닫게 되었다. 이건 모두 인터넷 익스플로러의 샌드박스(sandbox) 때문이었다.
(역주: ‘integrity level’이라는 용어는 Microsoft의 한국어 문서에서는 “무결성 수준”으로 직역되어 있지만, 일부 자료에서는 “신뢰도”라고 번역하는 경우도 있습니다. 개인적으로는 “신뢰도”라고 번역하는 쪽이 더 이해하기 쉽다고 생각하지만, 일단 Microsoft 문서의 번역을 추종하였습니다. 이하 다른 용어 또한 다른 이유가 없는 한 Windows 운영체제의 개발사인 Microsoft의 한국어 문서에 나오는 번역을 우선으로 선택합니다.)
TouchEn Key가 인터넷 익스플로러에서 ActiveX로 실행될 때는 낮은 무결성 수준으로 실행된다. 이렇게 샌드박스 안에서 갇힌 채 실행될 때는 SendInput()
을 호출하는 것이 불가능하다. 이 제한을 우회하기 위해 인터넷 익스플로러의 샌드박스에서 CKAgentNXE.exe
를 실행하고 권한을 자동으로 높일 수 있게 한 것이다. 일단 도우미 애플리케이션이 실행되고 나면 샌드박스 안에 있는 ActiveX에서 이것과 연결하여 무언가 하게끔 요청할 수 있다. 예를 들면 SendInput()
을 호출한다던가.
인터넷 익스플로러 밖에서야 이런 방식이 아무런 의미도 없지만, 그럼에도 불구하고 TouchEn nxKey는 계속해서 똑같이 작업을 CKAgentNXE.exe
에 맡긴다. 그리고 이건 보안에 악영향을 미친다.
낮은 무결성 수준으로 실행된 악성코드(Malware)가 있다고 치자. 브라우저 취약점을 사용해서 침투에 성공하거나 했겠지만, 이제는 샌드박스에 갇히고 말았다. 무엇을 더 할 수 있을까? 그저 CKAgentNXE.exe
가 실행되기만을 기다리면 (조만간 일어날 가능성 있음) 이걸 써서 탈출할 수 있다!
내가 만든 개념증명 애플리케이션에서는 CKAgentNXE.exe
에게 다음과 같은 가짜 키보드 입력을 생성하도록 요청하였다. 윈도우 키, 그리고 C, M, D, 엔터 키. 그 결과 명령 프롬프트가 열렸는데, 이건 중간 무결성 수준(기본값)으로 실행된 것이다. 만약에 이게 진짜 악성코드였다면 여기에다가 어떤 명령어를 입력시켜 샌드박스 바깥에서 임의의 코드를 실행했을 것이다.
진짜 악성코드라면 이렇게 눈에 쉽게 띄는 방법을 쓰지는 않을 것이다. CKAgentNXE.exe
는 명령 코드 5를 받으면 임의의 DLL을 아무 프로세스에나 로드하게 해 주는 기능도 있다. 시스템을 감염시키기에는 이게 더 괜찮은 방법 아닌가?
적어도 이번에는 필수 설치 보안 애플리케이션 하나가 그나마 유용하게 동작해서 위험을 감지했다.
악성코드를 만드는 자는 어떻게 이 경고가 발생하는지 파악하여 우회할 수 있을 것이다. 아니면 웹소켓 연결을 통해 실제 은행 웹사이트에서와는 달리 안랩 애플리케이션이 실행되지 않는 상태에서 CKAgentNXE.exe
를 실행시킬 수 있을지도 모른다. 하지만 그렇게 번거롭게 할 필요가 있을까? 이건 그냥 경고 메시지일 뿐이고, 공격을 능동적으로 막지는 않는다. 사용자가 악성코드 치료하기 버튼을 눌렀을 때는 이미 너무 늦었다. 공격은 이미 성공했다.
위에서 언급한 것처럼 TouchEn nxKey 애플리케이션(드라이버에서 받는 키보드 입력을 암호화하는 녀석)은 사용자 권한으로 실행된다. 높은 권한으로 실행되는 것이 아니므로 특별한 권한이 없다. 그렇다면 드라이버의 기능에 대한 접근은 어떻게 제한할까?
정답은 물론 ‘하지 않는다’이다. 시스템의 어떤 애플리케이션도 이 기능에 접근할 수 있다. 그냥 nxKey가 드라이버와 어떻게 통신하는지만 알면 된다. 궁금해할 것 같아서 말해두자면, 그 통신 프로토콜은 그렇게 많이 복잡하지는 않다.
대체 무슨 생각으로 이렇게 만든 것인지 잘 모르겠다. 드라이버와 통신할 때 쓰는 라이브러리인 TKAppm.dll
은 Themida를 통해서 난독화(obfuscation)되어 있다. Themida를 만든 업체는 다음과 같이 약속한다.
Themida®는 SecureEngine® 보호 기술을 사용하여 가장 높은 권한으로 실행될 때 이전에 보지 못한 보호 기법을 구현하여 고도의 소프트웨어 크래킹으로부터 애플리케이션을 보호합니다.
어쩌면 nxKey 개발자들은 이것으로 충분히 리버스 엔지니어링을 막을 수 있다고 생각했을지도 모르겠다. 그러나 런타임에 디버거를 연결해 보니 메모리에 이미 난독화가 해제된(decrypted) TKAppm.dll
이 올라와 있었기 때문에, 이걸 그대로 저장해서 Ghidra에 띄워 분석할 수 있었다.
(역주: Ghidra는 미국 NSA에서 개발하여 오픈소스로 공개한 것으로 유명한 소프트웨어 리버스 엔지니어링 도구 모음집입니다. 사이버 보안 분야에서 악성 코드나 소프트웨어 취약점을 분석하기 위해 쓰이고 있습니다.)
미안하지만 너무 늦었다. 내가 필요한 건 이미 다 가져갔다. 그리고 안전 모드 부팅시 애플리케이션이 동작을 거부하는 것도 소용없다.
어쨌든 나는 이 드라이버에 연결하여 시스템의 모든 키보드 입력 내용을 가로챌 수 있는 아주 작은 (코드 70줄 미만) 애플리케이션을 만들 수 있었다. 관리자 권한도 필요 없이 사용자 권한만으로도 잘 작동했다. 그리고 앞서 봤던 웹페이지에서의 데모와는 달리, 이 앱은 입력 내용이 원래 목적지로도 전달되도록 할 수 있기 때문에 사용자는 아무것도 눈치챌 수 없다. 키로거 만들기가 이렇게 쉬울 수가 없다!
가장 좋은 점은, 이 키로거가 nxKey 애플리케이션과 잘 연동된다는 점이다. 그래서 nxKey가 키보드 입력을 받아 암호화한 데이터를 웹사이트로 보낼 때, 내가 만든 작은 키로거는 동일한 키보드 입력 내용을 암호화되지 않은 평문으로 기록할 것이다.
커널 드라이버를 개발할 때 알아야 할 점이 하나 있다. 드라이버가 죽으면 시스템 전체가 죽는다. 그래서 드라이버 코드에서는 절대로 오류가 발생하지 않도록 특히 꼼꼼한 확인이 필요하다.
nxKey가 사용하는 드라이버에서 오류가 발생할 수 있을까? 그렇게 자세히 살펴본 것은 아니지만, 우연히 그럴 수 있다는 것을 발견했다. 애플리케이션에서는 DeviceIoControl()
을 사용해서 입력 버퍼의 포인터를 달라고 드라이버에게 요청한다. 그러면 드라이버는 이 포인터 생성을 위해 MmMapLockedPagesSpecifyCache()
를 호출한다.
그렇다. 이 입력 버퍼는 시스템의 모든 애플리케이션에서 다 볼 수 있다. 하지만 지금 말하고자 하는 건 그게 아니다. 만약 애플리케이션에서 한번 더 포인터를 달라고 요청하면 어떻게 될까? 드라이버에서는 단순히 MmMapLockedPagesSpecifyCache()
를 한번 더 호출한다.
이걸 무한루프로 돌리자, 20초 정도 지난 시점에서 전체 가상 메모리 주소가 소진되어 MmMapLockedPagesSpecifyCache()
가 NULL
을 반환했다. 그러자 드라이버가 반환된 값을 확인하지 않았기 때문에 죽어버리고 말았다. 따라서 운영체제가 자동으로 재부팅되었다.
내가 보기에 이 문제점을 악용하는 것은 가능한 것 같지는 않지만 (참고로 나는 바이너리 악용에 관한 전문가가 아니다) 그래도 아주 형편없는 문제점이다.
내가 일반적으로는 취약점을 공개할 땐 이미 문제가 수정된 후이다. 유감스럽게 이번은 그렇지 못하다. 내가 알 수 있는 범위에서는 한 문제도 수정이 되지 않았다. 업체가 언제 이 문제들을 고칠지도 모른다. 그리고 사용자에게 어떻게 업데이트를 전달할 지도 모른다. 특히 은행에서 가장 최신 버전에서 3 버전 이전 버전을 아직 배포하고 있다. 이것을 기억하라: 자동 업데이트 기능이 없다.
문제를 보고하는 것도 복잡했다. 라온시큐어가 보안 전문기업이지만 서울 전화번호 하나를 제외하고는 보안문제관련 공개된 연락처가 없다. 한국에 직접 전화해서 거기서 누가 영어를 하는지 물어볼 생각은 없다.
운이 좋게도, KrCERT를 외국인이 사용할 수 있도록 취약점 보고 양식을 제공하고 있다. 이 폼은 자주 에러가 나서 모든 내용을 처음부터 다시 입력하도록 하기도 하며 어떤 보고는 아무 이유없이 웹 파이어월에 잡히기도 한다. 그래도 보안 연락처를 찾는 것은 내가 할 필요가 없는 다른 사람 일이다.
내가 찾은 취약점 모두를 2022년 10월 4일에 KrCERT에 보고하였다. 그 외에도 라온시큐어 임원들에 직접 연락시도를 했지만 응답을 받지 못했다. 그래도 KrCERT에서 내 보고 내용을 라온시큐어에 전달했다고 대략 2주 후에 연락받았다. 그리고 라온시큐어에서 나에게 연락하기 위해서 내 이메일 주소를 물어봤다고 전해들었지만 연락을 받은 적이 없다.
이것이 전부이다. 취약점 보고 후 90일 동안 비공개를 유지하는 데드라인은 이미 한 주전에 지났다. TouchEn nxKey 1.0.0.78은 2022년 10월 4일에 릴리즈가 되었고 같은 날에 보안 취약점에 대해서 보고하였다. 이 글을 쓰는 시점에서는 여전히 가장 최신 버전이며 여기 기술된 모든 취약점들이 여전히 존재한다.
업데이트 (2023-01-10): 라온시큐어에서는 한국매체에 취약점을 수정하였다고 주장하였고 사용자에게 업데이트를 배포할 것이라고 언급하였다. 나는 현재 이 사실에 대해서는 확인할 수 없다. 그 회사의 다운로드 서버에서는 여전히 TouchEn nxKey 1.0.0.78을 배포하고 있다.
그들이 뭔가 수정을 하고 있다는 것을 내가 어떻게 알 수 있을까? 이전에 한 번도 일어난 적이 없는 일 덕분에 가능했다. 내 PoC를 90일 데드라인 이전에 실수로 공개했다 (이것은 취약점에 기반으로 거의 완성된 공격 프로그램에 접근 가능하다는 뜻이다).
이전에는 보고서에 파일을 직접 첨부했었다. 보안 소프트웨어 때문에 첨부파일이 삭제되거나 파괴되는 일이 종종 일어났다. 그 대신에 문제를 데모하는 데 필요한 파일은 내 서버에 업로드 한다. 내 서버로의 링크는 항상 동작한다. 추가적인 혜택: 나에게 연락하지 않는 업체들이라고 해도 PoC 파일을 접근한 것은 알 수 있다. 즉, 내 보고서가 누구에게 전달이 됐는지 확인 가능하다.
며칠전에 TouchEn nxKey 관련 파일의 접근 로그를 확인했다. 즉시 구글봇을 볼 수 있었다. 이 파일들이 구글 검색엔진에 색인되어 버렸다.
나는 무작위로 생성된 폴더 이름을 사용하기 때문에 그냥 알아 낼 수 없다. 나는 링크는 업체들과만 공유한다. 그렇다면 업체가 공격 소프트웨어에 링크를 어딘가 공개적으로 올렸을 것이다.
사실 바로 그런 일이 발생했다. 개발 서버를 발견했는데 공개적으로 볼 수 있었고 구글 검색엔진에서 인덱스를 하였다. 아마도 이 서버에서 내 PoC 관련 링크가 존재한 것으로 보인다. 이것을 발견했을 때는 업체에서 복사해서 수정한 버전으로 호스팅하고 있었다.
구글봇의 첫번째 요청은 2022년 10월 17일이다. 그렇다면 90일간 제한 기간이 끝나기 2달 전부터 이미 구글 검색으로 취약점이 검색 가능했다고 봐야할 것이다. 이 파일들은 여러번 접근을 했었고 제품 개발자 만이 접근을 한 것인지는 알기 어렵다.
이 문제를 보고한 후 개발 서버는 공개 인터넷에서 바로 사라졌다. 하지만 민감한 보안 정보를 이렇게 경솔히 다루는 것은 전에 본 적이 없다.
TouchEn nxKey 관련 어려 취약점들을 살펴보았다. 키로거를 방지하기위해 완벽한 키로킹 툴을 개발했지만 그것에 접근을 제한하는 것은 실패했다. 하지만 아이디어는 괜찮다, 그렇지 않은가? 만약 제대로 만들어졌다면 유용한 보안 도구가 될지도 모른다.
그렇다면 궁금한 것은, 지금 막고자하는 키로거는 어떤 레벨로 실행이 되는가? 내가 보기로는 4가지 옵션밖에 없다:
- 브라우저 내부에서. 온라인 뱅킹 페이지에 악성 자바스크립트 코드가 실행되면 암호를 캡쳐하려고 한다. 그 코드는 웹페이지가 nxKey를 활성화 하는 것을 쉽게 막을 수 있다.
- 시스템 내부에서 사용자 권한으로. 이 권한으로 충분히 같은 사용자 권한으로 실행되고 있는
CrossExService.exe
를 죽일 수 있다. - 시스템 내부에서 관리자 권한으로. 이 권한으로 충분히 nxKey 드라이버를 언로딩 하고 트로전 버전(trojanized copy)으로 바꿔칠 수 있다.
- 하드웨어 내에서. 게임 끝. 어떤 소프트웨어 해법을 시도하려고 한다면 행운을 빌어 줄 뿐이다.
nxKey가 어떤 보안 기능을 제공하든 공격자가 nxKey와 그 기능에 대해서 모르고 있는 것에 의존하고 있다. 일반적인 공격은 막을 수 있을지 모르겠지만 한국의 은행이나 정부기관를 목표로 하는 공격을 막는데는 그다지 효과적이지 않을 것 같다.
위의 4개의 레벨 중에서 2번은 어쩌면 고칠 수 있는 가능성이 있어 보인다. CrossEXService.exe
가 관리자 권한으로 실행되도록 만들 수 있다. 그렇게 되면 악성 소프트웨어가 이 프로세스를 조작려는 시도를 막을 수 있다. 하지만 이것은 악성 소프트웨어가 사용자의 브라우저에 접근 할 수 없다는 가정하에 유효하다.
현재 제품의 동작방식이 다른 레벨에서 동작하는 악성 소프트웨어를 효과적으로 막을 수 없어 보인다.
역자 추가 - 원글에 대한 댓글/토론이 있는 웹사이트:
-
원글 Hacker News: https://news.ycombinator.com/item?id=34307602
-
원글 Reddit: https://www.reddit.com/r/korea/comments/107bpsm/touchen_nxkey_the_keylogging_antikeylogger/
-
루리웹 원문 요약: https://bbs.ruliweb.com/community/board/300143/read/59978508
-
개드립 다른 한국어 번역: https://www.dogdrip.net/456327682
-
PGR21: https://www.pgr21.com/freedom/97663, https://www.pgr21.com/freedom/97695
-
번역글 긱뉴스: https://news.hada.io/topic?id=8274
관련 기사: