0. 서론
2018년도, 군입대가 1달정도 남았을 때, 메일로 병역의무부과 통지서가 날라왔다. 신기했던 점은 통지서가 PDF나 다른 파일포맷이 아닌, html 파일로 통지서가 첨부되었다.
당시에 해당 글을 인상깊게 봐서 해당 케이스와 동일하다고 생각해서 통지서 파일을 분석했다.
1. 분석
통지서.html 파일을 열면, 다음과 같이 보안메일 비밀번호를 입력하는 폼을 확인할 수 있다.
비밀번호로 생년월일(YYMMDD)를 입력받고, 올바른 비밀번호를 입력하면 별도의 뷰어가 실행되며 통지서의 내용을 확인할 수 있게 동작한다. 해당 인증과정이 어떻게 이뤄지는지 확인하기 위해서 소스코드를 분석했다.
통지서.html 소스코드를 확인하면 위와 같이 난독화가 진행된 것을 확인할 수 있다. 보기 지저분하므로 beautify를 돌려보면 다음과 같은 깔끔한 코드를 확인할 수 있다.
하나하나 전부 분석하기엔 소스코드 양이 많아서 입력받는 폼에서부터 bottom-up으로 분석했다.
비밀번호 입력 후, 확인 버튼을 누르면 doAction( ) 함수가 실행된다.
doAction( ) 함수는 XEMViewerRun을 호출하는 것을 확인할 수 있다. XEMViewerRun은 난독화를 해제한 코드에서 함수 바디를 확인할 수 있다.
function XEMViewRun에서는 SetTimeout의 인자로 XEM을 호출하고, onStart()를 호출한다. onStart 함수는 단순히 display를 수정하는 기능을 제공한다. XEM 함수에서는 입력한 비밀번호 검증 과정이 이뤄진다. 소스코드만으로는 정적분석하기가 어려워서(의미없는 변수명 등) 해당 코드를 패치하며 크롬으로 디버깅을 해가며 다음과 같은 코드로 변수명 등을 수정했다.
function XEM() {
var a = null;
var user_pwd = null;
var pwd1_2_value= null;
var d_id = null;
var e_pwd1_or_pwd2 = null;
var f = null;
var pwd_type = xe.getPwdType(); //X-XEI_PWD_TYPE: 1
pwd_type == 1 ? e_pwd1_or_pwd2 = "pwd1" : e_pwd1_or_pwd2 = "pwd2";
d_id = "id";
var id_value = document.getElementById(d_id).value;
var pwd1_2_value= document.getElementById(e_pwd1_or_pwd2).value;
var place_id = document.getElementById(d_id).getAttribute("placeholder");
var place_pwd = document.getElementById(e_pwd1_or_pwd2).getAttribute("placeholder");
if (id_value == place_id) id_value = "";
if (pwd1_2_value== place_pwd) pwd1_2_value= "";
if (pwd_type == 1)
if (pwd1_2_value== "" || pwd1_2_value== null) {
pwd1_2_value= "none";
error_msg = "pwdInputFail"
} else user_pwd = pwd1_2_value;
else if (pwd_type == 2) {
if (id_value == "" || id_value == null) {
id_value = "none";
error_msg = "idInputFail"
}
if (pwd1_2_value== "" || pwd1_2_value== null) {
pwd1_2_value= "none";
error_msg = "pwdInputFail"
}
if (id_value == "none" && pwd1_2_value== "none") {
id_value = "none";
pwd1_2_value= "none";
error_msg = "idAndPwdInputFail"
} else if (id_value != "none" && pwd1_2_value!= "none") user_pwd = id_value + "\\n\\n" + pwd1_2_value
}
if (id_value == "none" || pwd1_2_value== "none") {
setTimeout(function() {
onEnd();
alert(errorCallBack(errorCodeList[f]))
}, 100);
return
}
dec_result = XecureExpress.decryptFunc(user_pwd); //!!!
if (dec_result != null) {
var k = navigator.userAgent;
var l = document.getElementById("XEMFrame");
if (!l) contentType = 2;
if (contentType == 2 || k.match(/iPhone|iPod|iPad|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || k.match(/LG|SAMSUNG|Samsung/) != null) {
onEnd();
document.open();
document.write(dec_result);
setTimeout(function() {
document.close()
}, 100)
} else {
l = document.getElementById("XEMFrame");
l.style.display = "block";
xemContent = l.contentWindow.document || l.contentDocument;
xemContent.open();
xemContent.write(dec_result);
xemContent.close()
}
} else {
onEnd();
var error_msg = null;
if (pwd_type == 1) error_msg = "invalidInputPWD";
else if (pwd_type == 2) error_msg = "idOrPwdInputFail";
alert(errorCallBack(errorCodeList[error_msg]))
}
}
코드를 간단히 요약하면 다음과 같다.
- 입력된 비밀번호, 환경 셋팅 등이 잘못되지 않았는지 검증한다.
- XecureExpress.decryptFunc(password)를 호출해서 올바른 비밀번호인지 검증한다.
- 올바른 비밀번호인 경우 뷰어를 실행, 틀린 경우 에러메세지를 alert 한다.
XecureExpress.decryptFunc( ) 함수를 디버깅하면서 복호화할 수 있는 방법을 찾아봤는데 당시에 암호를 잘 몰라서 그냥 해당 함수를 호출해서 브루트포싱을 해보는 방법으로 바꿨다. (물론 지금도 암호를 잘 못합니다 ㅎ)
function bruteBirth(){
var start_time = performance.now();
for(var y=1999;y<=2000;y++){
for(var m=1;m<=12;m++){
var m_tmp = m+""
if(m.toString().length == 1)
m_tmp = "0"+m_tmp
for(var d=1;d<=31;d++){
var d_tmp = d+""
if(d.toString().length == 1)
d_tmp = "0"+d_tmp
var tmp = y.toString()[2]+y.toString()[3]+m_tmp+d_tmp
console.log(tmp);
if(XecureExpress.decryptFunc(tmp) != undefined){
console.log(tmp);
console.log("소요시간:",(performance.now()-start_time)*0.001,"s");
return tmp;
}
}
}
}
}
단순히 decryptFunc 함수를 호출하며 브루트포싱을 수행하는 코드다. 물론 DEMO를 위해 1999~2000으로 고정했지만, 실제로 브루트포싱할 때는 이 값을 수정해서 공격하면 될 것이다. 해당 통지서를 받는 나이대는 20~30세이기 때문에 범위를 많이 축소할 수 있을 것이다.
2. 후기
군 입대하기전에 분석했던건데 갑자기 생각나서 이렇게 글을 올려봅니다. 뭔가 좀 더 다른 방법을 통해서 복호화하고 공격해볼 방법을 생각해봤는데 아직 암호에 대한 지식이 부족해 좀 더 공부가 필요할 것 같습니다. 이 글을 작성하며 더 구글링해봤는데 20년도에 다양한 보안메일을 분석한 글이 있더라고요. 이 글도 참고하시면 더 좋을 것 같습니다 :)
'ETC' 카테고리의 다른 글
GitHub PR in other branch (0) | 2024.04.25 |
---|---|
Challenges at LINE as a security engineer 리뷰 (0) | 2021.08.30 |
[linux] 라이브러리 관련 설정 (0) | 2021.02.25 |