<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    逆向角度看C++部分特性

    VSole2022-04-02 16:26:28

    單/多繼承

    單繼承

    測試源碼:

    #define MAIN __attribute__((constructor))#define NOINLINE __attribute__((__noinline__))
    class BaseClass{public:    int a,b;    BaseClass(int mA=1,int mB=2,int mC=3,int mD=4){        this->a = mA;        this->b = mB;        this->c = mC;        this->d = mD;    }private:    int c;protected:    int d;};
    class ChildClass: public BaseClass{public:    int m,n;    ChildClass(int mM=5,int mN=6){        this->m = mM;        this->n = mN;    }};
    MAINvoid test0(){    auto* baseClass = new BaseClass();    LOGD("baseClass   : %p sizeof: %d ",baseClass,sizeof(*baseClass));
        auto* child1 = new ChildClass(10,20);    LOGD("child1  : %p sizeof: %d", child1, sizeof(*child1));}
    

    LOG日志:

    內存情況:

    可以看到實際上單繼承就是把 baseClass 的成員變量完全copy了一份放在了我們childClass的前面。

    多繼承

    // 新增一個BaseNewClass,讓ChildClass:BaseClass繼承這兩個Classclass BaseNewClass{public:    int p,q;    BaseNewClass(int mP=10,int mQ=11){        this->p = mP;        this->q = mQ;    }};
    class ChildClass:BaseClass,BaseNewClass{public:    int m,n;    ChildClass(int mM=5,int mN=6){        this->m = mM;        this->n = mN;    }};
    // LOG()日志D/ZZZ: baseClass   : 0xf216dd70 sizeof: 16D/ZZZ: childClass  : 0xea17e280 sizeof: 32
    // 內存情況[Pixel XL::XXX]-> seeHexA(0xea17e280,32)           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEFea17e280  01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................ea17e290  0a 00 00 00 0b 00 00 00 05 00 00 00 06 00 00 00  ................
    

    其實也都是成員變量按順序往后排就完事。

    虛函數

    // 測試源碼class BaseClass{public:    int a,b;    BaseClass(int mA=1,int mB=2,int mC=3,int mD=4){        this->a = mA;        this->b = mB;        this->c = mC;        this->d = mD;    }    virtual void showLOG(){        LOGD("Called BaseClass showLOG");    }    virtual void showLOG1(){        LOGD("Called BaseClass showLOG1");    }private:    int c;protected:    int d;};
    class ChildClass:BaseClass{public:    int m,n;    ChildClass(int mM=5,int mN=6){        this->m = mM;        this->n = mN;    }    virtual void showLOG(){        LOGD("Called ChildClass showLOG");    }    virtual void showLOG1(){        LOGD("Called ChildClass showLOG1");    }    virtual void showLOG2(){        LOGD("Called ChildClass showLOG2");    }};
    MAINvoid test0(){    auto* baseClass = new BaseClass();    LOGD("baseClass   : %p sizeof: %d",baseClass,sizeof(*baseClass));
        auto* childClass = new ChildClass();    LOGD("childClass  : %p sizeof: %d",childClass,sizeof(*childClass));}
    // 日志D/ZZZ: baseClass   : 0xe75a7648 sizeof: 20D/ZZZ: childClass  : 0xe75d8d00 sizeof: 28
    // 內存情況[Pixel XL::XXX]-> seeHexA(0xe75a7648,20)           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEFe75a7648  24 59 5f d2 01 00 00 00 02 00 00 00 03 00 00 00  $Y_.............e75a7658  04 00 00 00                                      ....[Pixel XL::XXX]-> seeHexA(0xe75d8d00,28)           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEFe75d8d00  3c 59 5f d2 01 00 00 00 02 00 00 00 03 00 00 00  e75d8d10  04 00 00 00 05 00 00 00 06 00 00 00              ............
    

    由上我們可以看到這兩個Class的地址的開始位置都多了一個指針,指針后面的才是我們真實的結構體值,這第一個指針就是 vptr(虛函數指針),指向了虛函數表,然后再去讀一下這個指針。

    //讀取vptr指向的位置[Pixel XL::XXX]-> seeHexA(ptr(0xe75a7648).readPointer(),0x20)           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEFd25f5924  b1 fa 5a d2 c9 fa 5a d2 d4 60 5f d2 39 29 5f d2  ..Z...Z..`_.9)_.d25f5934  00 00 00 00 48 59 5f d2 e1 fa 5a d2 f9 fa 5a d2  ....HY_...Z...Z.
    [Pixel XL::XXX]-> Module.findBaseAddress("libdynamic.so")"0xd2593000"[Pixel XL::XXX]-> ptr(0xd25f5924).readPointer().sub(0xd2593000)"0x1cab1"[Pixel XL::XXX]-> ptr(0xd25f5928).readPointer().sub(0xd2593000)"0x1cac9"[Pixel XL::XXX]-> ptr(0xd25f592c).readPointer().sub(0xd2593000)"0x630d4"[Pixel XL::XXX]-> ptr(0xd25f5930).readPointer().sub(0xd2593000)"0x5f939"
    

    此時打開IDA驗證一下這前兩個地址就是真實的函數地址。

    // IDA查看地址:

    同理我們去看看另一個childClass類也會得到類似的結果:

    //[Pixel XL::XXX]-> seeHexA(ptr(0xe75d8d00).readPointer(),0x20)           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEFd25f593c  e1 fa 5a d2 f9 fa 5a d2 11 fb 5a d2 30 61 5f d2  ..Z...Z...Z.0a_.d25f594c  44 29 5f d2 00 00 00 00 01 00 00 00 2c 59 5f d2  D)_.........,Y_.
    [Pixel XL::XXX]-> ptr(0xd25f593c).readPointer().sub(0xd2593000)"0x1cae1"[Pixel XL::XXX]-> ptr(0xd25f5940).readPointer().sub(0xd2593000)"0x1caf9"[Pixel XL::XXX]-> ptr(0xd25f5944).readPointer().sub(0xd2593000)"0x1cb11"[Pixel XL::XXX]-> ptr(0xd25f5948).readPointer().sub(0xd2593000)"0x63130"[Pixel XL::XXX]-> ptr(0xd25f594c).readPointer().sub(0xd2593000)"0x5f944"
    

    第一二三個:明顯就是對應的虛函數具體的函數地址。第四五個:應該是和 C++中的RTTI機制 相關。

    // IDA查看地址:

    簡單歸納一下:

    ① 繼承這種操作其實就是得到了一個父類數據結構的副本,他們的vptr和屬于子類部分的數據結構都是獨有的。

    ② 繼承后父類的虛函數表也會被子類完全繼承。若無覆蓋時,子類的虛函數表會完全拷貝一份父類的虛函數表項,并將自己子類的虛函數表項拼接在上表后面。

    ③ 如果子類覆蓋了父類的某一個虛函數,虛函數表項值改變順序不變。

    這里簡單的提及了一下,更詳細的關于虛函數的介紹可以查看 這篇文章(https://blog.csdn.net/smartgps2008/article/details/90745271)。

    至于里面提到的關于 安全性 的反思:

    ① Base1 b1 = new Derive(); 將子類的指針轉為一個父類指針,只是在c++語法上限制了其對部分操作的可能性。 "子類中的未覆蓋父類的成員函數" ,對它的理解應該是:它本是是什么還是什么,語法上的限制完全可以使用指針操作來實現一定程度和語法的背道而馳。他提出的第二點 *"訪問non-public的虛函數" 其實和上述這一點也差不多的意思。

    ② 補充一點:其實對于繼承中的成員變量也有同樣類似的效果,父類不管把成員的訪問權限設置為什么,其實子類都有一個完整的拷貝,同樣可以通過指針操作繞過c++語法的禁止,去訪問并修改父類非公開成員變量。

    拷貝構造

    源碼以及匯編情況:

    NOINLINEvoid test1(ChildClass* cls){    cls->showLOG();}
    NOINLINEvoid test2(ChildClass &cls){    cls.showLOG();}
    NOINLINEvoid test3(ChildClass cls){    cls.showLOG1();}
    NOINLINEChildClass test4(ChildClass cls){    return cls;}
    MAINvoid test0(){    auto* baseClass = new BaseClass();    LOGD("baseClass   : %p sizeof: %d  typeid: %s",baseClass,sizeof(*baseClass),typeid(baseClass).name());
        auto* child1 = new ChildClass(10,20);    LOGD("child1  : %p sizeof: %d  typeid: %d", child1, sizeof(*child1), typeid(child1).hash_code());
        auto* child2 = new ChildClass(*child1);    child2->showLOG();
        test1(child2);    test2(*child2);    test3(*child2);    test4(*child2);}
    

    // 全局視圖:

    列舉出以下的幾種情況:函數參數值傳遞(值傳遞和引用傳遞)

    參見 test1 test2 可見:

    值傳遞對于基礎數據類型會直接mov出一個副本,值傳遞對象(class/struct)的話會調用對象的拷貝構造函數得到一個新的副本,所以對于類對象太大的情況建議使用指針傳遞或者使用引用傳遞(指針傳遞和引用傳遞在匯編層面其實是一樣的都是傳遞了一個指針[見上圖])。

    參見 test3 可見:

    test3進行了值傳遞,在進入函數前先對ChildClass調用了一次拷貝構造函數,將棧上拷貝出來的該類傳遞進了 test3。

    函數返回值

    參見 test4 可見:

    test4 和 test3 同樣在調用前都先調用了一次拷貝構造函數,但是test4的第一個參數是在棧上提前申請好預留給test4返回的空間,第二個參數為拷貝好的指向副本的類指針,進入test4后也會發現在內部在調用了一次拷貝構造函數,也就是說值傳遞加上返回值這種寫法相比直接引用傳遞會多調用兩次拷貝構造函數。

    從一個類創建另一個類

    參見 test1(v4) 上面的兩句:其實也是調用的拷貝構造函數,v4指向的拷貝好的類在棧上的首地址,第一個代表讀取vptr,第二個代表讀取vtable的第一個函數(child2->showLOG();就是ChildClass的第一個虛函數),然后再把自己(v4)當成this傳遞給這個虛函數調用。

    拷貝構造拷貝父類

    詳見下圖:// 由編譯器為我們生成的拷貝構造函數

    ChildClass(const ChildClass &child){    this->m = child.m;    this->n = 12;    LOGD("called ChildClass拷貝構造函數");}
    

    // 由我們自己編寫的拷貝構造函數:

    // 虛函數表:

    由此可見調用子類的拷貝構造函數會先調用父類的構造函數,然后在調用當前類的拷貝構造,這里的off_85600就是 vptr ,從虛函數表中也可以看見,子類覆蓋了父類的虛函數就會指向子類的虛函數。

    若沒有覆蓋,表項中依舊是指向父類的函數地址,而且順序是按照父類的虛函數表順序排列,子類中父類沒有的虛函數會按順序繼續排在后面,不同類的虛函數表其實都是在編譯期就已經確定了的,不同類的虛函數表處于臨近的內存區域。

    類的 構造/析構 函數調用時機

    詳見下圖(ChildClass中新增了一個析構函數),// 新增析構函數:

    類繼承權限

    類的繼承權限并不會影響子類繼承父類子類所擁有的父類的成員變量個數,換句話說,不管父類的成員變量是什么權限,之類都完全擁有一份父類的成員變量的拷貝(這里就不展示)。

    類型的強轉

    主要是針對 dynamic_cast 向下轉型的情況。

    BaseClass* baseTmp = dynamic_cast(child1);if (baseTmp!= nullptr){    baseTmp->showLOG1();}
    BaseClass* baseTmp1 = static_cast(child1);if (baseTmp1!= nullptr){    baseTmp1->showLOG1();}
    ChildClass* baseTmp2 = dynamic_cast(baseTmp);if (baseTmp2!= nullptr){    baseTmp2->showLOG1();}
    

    // 向下轉型

    由上兩圖可見對于類 向上轉型 dynamic_cast 和 static_cast 本質是一樣的,沒有做任何處理。

    dynamic_cast 向下轉型的時候是借助了 RTTI 機制,就是我們前面圖中看到的vptr->vtable 除了虛函數以后的指針標識該類的類型用于動態類型轉換,同樣也是typeid這個操作符的信息來源,具體可以參考 這篇文章(http://c.biancheng.net/view/2343.html)。

    其實虛表什么的都是在編譯期間就已經完全確定了,之前還誤解以為動態類型轉換中的向上轉型可以讓該子對象調用已經被子對象覆蓋的父對象的方法,想多了想多了... 但是如果真想實現這樣的"向上轉型"也不是不行,借助指針去操作虛函數表即可 ↓

    // 實現所謂的"向上轉型"

    // 效果圖

    最后兩條日志可見,我們對同一對象調用 showLOG() 一個是父函數,一個是子函數,對應代碼 815 和 819 行。

    簡介 lambda

    細節介紹參考 

    這個(https://en.cppreference.com/w/cpp/language/lambda) 和 

    這篇文章(https://zhuanlan.zhihu.com/p/384314474

    引用傳遞和值傳遞

    關于lambda表達式分為三部分解析

    1、表達式位于類中 (為了看到最原始的實現,不要開編譯器優化)
    #define xASM(x) __asm __volatile__ (x)using namespace std;
    class testA{private:    int tempInt0 = 123;    int tempInt1 = 321;    int tempInt2 = 110;public:    testA(){        testFunction();    }
        NOINLINE    void testFunction(){        [&]()-> void {            LOGD("testA anonymous_0 -> tempInt2:%d",this->tempInt2);        }();
            []()-> void {            xASM("MOV r4, r0":::"r4");            int** tmp = nullptr;            xASM("MOV %0, r4":"=r"(tmp)::);            LOGD("testA anonymous_1 -> tempInt0:%d",**(tmp + 0x1));        }();
            []()-> void {            xASM("MOV r4, r0":::"r4");            int tmp = 0;            xASM("LDR r4, [r4, #0x8]":::"r4");            xASM("LDR r4, [r4, #0x4]":::"r4");            xASM("MOV %0, r4":"=r"(tmp)::"r4");            LOGD("testA anonymous_2 -> tempInt1:%d",tmp);        }();    }};
    

    構造以及調用testFunction

    testFunction匯編也可以明顯看到被IDA識別為了lambda表達式。

    從這里我們可以看到雖然源碼后面兩個lambda表達式雖然沒有捕獲參數,但是依舊有一個棧地址的傳遞(可以理解為一個空 this)。

    傳遞棧上地址逐個相差一個指針長度。

    0x4*6 = 24(0x18)  sp+0x4 sp+0x8 sp+0xc
    

    上述源代碼中沒有表現出來,即便是空 lambda 實現,編譯器依舊會傳遞一個棧上地址過去,這里也不做展示了。

    然后后面兩個 lambda 實現主要是為了實踐,即使不捕獲任何的參數,依舊可以拿到類實例,以及去讀取類成員變量。

    在類里面的 lambda 可以理解為對 () 的重載(被IDA也是識別為重載 operator())。

    讀取類成員變量日志

    2、表達式位于類外無捕獲參數
    NOINLINEvoid testB(){    auto testNoCatch = [](int s) -> int {        LOGD("testB called lambda function : %d", ++s);        return s;    };
        LOGD("testB testNoCatch ---> %p",*testNoCatch);
    //    using TypeFunc = int(*)(int);    auto testNoCatchNew = *testNoCatch;    testNoCatchNew(5);    testNoCatch(10);}
    

    表達式位于類外無捕獲參數

    中間函數用來返回lambda函數真實的地址

    中間跳板函數

    lambda函數的實現,和普通函數沒有啥差別。

    類外 lambda 函數,*lambda 都會生產這樣的一個跳轉邏輯。

    類內 lambda 函數不管有沒有捕獲參數都是直接理解為匿名類重載 () 運算符。

    testB 日志

    3、表達式位于類外有捕獲參數
    NOINLINEvoid testC(){    int a = 10;    int* b = new int(11);    auto c = make_unique(12);    auto* d = new float(13);
        auto testNoCatch = [](int s,int t) -> void {        LOGD("testC called lambda function : %d", s+t);    };
        auto testCatch = [=,&c,&d](int s) -> void {        LOGD("testC called lambda function : {%d} | {%d,%d,%d,%f}", ++s,a,*b,*c,*d);    };
    //    LOGD("testC  testNoCatch typeid:%s | testCatch typeid:%s",typeid(testNoCatch).name(),typeid(testCatch).name());    LOGD("testC ------ ");    testNoCatch(3,4);    testCatch(2);
        // &testCatch 拿到的是他準備好的參數在棧區的首地址    LOGD("testC &testCatch %p", &testCatch);    LOGD("testC &testNoCatch %p", &testNoCatch);
        void* funcAddress = nullptr;    auto testCatchNew= [=,&c,&d,&funcAddress](int s) -> void {//        xASM("MOV %0, lr":"=r"(funcAddress)::);//        [&funcAddress]()-> void{//            if (funcAddress == nullptr) xASM("MOV %0, lr":"=r"(funcAddress)::);//        }();        LOGD("testC called lambda function : {%d} | {%d,%d,%d,%f}", ++s,a,*b,*c,*d);    };
        testCatchNew(3);    LOGD(" %p ", funcAddress);    free(b);}
    

    IDA反匯編

    testC 日志

    從 sub_319EC(v7, 3, 4) → testNoCatch(3, 4)

    可以看出 [] 捕獲的參數其實都在第一個參數,lambda的傳參在 第二個參數往后,結合把lambda理解為一個重載 () 運算符的類也是自洽的。

    從 sub_31A4C(v6, 2) → testCatch(2);

    再去對應看v6的參數,也就可以更加理解,lambda 表達式引用傳遞和值傳遞的區別,源碼中的 c為一個類 (理解為→ 構造 : sub_3198C(v10, &v9); | 析構 : sub_31BB4(v10);),棧傳參的時候源碼中的引用傳遞放在最前面,其次按順序傳遞參數。

    從 sub_3198C(v10, &v9); 和 sub_31BB4(v10)

    sub_3198C(v10, &v9) → auto c = make_unique(12);

    sub_31BB4(v10) → 作用域結束,對unique指針的析構。

    從 上圖 29 30 行可見:對帶捕獲參數的 lambda 表達式取地址得到的只是 匿名類(分配在棧上)的首地址,其實從棧的角度看也是待傳參數數組的首地址。

    沒有帶捕獲參數的 lambda 表達式 基本上可以等價于一個普通函數,函數地址通過 來獲得(編譯器針對表達式特殊處理的);帶捕獲參數的 lambda 表達式 不能使用 ,如果使用 & 只能獲得該匿名類首地址,而且 匿名lambda類的構造函數可以理解為inline構造。

    lambda 表達式可以使用 [=] / [&] 捕獲外部 值傳遞 / 引用傳遞,編譯器只會把使用到的變量按照對應傳遞方式傳遞給匿名lambda類,沒用到的變量不會被拷貝。

    由此上結論我們可以將 dobby hook 稍微封裝一下。

    registerHook的重載第三個參數(Callback)本來是想用模板的但是好像不太行。

    得到一個類似于java函數回調一樣的寫法。

    這里srcCall可以用一個變長參數簡寫一下代碼。

    函數返回對象
    class testClass {public:    int a = 10;    int b = 11;    int c = 12;    int d = 13;    testClass(){        LOGD("testClass");    }     void callTest() {        LOGD("callTest");    }}; NOINLINEtestClass getTestClass() {    auto tmp = testClass();    LOGD("getTestClass %p %d",&tmp,tmp.c);    return tmp;} void testRetClass() {    testClass tt = getTestClass();    LOGD("testRetClass b -> %d",tt.d);}
    

    “SUB SP, SP, #24” 4*6 總計開辟了 6 個位置,最下面的那個位置(fp-0x4) 用于存放棧檢查的fp或者說成sp。

    正常情況下 class() 構造出來的類就在當前函數棧中,但是這里有一個特例:對于在 函數getTestClass 中 創建在棧上的class(tmp),實際上他真實存在的位置是在 函數testRetClass 的棧上 位置 {fp-0x8,fp-0x14},最頂上的那個位置(sp)是空的(在這里分配棧最小差值0x8),結合上述描述再去看地址 0x4F5FC 就是logd中的第三個參數 “tt.d”。

    ps:如果這里的 class 只有三個成員變量,這里的 “SUB SP, SP, #0x18” 將會變成 “SUB SP, SP, #0x10”,剛好用滿棧的四個位置。

    對于地址 0x4F5E8 這里的這個函數調用就是對 類testClass 的初始化,傳遞了第一個地址(函數testRetClass棧地址)進去 對 int a,b,c,d 的初始化就放在 “LOGD("testClass");” 之前。

    模板類的實現

    模板類的實現

    實現

    • 它的上一層是一個 plt 跳轉。
    • 使用模板對應生成了多個實現方法。

    該文章作為日常學習理解的記錄,理解可能又不準確的地方,歡迎大佬們指出!

    lambda虛函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    House of Cat5月份偶然發現的一種新型GLIBC中IO利用思路,目前適用于任何版本,命名為House of cat并出在2022強網杯中。但是需要攻擊位于TLS的_pointer_chk_guard,并且遠程可能需要爆破TLS偏移。并且house of cat在FSOP的情況下也是可行的,只修改虛表指針的偏移來調用_IO_wfile_seekoff即可。vtable檢查在glibc2.24以后加入了對函數的檢測,在調用函數之前首先會檢查函數地址的合法性。
    逆向角度看C++部分特性
    前言 1、這篇文章講了什么? 文本圍繞三個問題 lambda會遇到什么攻擊場景 什么情況下,在lambda中讀取到的env環境變量密鑰可以讓我們接管服務器甚至整個賬號 什么情況下,可以通過lambda權限去橫向到其他的EC2服務器 本文會對這三個問題進行解答,并且進行演示
    他們直接聯系AWS API,進一步枚舉帳戶,進而收集信息和泄露數據。不幸的是,AWS集群角色錯誤配置,擁有過大的讀取權限。本意是允許讀取特定的S3存儲桶,但權限允許角色讀取帳戶中的一切,這使攻擊者得以進一步了解AWS帳戶,包括Lambda。受影響的AWS帳戶中有不同的Lambda函數,主要與帳戶自動化有關。還有證據表明攻擊者執行了盜取的軟件。
    Fugue近日發布了Regula 1.0,這是一種用于基礎設施即代碼(IaC)安全性的開源策略引擎。該工具可在GitHub上獲得,包括對常見IaC工具(如Terraform和AWS CloudFormation)的支持、具有數百個驗證AWS、Microsoft Azure和Google Cloud資源策略的預構建庫,以及支持自定義規則開發和使用Open策略代理。
    近日,云安全公司Sysdig發布了《2023年全球云威脅報告》,研究了瞄準垂直行業的針對性云攻擊,結果發現云攻擊者正在通過利用云服務和常見的錯誤配置,以復雜的方式發展他們的技術和工具包。更重要的是,云中的攻擊移動速度很快,偵察到威脅和造成嚴重破壞之間的間隔可能僅幾分鐘。
    寫在前面關于無字母數字Webshell這個話題,可以說是老生常談了。之前打 CTF 的時候也經常會遇到,每次都讓人頭大,所謂無字符webshell,其基本原型就是對以下代碼的繞過:
    可想而知,如果 Spring 城門失火,Java 必定遭殃。根據官方文檔,Spring Cloud Function 是基于 Spring Boot 的函數計算框架,它可以:通過函數促進業務邏輯的實現。
    近期,研究人員對一個名為Elektra-Leak的惡意活動進行了持續跟蹤和深入分析,并發現相關的威脅行為者在Elektra-Leak惡意活動中能夠實現在公共GitHub代碼庫內自動獲取IAM(身份和訪問管理)憑證信息。
    賽時有考慮過ret to dl_resolve的做法,在網上查了下也沒發現有相關的文章,當時也沒有詳細研究,這次趁著期末考前有空,仔細琢磨了一下。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类