吾愛破解 - LCG - LSG |安卓破解|病毒分析|破解軟件|pipinga.com

 找回密碼
 注冊[Register]

QQ登錄

广西快三只需一步,快速開始

搜索
查看: 12131|回復: 97

[原創] X86指令混淆之函數分析和代碼塊粉碎

  [復制鏈接]
樓主
昨夜星辰恰似你 發表于 2020-1-9 15:52 回帖獎勵
本帖最后由 昨夜星辰恰似你 于 2020-1-9 22:30 編輯

原理

二進制分析的時候經常遇到經過混淆的函數,所以一直想自己學習實現一個簡單的指令亂序demo,也不需要太復雜(其實就是自己菜而已)。能阻止IDA广西快三 F5黨(就是我)就可以了(能去混淆的師傅除外),常見的指令亂序是把一段代碼根據jcc指令劃分成若干基本塊jcc指令通俗來說就是常見的跳轉指令諸如jz,jnz,jmp....此類。基本塊的概念參考IDA截圖,像這種loc_xxx就能看作基本塊。

更直觀一點就是下面這張圖,代碼被劃分成塊,執行流程被分析的明明白白

劃分完基本塊之后再打亂或者隱藏各個基本塊之間的直接聯系,使靜態反編譯工具無法分析執行流程。
广西快三 更無法通過F5看偽代碼。

最簡單最原始的做法就是增加新的代碼塊A,找出所有jcc指令,修改該指令跳轉到A,
再通過A跳轉到正確的代碼塊,代碼塊A可以根據數學公式實現一些運算,動態計算出跳轉地址,模糊控制流
這種做法也被大牛們叫做控制流程平坦化,代碼塊A也叫做控制分發器,負責分發指令跳轉。

當然這只是最簡單最基本的控制流程平坦化,去混淆也很容易,幾乎可以靜態將代碼打回原形
我沒有采用上面的方法,我的基本想法是以函數為單位進行混淆,比如有函數F,抽取出F函數的所有指令,
申請一個新的空間將每條指令隨機亂序放置在新的空間,再增加指令保證兩條指令的執行順序和原始函數一致
可以采用上面說的復雜算法計算出下一條指令的地址也可以使用直觀的跳轉指令進行鏈接
實現每條指令空間順序上的隨機亂序,但是執行順序不變,空間上相鄰的兩條指令之間也可以生成一些大小隨機的花指令進行干擾。

最后修復跳轉關系和重定位表。這樣就完成了對一個函數的“粉碎”。
使用工具:自己擼的一個PE操作類,反匯編引擎使用的udis86,匯編引擎使用的asmjit


函數分析

函數分析的意思是,給定一個代碼塊,識別出函數的起始地址和大小,類似IDA以sub_xxx標注出函數的功能
如圖

正確識別出函數是很困難的事情,因為每個編譯器生成的函數特征可能都不一樣,比如某些函數以ret指令結尾,
有些函數根本沒有ret指令,有些函數也不是以push xxx開頭。所以只能盡可能加入較多的函數特征

連IDA這種級別的反編譯器都不可能百分百識別出代碼和數據有些編譯器把部分數據和代碼混合編譯在一起,
比如delphi。或者編程者故意插入了導致某些反編譯結果出錯的花指令,這種情況是無法分析函數的。

參考了玩命的關于代碼數據識別的文章,自己再總結了一些規則,得出能識別大部分函數的算法,
广西快三 暫時沒有加入識別某些delphi函數的規則,這類函數代碼和數據混雜在了一起。

基本算法如下

1.jmp immediate(立即數)

  • 反編譯過程中記錄遇到的所有jcc指令跳轉目的地址,每次都和新遇到的jcc指令目的地址比較,記錄下跳轉目的地址最大的一個

    2.ret結尾識別

  • 遇到ret指令則比較前面保留的最大跳轉地址和當前地址,如果當前地址大于跳轉地址則函數結束,如果小于跳轉目的址則從跳轉地址開始繼續分析

    3.其他情況結尾判斷

  • 如果遇到向上跳轉的無條件jmp指令則函數結束
  • 如果找到nop (0x90)則函數結束
  • 如果找到至少連續兩個以上int3  (0xCC)則函數結束
  • 如果找到add [eax], al  (0x00,0x00....)則函數結束
  • 函數第一條指令是無條件jmp則函數結束,并把jmp指令目標地址加入待分析函數地址集合
  • 如果下一條指令是另一個函數的開始,(比如遇到指令push esp,mov ebp, esp) 則函數結束,并把下一條指令地址加入待分析函數地址集合

    4.Call immediate(立即數)

  • 遇到call立即數指令則把目的地址加入待分析函數起始地址集合

    5.其他

  • 如果程序有調試信息也可以根據調試信息來區分指令數據和函數,這里的規則肯定代表全部,可以根據每個編譯器的不同加入自定義規則
  • 基本思路是從pe文件入口點開始使用上面的算法啟發式分析,遇到函數調用就把調用目地地址加入待分析集合,重復以此

部分代碼如下

/***
* 指令流節點
*/
typedef struct _Instr_Flow_Node
{
    bool isJmpDown;//是否向下跳
    DWORD64 jmpRange;//跳轉范圍大小
    ud_mnemonic_code type;//指令類型
    ud_type operatorType;// 操作數類型   1.跳轉立即數 2.寄存器 3.內存地址
    bool isJcc = false;//是否是jcc類型的指令
    bool isCall = false;//是否是Call類型的指令
    DWORD64 loadImageAddress;//當前指令虛擬內存
    DWORD64 memoryFileAddress;//當前指令文件內存
    DWORD64 jmpLoadImageAddress;//跳轉目的地虛擬內存
    DWORD64 jmpMemoryFileAddress;//跳轉目的地文件內存
    DWORD insnLen;//指令長度

    //jcc立即數跳轉類型的跳轉偏移量
    struct
    {
        union
        {
            int8_t sbyte;
            int16_t sword;
            int32_t sdword;
        };
    } jmpOffset;

    bool operator < (const _Instr_Flow_Node & node) const
    {
        return this->memoryFileAddress < node.memoryFileAddress;    //  < 升序
    }

    bool isInvalid()
    {
        return this->type == UD_Iinvalid;
    }

} InstrFlowNode;
FunctionNode X86Analysis::AnalysisFunction(DWORD64 begin, DWORD bufferSize, map<DWORD64, FunctionNode>* functionMap, map<DWORD64, FunctionNode>* excludeMap, DWORD64 pc)
{
    ud_t ud;
    ud_init(&ud);
    ud_set_mode(&ud, 32);
    ud_set_syntax(&ud, UD_SYN_INTEL);
    ud_set_input_buffer(&ud, (uint8_t*)begin, bufferSize);
    ud_set_pc(&ud, pc);
    InstrFlowNode jcc_max, jcc_flow;
    memset(&jcc_max, 0, sizeof(jcc_max));
    while (ud_disassemble(&ud))
    {
        jcc_flow = GetInstrNode(&ud);

        if (jcc_flow.isInvalid())
        {
            //遇到無效指令(可能花指令),則停止分析該函數,返回前面分析完成的部分,可能函數長度為0
            FunctionNode function;
            function.memoryFileAddress = begin;
            function.loadImageAddress = pc;
            function.size = begin - (jcc_flow.memoryFileAddress - jcc_flow.insnLen);
            return function;
        }

        if (jcc_flow.isJcc && (jcc_flow.operatorType == UD_OP_JIMM))
        {
            if (jcc_flow.jmpMemoryFileAddress > jcc_max.jmpMemoryFileAddress)
            {
                //記錄CFG流圖中跳轉目標地址最大的跳轉指令
                jcc_max = jcc_flow;
            }
        }

        switch (jcc_flow.type)
        {

        case UD_Ijmp:
        {
            //如果無條件跳轉目標地址小于函數起始或者當前跳轉指令是函數第一條指令則視為結束
            //并把目標地址加入預分析函數節點
            if (jcc_flow.operatorType == UD_OP_JIMM)
            {

                if ((jcc_flow.jmpMemoryFileAddress < begin) || (jcc_flow.memoryFileAddress == begin))
                {
                    if (functionMap != nullptr)
                    {
                        //如果排除map中不存在已經分析過的函數則插入節點
                        if ((excludeMap != nullptr) && (!excludeMap->empty()))
                        {
                            if (excludeMap->find(jcc_flow.jmpMemoryFileAddress) == excludeMap->end())
                            {
                                FunctionNode node(jcc_flow.jmpMemoryFileAddress, jcc_flow.jmpLoadImageAddress);
                                functionMap->operator[](node.memoryFileAddress) = node;
                            }

                        }
                        else
                        {
                            FunctionNode node(jcc_flow.jmpMemoryFileAddress, jcc_flow.jmpLoadImageAddress);
                            functionMap->operator[](node.memoryFileAddress) = node;
                        }

                    }

                    return FunctionNode
                    (
                        begin,
                        pc,
                        jcc_flow.memoryFileAddress + jcc_flow.insnLen - begin
                    );
                }
            }
            break;
        }
        case UD_Icall:
        {
            if (functionMap != nullptr)
            {
                if (jcc_flow.operatorType == UD_OP_JIMM)
                {
                    if ((excludeMap != nullptr) && (!excludeMap->empty()))
                    {
                        //如果排除map中不存在已經分析過的函數則插入節點
                        if (excludeMap->find(jcc_flow.jmpMemoryFileAddress) == excludeMap->end())
                        {

                            //4字節立即數call
                            FunctionNode node(jcc_flow.jmpMemoryFileAddress, jcc_flow.jmpLoadImageAddress);
                            functionMap->operator[](node.memoryFileAddress) = node;
                        }

                    }
                    else
                    {
                        //4字節立即數call
                        FunctionNode node(jcc_flow.jmpMemoryFileAddress, jcc_flow.jmpLoadImageAddress);
                        functionMap->operator[](node.memoryFileAddress) = node;
                    }

                }
            }
            break;
        }

        case UD_Iret:
        {
            if (jcc_max.jmpMemoryFileAddress > jcc_flow.memoryFileAddress)
            {
                //ret之后還有CFG流

                DWORD skip_bytes = jcc_max.jmpLoadImageAddress - ud.pc;
                ud_input_skip(&ud, skip_bytes);
                ud_set_pc(&ud, jcc_max.jmpLoadImageAddress);
            }
            else
            {
                return FunctionNode
                (
                    begin,
                    pc,
                    jcc_flow.memoryFileAddress + jcc_flow.insnLen - begin
                );
            }
            break;
        }

        case UD_Inop:
        case UD_Iint3:
        {
            if (ud_insn_mnemonic(&ud) == UD_Iint3)
            {
                //必須至少出現連續兩個CC指令才說明識別到函數末尾
                if (*((ud_insn_ptr(&ud) + 1)) != 0xCC)
                {
                    break;
                }
            }
            return FunctionNode
            (
                begin,
                pc,
                jcc_flow.memoryFileAddress - begin
            );
        }
        case UD_Iadd:
        {
            if (!memcmp(ud_insn_hex(&ud), "0000", 4))
            {
                return FunctionNode
                (
                    begin,
                    pc,
                    jcc_flow.memoryFileAddress - begin
                );
            }
            break;
        }

        default:
        {
        }

        }

        //如果接下來的代碼是函數的開始特征
        DWORD64 ptr = jcc_flow.memoryFileAddress + jcc_flow.insnLen;
        if (LookNextBegin(ptr))
        {
            //如果排除map中不存在已經分析過的函數則插入節點
            if ((excludeMap != nullptr) && (!excludeMap->empty()))
            {
                if (excludeMap->find(ptr) == excludeMap->end())
                {
                    FunctionNode node(ptr, jcc_flow.loadImageAddress + jcc_flow.insnLen);
                    functionMap->operator[](ptr) = node;
                }

            }
            else
            {
                FunctionNode node(ptr, jcc_flow.loadImageAddress + jcc_flow.insnLen);
                functionMap->operator[](ptr) = node;
            }

            return FunctionNode
            (
                begin,
                pc,
                jcc_flow.memoryFileAddress + jcc_flow.insnLen - begin
            );
        }
    }

    return FunctionNode
    (
        begin,
        pc,
        bufferSize
    );

}

广西快三分析procmon.exe的winmain函數和IDA對比的效果如圖

根據IDA的識別計算一下函數大小0x0045E6B4-0x0045D840=3700广西快三,和自己程序的識別結果一致


函數粉碎

有了上面得到的信息就能進行粉碎了,用前面講的方法將函數進行混淆得到新的代碼塊,
找到reloc段的前面一個段向下合并reloc段創建一個新的text段,把混淆代碼放進去,
最后在新text段后面創建reloc段,修復重定位信息。
混淆之前必須先掃描記錄當前函數的所有重定位信息广西快三,混淆過程中將原始重定位信息和新的重定位信息聯系在一起,以便后面進行重定位修復。

部分代碼實現:

char* x86PEObfuscate::BrokenFunction(FunctionNode function,DWORD *obfucodeSize, vector<RelocFixer> &relocFixBox,DWORD64 VirtulAddress)
{

    //解析指令流
    vector<InstrFlowNode> instrbox = this->m_Analysis.InstrExtract(function.memoryFileAddress, function.size, function.loadImageAddress);
    if (instrbox.empty())
    {
        return nullptr;
    }

    //獲取PE重定位表
    map<DWORD, vector<WORD>> relocTable;
    vector<RelocInstr>relocInstrBox;
    bool hasReloc = this->m_pefile.getRelocTable(relocTable);
    if (hasReloc)
    {
        //得到指令集合中的重定位信息
        checkReloc(instrbox, relocTable, relocInstrBox);
    }

    class ObfuscateInstr
    {
    public:
        ObfuscateInstr() {};
        ObfuscateInstr(const ObfuscateInstr&o)
        {
            this->memoryAddress = o.memoryAddress;
            this->virtulAddress = o.virtulAddress;
            this->prexCodeSize = o.prexCodeSize;
            this->size = o.size;
            this->code = new char[this->size];
            memcpy(this->code, o.code, this->size);
        }
        ~ObfuscateInstr() 
        {
            if (code != nullptr)
            {
                delete[] code;
            }
        }

        //該混淆指令塊被隨機分配到內存中的地址,以便后面指令修復尋找地址
        //隨機方式將chunkbox集合中的指令塊分配到內存
        //修復的時候順序遍歷chunkbox集合,根據相鄰元素的memoryAddress鏈接相鄰的指令塊
        //遍歷chunkbox的時候根據下標找到instrbox中的原始指令元素,判斷當前是否是jcc指令
        //是則根據以原始指令內存地址為key,在orign_chunk_map中找到目標指令塊地址,計算修復當前指令
        DWORD64 memoryAddress = 0;// 該混淆塊被寫入新空間的地址
        DWORD64 virtulAddress = 0;// 該混淆塊的 VA
        DWORD prexCodeSize = 0;  //混淆塊中原始功能指令前面的指令長度=原始功能指令在該指令塊的偏移
        char *code = nullptr;//混淆塊
        DWORD size = 0;

    };

    //對每條指令預進行預處理混淆
    vector<ObfuscateInstr> chunkbox;
    x86::Gp registers[] = { x86::eax,x86::ebx,x86::ecx,x86::edx,x86::esi,x86::edi };
    bool first = true;  //true 代表處理指令流的第一條指令 pop reg
    int index;

    for (auto instr : instrbox)
    {

        CodeHolder code;
        code.init(CodeInfo(ArchInfo::kIdX86));
        x86::Assembler assember(&code);

        //原始功能指令前面
        if (!first)
        {
            assember.pop(registers[index]);
            assember.popfd();//保護標志位
        }
        else
        {
            first = false;
        }

        DWORD prexCodeSize = code.sectionById(0)->buffer().size();  //前綴指令的長度

        //處理原始功能指令
        int insn_len;
        if ((instr.isJcc||instr.isCall)&&(instr.operatorType == UD_OP_JIMM))
        {
            char *new_jcc = new char[6];
            DWORD jcc_padding = 0xAAAAAAAA;//jcc跳轉偏移填充
            WORD jcc_opcode = this->jcc_long_opcode[instr.type];
            if ((instr.type == UD_Icall) || (instr.type == UD_Ijmp))
            {
                //如果原始指令是call或者jmp 這兩種指令opcode和jx類指令的長度不一致 單獨處理   0xE8  0xE9
                memcpy(new_jcc, (char*)&jcc_opcode, 1);     
                memcpy(new_jcc + 1, &jcc_padding, 4);//0xAAAAAAAA占位,必須修復
                insn_len = 5;
            }
            else
            {
                memcpy(new_jcc, (char*)&jcc_opcode, 2);   //0F 80  ....
                memcpy(new_jcc + 2, &jcc_padding, 4);//0xAAAAAAAA占位,必須修復
                insn_len = 6;
            }
            assember.embed(new_jcc, insn_len);
            delete[] new_jcc;
        }
        else
        {
            insn_len = instr.insnLen;
            assember.embed((char*)instr.memoryFileAddress, instr.insnLen);//  目標指令 非jcc指令 直接寫入內存
        }

        //原始功能指令后面
        assember.pushfd();//保護標志位
        index = this->GetRandomKey() % sizeof(registers) / sizeof(x86::Gp);  //隨機選擇寄存器
        assember.mov(x86::dword_ptr(x86::esp, -4), registers[index]);
        assember.add(x86::esp, -4);
        Label label = assember.newLabel();
        assember.call(label);
        assember.bind(label);
        assember.pop(registers[index]);

        int num = -(13 + prexCodeSize + insn_len);//12+1
        assember.add(registers[index], num);  //得到本混淆指令塊開始地址

        assember.add(registers[index], 0xdeadbeaf);   //0xdeadbeaf占位,必須修復
        assember.push(registers[index]);
        assember.ret();

        CodeBuffer& buffer = code.sectionById(0)->buffer();
        ObfuscateInstr instrchunk;
        instrchunk.code = new char[buffer.size()];
        ::memcpy(instrchunk.code, buffer.data(), buffer.size());
        instrchunk.size = buffer.size();
        instrchunk.prexCodeSize = prexCodeSize;
        chunkbox.push_back(instrchunk);
    }

    //將指令塊隨機亂序分配到新空間
    DWORD buffer_index = 0;
    DWORD buffer_size = function.size * 100;
    char * buffer = new char[buffer_size];  //申請100倍原始函數空間的大小
    map<DWORD64, DWORD64> orign_chunk_map;   //修復跳轉指令 key是原始指令內存地址,value是被混淆過后的指令被隨機分配后的地址
    vector <int> indexTable;   //chunkbox的索引表
    for (int i = 0; i < chunkbox.size(); i++)
    {
        indexTable.push_back(i);//保存chunkbox的索引表
    }
    while (!indexTable.empty())
    {
        //隨機選擇指令塊到新內存
        int key = this->GetRandomKey() % indexTable.size();
        int index = indexTable[key];
        memcpy(buffer + buffer_index, chunkbox[index].code, chunkbox[index].size);
        DWORD64 addr = (DWORD64)buffer + buffer_index;
        DWORD64 va = VirtulAddress + (addr - (DWORD64)buffer);
        chunkbox[index].memoryAddress = addr;   //隨機放置的地址
        chunkbox[index].virtulAddress = va;  //該指令塊的va
        orign_chunk_map.insert(pair<DWORD64, DWORD64>(instrbox[index].memoryFileAddress, addr));

        buffer_index += chunkbox[index].size;

        //隨機產生5-20字節垃圾數據
        int junk_size = this->GetRandomKey() % 16 + 6;
        this->GetRandomBytes(buffer + buffer_index, junk_size);
        buffer_index += junk_size;

        //從tmpchunk中刪除當前指令元素
        auto iter = indexTable.begin();
        iter += key;
        indexTable.erase(iter);
    }

    //修復指令間執行順序和jcc跳轉目的地址  注意最后一條指令的處理
    DWORD offset_flag = 0xdeadbeaf;
    DWORD jcc_flag = 0xAAAAAAAA;
    for (int i = 0; i < chunkbox.size(); i++)
    {
        char *begin = (char*)chunkbox[i].memoryAddress;
        char *end = (char*)chunkbox[i].memoryAddress + chunkbox[i].size;
        DWORD offset;
        if (i < chunkbox.size() - 1)
        {
            offset = chunkbox[i + 1].memoryAddress - chunkbox[i].memoryAddress;

        }
        else
        {
            //如果是函數最后一條指令,則隨機跳到前面任意一條原始指令混淆之后的塊(不會執行)
            int k = this->GetRandomKey() % (chunkbox.size() - 1);
            offset = chunkbox[k].memoryAddress - chunkbox[i].memoryAddress;
        }
        //修復相鄰指令執行順序
        char* ptr = this->SearchBytes(begin, end, (char*)&offset_flag, sizeof(DWORD));
        memcpy(ptr, &offset, 4);

        if ((instrbox[i].isJcc|| instrbox[i].isCall) && (instrbox[i].operatorType == UD_OP_JIMM))
        {
            //修復jcc指令跳轉偏移
            char* ptr = this->SearchBytes(begin, end, (char*)&jcc_flag, sizeof(DWORD));
            DWORD64 addr = 0;
            if ((instrbox[i].type == UD_Icall)||(orign_chunk_map.count(instrbox[i].jmpMemoryFileAddress)<=0))
            {
                //混淆是以函數為單位
                //如果當前指令是call或者“偽call”(某種jmp),或者map中沒有跳轉目的指令的記錄
                //總的來說,也就是說明當前指令的目的地址不在本函數空間中
                //這里不完善(其實也不用處理畸形程序)
                //jmpLoadImageAddress是加載地址,可能會因為地址隨機化而改變
                //但是相對va地址不會變
                //chunkbox[i].memoryAddress=begin - (DWORD64)buffer是該指令塊相對于起始塊的偏移
                //加上VirtulAddress就是加載地址va
                addr = instrbox[i].jmpLoadImageAddress;
                DWORD64 va = VirtulAddress + ((DWORD64)begin + chunkbox[i].prexCodeSize - (DWORD64)buffer);
                offset = addr - va - 5;//e9:jmp imm e8:call imm   指令長度是5
            }
            else
            {
                addr = orign_chunk_map[instrbox.jmpMemoryFileAddress] + chunkbox[i].prexCodeSize;

                if (instrbox[i].type == UD_Ijmp)  //e9:jmp imm e8:call imm   指令長度是5
                {
                    offset = addr - ((DWORD64)begin + chunkbox[i].prexCodeSize) - 5;//目標-當前-當前指令長度
                }
                else
                {
                    offset = addr - ((DWORD64)begin + chunkbox[i].prexCodeSize) - 6;//目標-當前-當前指令長度
                }
            }

            memcpy(ptr, &offset, 4);
        }

    }

    //保存新的重定位項
    for (auto relocInstr : relocInstrBox)
    {
        DWORD index = relocInstr.index;
        DWORD64 relocVa = chunkbox[index].virtulAddress + chunkbox[index].prexCodeSize + relocInstr.off;
        DWORD rva = relocVa - this->m_pefile.getOptionHeader()->ImageBase;
        DWORD orignRva = instrbox[index].loadImageAddress + relocInstr.off - this->m_pefile.getOptionHeader()->ImageBase;
        WORD typeOffset = rva % 0x1000;  //新的typeOffset
        DWORD newPage = rva - typeOffset;
        typeOffset |= ((WORD)(relocInstr.type << 12));
        RelocFixer fixer;
        fixer.orignRva = orignRva;
        fixer.newPage = newPage;
        fixer.typeOffset = typeOffset;
        relocFixBox.push_back(fixer);
    }

    //修改原函數入口代碼
    //原始函數剩余空間填充代碼
    char *begin = (char*)function.memoryFileAddress;
    char opcode[] = { 0xe9,00,00,00,00,0xc3 };  //jmp imm
    DWORD64 firstCodeVa = VirtulAddress + (chunkbox[0].memoryAddress - (DWORD64)buffer);//代碼塊的首指令va
    DWORD jmpoffset = firstCodeVa - function.loadImageAddress - 5;
    memcpy(opcode + 1, &jmpoffset, 4);
    memcpy(begin, opcode, 6);
    begin += 6;
    this->GetRandomBytes(begin, function.size - 6);

    char *obfucode = new char[buffer_index];
    memcpy(obfucode, buffer, buffer_index);
    *obfucodeSize = buffer_index;
    delete[] buffer;
    return obfucode;
}

對procmon.exe的winmain函數進行粉碎生成procmon2.exe

procmon2.exe正常運行

混淆前:

混淆后:

原理很簡單,只實現了亂序粉碎的功能,而且是很簡單的函數粉碎,只作為學習的一個玩具demo參考
代碼變形,常量隱藏,導入表加密等等功能都沒有加入沒什么技術含量,代碼很垃圾,大佬輕噴
發個demo bin玩玩,把Obfuscater.exe和procmon.exe放在同一目錄,運行即可生procmon.obf.exe
Obfuscater.exe處理的exe和函數我都寫死了,想用的大佬可以自行逆向patch一下

圖片1.png (108.96 KB, 下載次數: 3)

圖片1.png

圖片2.png (63.76 KB, 下載次數: 2)

圖片2.png

圖片3.png (53.26 KB, 下載次數: 3)

圖片3.png

圖片4.png (118 KB, 下載次數: 3)

圖片4.png

圖片5.png (79.11 KB, 下載次數: 2)

圖片5.png

圖片6.png (357.08 KB, 下載次數: 2)

圖片6.png

圖片7.png (270.22 KB, 下載次數: 1)

圖片7.png

圖片8.png (323.43 KB, 下載次數: 3)

圖片8.png

圖片9.png (127.53 KB, 下載次數: 3)

圖片9.png

圖片10.png (118.04 KB, 下載次數: 2)

圖片10.png

圖片11.png (63.81 KB, 下載次數: 3)

圖片11.png

圖片12.png (81.5 KB, 下載次數: 3)

圖片12.png

圖片13.png (17.78 KB, 下載次數: 3)

圖片13.png

圖片14.png (84.54 KB, 下載次數: 2)

圖片14.png

圖片15.png (10.46 KB, 下載次數: 3)

圖片15.png

溜了.jpg (6.12 KB, 下載次數: 3)

溜了.jpg

demo.rar

1.49 MB, 下載次數: 55, 下載積分: 吾愛幣 -1 CB

免費評分

參與人數 46吾愛幣 +46 熱心值 +42 收起 理由
chen_null + 1 謝謝@Thanks!
alittlehorse + 1 感謝發布原創作品,吾愛破解論壇因你更精彩!
13697i + 1 熱心回復!
FJFJ + 1 + 1 用心討論,共獲提升!
Id!0tNe + 1 我很贊同!
yaoyao7 + 1 + 1 用心討論,共獲提升!
回蕩 + 1 + 1 用心討論,共獲提升!
當我入夢 + 1 + 1 用心討論,共獲提升!
pklong007 + 1 + 1 這個6,我等菜逼何時能達到這種程度
自強 + 1 + 1 謝謝@Thanks!
springwillow + 1 不混淆的我現在都還搞不定,學習了
耶穌 + 1 + 1 感謝您的寶貴建議,我們會努力爭取做得更好!
fsrank + 1 + 1 謝謝@Thanks!
eyey000 + 1 + 1 謝謝@Thanks!
zhan + 1 + 1 謝謝@Thanks!
poisonbcat + 1 + 1 謝謝@Thanks!
北方小白兔 + 1 + 1 用心討論,共獲提升!
QGZZ + 1 + 1 謝謝@Thanks!
韜. + 1 + 1 我很贊同!
冰雪冬櫻250 + 1 + 1 謝謝@Thanks!
Mr.Eleven + 1 + 1 謝謝@Thanks!
Spwpun + 1 + 1 用心討論,共獲提升!
yixi + 1 + 1 謝謝@Thanks!
snatch2null + 1 用心討論,共獲提升!
Mouse + 1 + 1 謝謝@Thanks!
月六點年一倍 + 1 熱心回復!
solly + 1 + 1 用心討論,共獲提升!
sunnylds7 + 1 + 1 熱心回復!
yppsniper + 1 + 1 我很贊同!
wmsuper + 3 + 1 謝謝@Thanks!
N0LL + 1 + 1 謝謝@Thanks!
KCNKCN + 1 + 1 用心討論,共獲提升!
straychild + 1 + 1 用心討論,共獲提升!
Nachtmusik + 1 謝謝@Thanks!
kakudesu + 1 + 1 熱心回復!
gaosld + 1 + 1 用心討論,共獲提升!
blmk + 2 + 1 我很贊同!
二娃 + 2 + 1 謝謝@Thanks!
aassaa + 1 + 1 我很贊同!
gongyong728125 + 1 + 1 熱心回復!
chh183 + 1 + 1 用心討論,共獲提升!
溫柔海洋 + 1 + 1 我很贊同!
Ravey + 1 + 1 謝謝@Thanks!
zhanglei91186 + 1 + 1 我很贊同!
magus + 1 + 1 用心討論,共獲提升!
Dyingchen + 1 + 1 用心討論,共獲提升!

查看全部評分

本帖被以下淘專輯推薦:

發帖前要善用論壇搜索功能,那里可能會有你要找的答案或者已經有人發布過相同內容了,請勿重復發帖。

推薦
Hmily 發表于 2020-1-11 10:37
昨夜星辰恰似你 發表于 2020-1-9 22:27
使用md重新編輯排版了一下 圖片是外鏈過來的,還不是很會用markdown排版時硬傷

圖片可以用discuz自帶的,點擊輸入進去就行了。

免費評分

參與人數 1吾愛幣 +1 熱心值 +1 收起 理由
xiaomin86 + 1 + 1 熱心回復!

查看全部評分

推薦
 樓主| 昨夜星辰恰似你 發表于 2020-3-11 13:23 <
1364847132 發表于 2020-3-11 11:57
樓主為什么不在編譯期做混淆,比如參考ollvm寫個Pass?

只是學習一些東西寫的demo,考慮的是直接處理bin,沒有想過用ollvm,比如我不知道咋用ollvm混淆delphi之類的
沙發
Dyingchen 發表于 2020-1-9 16:03
學習了,經常遇到代碼混淆而且沒有什么好的辦法,有沒有什么辦法反混淆呢
3#
lynn 發表于 2020-1-9 16:07
強大,膜拜啊!
4#
2Burhero 發表于 2020-1-9 17:34
感謝分享
5#
a1069517910 發表于 2020-1-9 20:02
感謝樓主分享!!!
6#
溫柔海洋 發表于 2020-1-9 21:42
感謝分享
7#
 樓主| 昨夜星辰恰似你 發表于 2020-1-9 22:27 <
使用md重新編輯排版了一下 圖片是外鏈過來的,還不是很會用markdown排版時硬傷

點評

圖片可以用discuz自帶的,點擊輸入進去就行了。  詳情 回復 發表于 2020-1-11 10:37
8#
ol416 發表于 2020-1-10 00:32
強大,膜拜啊!
9#
Hmily 發表于 2020-1-10 08:34
@昨夜星辰恰似你 還差圖沒貼好
10#
gongyong728125 發表于 2020-1-10 09:57
學習學習謝謝樓主分享!
您需要登錄后才可以回帖 登錄 | 注冊[Register]

本版積分規則 警告:本版塊禁止灌水或回復與主題無關內容,違者重罰!

快速回復 收藏帖子 返回列表 搜索

RSS訂閱|小黑屋|聯系我們|吾愛破解 - LCG - LSG ( )

GMT+8, 2020-4-3 11:48

Powered by Discuz!

广西快三Copyright © 2001-2020, Tencent Cloud.

快速回復 返回頂部 返回列表