re-part-4
终于,来到了难度4!
easyCpp
0x00
发现是64位的ELF。
一步步分析:
1 | /* |
没咋搞懂,动态调也没看出来,搜了一下要补以下C++的逆向知识。(上述工作耗费了2day
0x01
先看https://blog.csdn.net/larry_zeng1/article/details/93027422
1 |
|
相应解释:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
再来看本题:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
分析到std::transform
时,点进去看看,发现有:
1 | /* |
再来分析std::accumulate
函数:
1 | int64 __fastcall std::accumulate<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,std::vector<int>,main::{lambda(std::vector<int>,int)#2}>(__int64 a1, __int64 a2, __int64 a3, __int64 a4, int a5, int a6, char a7) |
其中有一个main::{lambda(std::vector<int>,int)#2}::operator()
,此函数为:
1 | __int64 __fastcall main::{lambda(std::vector<int>,int)#2}::operator()(__int64 a1, __int64 a2, __int64 a3, int a4) |
重点看其中的std::copy<__gnu_cxx::__normal_iterator<std::vector<int>>>
,有:
1 | __int64 __fastcall std::copy<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,std::back_insert_iterator<std::vector<int>>>(__int64 a1, __int64 a2, __int64 a3) |
其中又调用了std::__copy_move_a2
,其中主要调用了std::__copy_move_a
,里面又调用了std::__copy_move
,再进去看:
1 | /* |
看了好久,终于把std::accumulate
函数的逆序看明白了。(太难了,,一会儿总结一波。。
直接写脚本:
1 | #!/usr/bin/env python |
0x02
前面的题目分析,写的有点烂,再来归纳总结一下。最后输出为奇数的输入数字。
0x03
其他师傅大多用的是动调。第一次做正经的cpp题目,感觉难度还是比较大的,主要是得细心分析,应该就没问题。
自己又动调了一下,在下图红框所示打断点:
经过0x00与0x11的分析之后,输出变量为v30。
点进去之后发现:
一开始动调就是卡到这里了,看了其他师傅的wp。他们是这样做的:
- v30是_int64 [4]的数组,所以对栈中的元素选中并进行分析,就会得到:
- 点进去之后发现:
发现是逆序输出,结束!!
PS:还得多多练练动调技能,要不一开始咋没做出来呢。
getit
0x00
发现是64位的ELF。
看了一眼,发现最后把生成的/tmp/flag.txt
又删除掉,直接不让他删除,在remove
上打断点,然后直接cat /tmp/flag.txt
发现是:
竟然还被隐藏了,再回头仔细瞅瞅。
直接在第一次strcpy(stream, "%s\n", u)
中下断点,直接得到:SharifCTF{b70c59275fcfa8aebf2d5911223c6589}
。是正确的。
也可以脚本:
1 | #!/usr/bin/env python |
easy money。
0x01
为什么最后变成了**********
呢?分析一波:
在第二次循环下断点,记录每一轮时的参数值。
1 | round0: |
原来是把指针调整到开头直接用u
覆盖了。。
知识点1:
[1]fseek
作用:为调节文件指针位置。
应用场景:当我们使用文件指针通过fwrite()函数向文件中输入数据,假设数据为字符串“麻辣香锅”,输入完毕后文件指针的指向位置实际上是“锅”的后面,相当于光标。此刻如果使用fread函数是无法正常读取文件内容的,原因是fread只能读取指针往后的内容。需要将文件指针前置,这时候就需要用到fseek函数。
使用:fseek(arg1, arg2, arg3)
。
参数详解:arg1:文件指针。arg2:偏移量。arg3:
[2]fputc
csaw2013reversing2
0x00
发现是32位的PE。
打开报:(它说是连接了一个debug信息,但是我这没有呀
运行了一下,发现:(乱码
程序主函数如下:
但是到不了这里,因为有IsDebuggerPresent()
。
看一下sub_40100
:
方法1:直接用动调,在if(!sub_1B102A() && !IsDebuggerPresent())
上设置zf=1
,直接跳过sDebuggerPresent()
的判断,最后运行完sub_40100
之后看lpMem
,发现是:flag{reversing_is_not_that_hard!}
。
方法2:
一直不知道传入a2
时a2
的值,调试之后发现是0xbca0ccbb
,又想了想,反正v3-(a2+2)
会将a2
抵消。计算result=((v3-(a2+2))>>2)+1=((a2+1+35+1-(a2+2))>>2)+1=9
,直接循环9次。可以写脚本:
1 | #!/usr/bin/env python |
忒简单了。
0x01
看了其他师傅的wp,有的是直接跳到了弹窗部分,直接显示flag,感觉比较帅,试一试。他是直接从call ds:ExitProcess
跳到callloc_1B10B9
了。
调试之后,发现:
妈的还是个坑!cao
真实的MessageBoxA
调用
虚假的MessageBoxA
调用
不惯着他,直接添加指令(但是添加指令好像很麻烦,得扩展段的空间,直接屈服,老老实实跳吧)
一个字,优雅。
这里就可以知道为什么调用第一个弹窗会失败了,因为第一个字符解码后为’\0’,直接截断,所以会输出空白。原来如此,所以才会有inc eax
操作,学到了。题目简单,但是知识的确很多。
0x02
知识点1:win32的函数
函数名 | 作用 |
---|---|
HeapCreate(arg1,arg2,arg3) | 给进程创建堆。arg1为堆分配选项(不太重要),arg2为堆的初始大小,arg3为堆的最大大小,返回值为新创建的堆的引用。 |
HeapAlloc(arg1,arg2,arg3) | 从堆中分配一块内存。arg1为堆的句柄,arg2为堆分配选项(不太重要),arg3为要分配的字节数,返回值为分配的内存块的指针。 |
IsDebuggerPresent() | 确定调用进程是否正在调试器中调试,若未在调试器中运行,则返回值为零。 |
MessageBoxA(arg1,arg2,arg3,arg4) | 显示一个对话框。arg1为创建的消息框的所有者窗口的句柄,arg2为要显示的消息,arg3为对话框标题,arg4为对话框的内容和行为(有几个按钮)。 |
HeapFree(arg1,arg2,arg3) | 释放从堆中分配的内存块。arg1为要释放其内存块的堆的句柄,arg2不重要,arg3为要释放的内存块的指针。如果函数成功,则返回值为非零。 |
HeapDestroy() | 销毁指定的堆对象。 |
ExitProcess(arg1) | 结束调用进程及其所有线程。arg1为进程和所有线程的退出代码。 |
知识点2:ida使用:
- option->setup data types可以将数据由字节变为字/双字等。
- patch完字符后,如果要保存, IDA->edit->Patch program->Apply patches to input。
知识点3:C语言strlen函数不包含”\0”
no-strings-attached
0x00
32位的ELF。运行了一下:
搜了一下是:越界访问了动态分配的内存。
主函数为:
方法1:
最简单的方法,动调直接看s2
,但是前面运行的时候根本到不了Please enter authentication details:
这一步,推测应该是wprintf(&unk_80489F8);
访问了那个随机数rand()
。
动调试了试,发现动调可以继续往下运行:
之后直接到s2 = (wchar_t *)decrypt((wchar_t *)&s, (wchar_t *)&dword_8048A90);
,看s2
为9447{you_are_an_international_mystery}
,轻松得到flag。
但是运行到fgetws
时,就直接卡住。(奇怪,在这里越界访问了动态分配的内存)
还有一个奇怪的点就是,运行到prompt_authentication
函数时,call _wprintf
没及时打印出字符串,而是等到authenticate
的fgetws
才打印出来。
- 尝试直接跳过
prompt_authentication
函数,没有效果,应该不是_wprintf
的问题。 - 尝试将下图的0x2000改成0x100,也不行。
- 再仔细分析一下,正常能打印出来的
_wprintf
是这样的:
prompt_authentication
中的_wprintf
是这样的:
突发奇想,ESP-18h
是不是干扰了_wprintf
的输出?直接判定字符串结尾了,输出为空?
试一试。直接把sub esp 18h
nop掉,还是打印不出来。又看了看,别的_wprintf
也有sub esp 18h
。最后,我终于知道了,因为Please enter authentication details:
最后没有换行符(0Ah
),最后加上换行符0Ah
就OK啦。
但是还是报错,只不过报错是在Please enter authentication details:
这行之后了。
试验了一下,如果按如下方式改的话,就不会报错啦(我感觉应该是一开始给的空间太大了,堆空间溢出到了程序动态链接的地址中)。但是,输入正确的flag之后还是报Access Denied
。(看一看其他人的wp仔细研究一波)
方法2:
接着分析一下decrypt(&s, &dword_8048A90)
:
很简单,直接上解密脚本:
1 | #!/usr/bin/env python |
0x01
其他师傅wp写的都挺水的,还是我牛逼啊。哈哈
知识点1:宽字符是宽度始终为 16 位的多语言字符代码。 字符常量的类型是 char
;对于宽字符,该类型是 wchar_t
。
知识点2:wprintf
打印宽字符。
知识点3:
setlocale(arg1,arg2)
。设置或检索运行时区域设置。arg1
受语言环境影响的类别,arg2
语言环境说明符。
Hello, CTF
0x00
32位的PE。扔进ida瞅了一眼,我感觉这难度4的有失难度4的水准,感觉比难度3简单。
其实就是输入字符串,对字符串做处理,然后与指定的字符串比较,我感觉这种题已经被做烂了。
1 | #!/usr/bin/env python |
tar-tar-binks
0x00
两个文件,竟然是17年的题,唉后悔自己入门re太晚。libarchive.dylib
占大头。
搜了一下macOS系统的动态库使用dylib文件作为动态库,是64位的MAC OS X Mach-O
。
我心想ida这么牛,应该能打开吧。
好多函数,不愧是链接库。
对flag.tar
进行解压显示文件损坏。解压这个文件花了我好长时间(1 day),他的文件头是正确的,一开始我猜测是校验和不对,但是网上搜索不到对应的脚本。之后我又猜测是不是加入了ustar
的magic
字段的元素,但是最终还是失败了。最终下载了一个修复工具DataNumen TAR Repair
,就直接修好了。(无语住了,有时候工具真管用)
用010editor看了一下,最初的tar
文件头为:
修复后的文件头为:
是把mod,uid,gid,size,mtime,chksum
改了,感觉确实是校验和的问题。(无奈自己太菜了,之后看看其他师傅的wp看看有没有计算校验和的脚本。)
里面有一个flag.txt
:
1 | F5D1,4D6B,ED6A,08A6,38DD,F7FA,609E,EBC4,E55F,E6D1,7C89,ED5B,0871,1A69,5D58,72DE,224B,3AA6,0845,7DD6,58FB,E9CC,0A2D,76B8,ED60,251A,1F6B,32CC,E78D,12FA,201A,E889,2D25,922A,4BC5,F5FF,F8E5,C79B,3A77,4BDB,EA11,5941,58BD,3A95,F5C9,A225,AD40,F8BD,095D,70B6,458C,E7A9,EA68,252F,094B,5E41,0969,6015,5ED5,F6E5,59B9,7CAF,66DF,265B,7837,57B4,7CAF,AED9,F707,6A3C,F8E5,F509,7C8B,0915,2235,336F,33E9,2D14,7C91,5804,83E5,E78D,F4EA,0874,ED6B,4B35,E839,57B4,E77C,EA68,2525,AD41,ED6F,3A4A,4BCC,6015,F440,0858,3AA6,7809,671D,0874,EA77,63AF,2E91,5845,F6C4,086D,7795,3939,57B4,7C89,82DC,32ED,B994,C7AF,9135,0E65,1B66,ED5B,3235,6577,5A80,3AD3,E776,1EE5,AD41,ED59,864C,70B4,3876,ED67,64D6,F8E5,F505,EAD9,7C9C,32ED,B994,B4EF,0C6C,F665,F5F5,9047,521A,E99E,EA68,252F,9D09,76B7,E776,1ED0,095D,0D4D,5D5A,087B,2005,1526,7E76,85AD,78B9,E8B6,782C,251C,32ED,7F68,EBE3,EA41,57FD,ED59,846D,7A05,B994,BB78,ED6A,08A6,38DD,3B5D,7E45,E839,738C,E9CC,0A2D,764A,609E,E8B6,EA68,2524,E6BB,7C9C,639F,3A95,0895,F40F,8328,EA69,7EE5,F8BD,7F7D,0D6D,70B6,458C,E8B6,EA68,251C,6065,B35F,C789,5845,7F7D,6D89,4C6E,A20E,60B5,7E45,ED59,F707,69EF,922A,4BC5,F6EF,8635,F4B9,57B4,7CF8,ED60,2510,095D,20AF,3545,F40F,8328,EA41,58A4,225D,7E7C,4BDB,F8BD,082C,EAE7,5D57,5D50,0914,E7C7,8624,7CF8,ED60,2511,7C8E,7159,8416,7EF9,E7E5,774A,3895,1EC9,7C90,09B9,58BD,5FF5,E99E,EA68,250A,224C,EA3D,73F5,7C89,53A6,3190,3B5D,1526,7DD5,666A,0919,225F,CDEF,79E1,7E7B,7E6B,082C,A277,E885,E8BB,E775,5FF7,EA68,251B,7FDF,589D,7A05,779A,8A5A,7C91,5D5C,32ED,F628,2195,F49A,0C77,EAE1,59B9,58BD,E570,E99E,EA3D,73F9,13AD,2BF5,225D,7F7D,70B6,4A9C,337A,1EC9,4D05,7E75,2578,ED59,38E5,1ECA,A210,3B5D,779A,8A6F,C790,2518,4B41,7C89,5D49,4D05,152D,73C5,79F9,4BED,913C,37C9,5D4D,53C8,0941,7C97,5D5B,346A,82D8,5F36,801F,C800 |
其实不修复也能看到,可能我比较追求完美吧,嘿嘿。应该是加密后的密文,猜测是用dylib库内的某个函数加的密。
在库中找到这样一个函数:
与文件结果很匹配,猜测就是打开一个文件,加密之后把结果以大写16进制写入了flag.txt
。
追踪sub_101
数组:
追踪a1
:
追踪sub_1023456
:
发现sub_1023458
中的a1
数组的元素始终在[0-39]之间。(可以遍历)
一开始写了一个遍历脚本,64线程,跑了1day,没跑出来。我的垃圾脚本如下:
1 | #!/usr/bin/env python |
最终,仔细瞅了瞅sub_1023456
,总结如下:
- 当输入字符为
a-z0-9
,返回ctable
中对应的标号。 - 当输入字符为
A-Z(!@#,.?/*)<>
时,返回39
与返回ctable
中对应的标号-39 - 输入其他字符时,返回37。
可以写脚本:
1 | #!/usr/bin/env python |
输出了一段话,看了好久不知道啥意思,为啥放在flag.txt
里啊,不理解。
之后我又试了试md5
,sha1
,sha256
,都不是正确的flag。准备看wp了。
0x01
看了一下,我真是草泥马。最后是md5加密,为啥我的不对,草泥马。
吐槽一下,继续瞅瞅。
最终,我发现一个很有意思的事情,我在线md5加密和python的md5加密不一样。仔细瞅了瞅,发现python对字符串encode
之后,是这样的:
而在线md5无法输入\x00\x00
,所以导致了不一样的问题。悔恨啊!
但是,最终的结果更狗。最终md5是先把尾部的\x00\x00
去掉,然后再加上\n
,然后再md5。行吧,以后记得加\n
。
上述脚本中加入,获得flag:
1 | m = hashlib.md5() |
关于解压的问题,别的师傅根本没详细说,感觉他们可能以为这个太简单了?
搞了好久,其实就差最后一口气。
知识点1:Windows系统的动态库是DLL文件,Linux系统是so文件,macOS系统的动态库则使用dylib文件作为动态库。
知识点2:tar文件结构。tar程序是一个归档程序,它将文件存储在一个单独的归档文件中,而不进行压缩。
https://blog.csdn.net/DisMisPres/article/details/94733424
知识点3:输出长话要试一试各种哈希算法,哈希算法结果也试一试大小写。而且加密的时候字符串一般都是先把尾部的\x00
去掉,然后加上\n
再哈希。
handcrafted-pyc
0x00
这个题目的名字:手工制作pyc,文件后缀.py_bc552f58fe2709225ca0768c131dd14934a47305
好长。exeinfope说是py文件。
改成py文件之后瞅瞅。
1 | #!/usr/bin/env python |
知识补充:
1. marsha1库。Python的marshal模块,顾名思义也是负责在Python数值与二进制字节对象之间进行转换的。但是,marshal模块仅供Python解析器内部用作对象的序列化,不推荐开发人员使用该模块处理Python对象的序列化和反序列化(更推荐用pickle)。marshal模块不是通用的序列化/反序列化模块,而是以读写.pyc文件中的Python代码为目的设计的。marshal模块提供的函数可以读写二进制对象为Python数值。这里的二进制对象是Python字节定义的独特二进制格式,与所在机器的体系结构无关。
函数名 | 函数作用 |
---|---|
load(file) | 从文件读取Python数值并返回该值 |
loads(bytes) | 将读入的字节对象转换为Python数值 |
dump(value, file) | 将Python数值写入到文件 |
dumps(value) | 将读入的Python数值转换为一个字节对象 |
2. pyc文件。pyc文件是py文件编译后生成的字节码文件(byte code)。pyc文件经过python解释器最终会生成机器码运行。所以pyc文件是可以跨平台部署的,类似Java的.class文件。一般py文件改变后,都会重新生成pyc文件。
补充完毕!
之后准备生成handcrafted.pyc
文件,脚本如下:
1 | #!/usr/bin/env python |
然后使用uncompyle6
将pyc转为py,报错:
之后我们就要修复生成的handcrafted.pyc
文件。
学习完pyc文件分析后,对比并在文件开头加3个字节,并将开头四个字节改为03 F3 0D 0A
,表示是python2.7。之后使用uncompyle6反编译仍然报错:
但是还是生成的python文件,瞅了瞅,里面写了一些opcode乱码。其实可以直接看到flag了,乱码如下:
1 | # uncompyle6 version 3.8.0 |
里面这么多chr
函数,还有数字。写个脚本瞅瞅:
1 | #!/usr/bin/env python |
输出有flag的样子了,但是看着很奇怪,还是老老实实反编译吧。
唉,看看wp吧,没做过这种题鸭。(我就是fw
0x01
看了一下其他师傅的wp,感觉自己还是能做出来的啊,都是没真正反编译成功,都是看字节码(自己没有耐心看了,这是我的问题。
一开始吐血的是python2可以直接运行,我说python3咋运行不出来,但是又想自己在文件头前面加的是python2.7的头,瞬间觉得自己真傻逼。
OK,事已至此,不具体看wp了,自己分析并写脚本好吧。
补充知识:python虚拟机
1. ROT_TWO,python opcode,主要是交换两个变量的值:
1 | TARGET(ROT_TWO) { |
2. python字节码执行函数,例如有这样一个函数:my_function(my_var, 2)
。它的执行过程是这样的:
1 | //Python 将转换为一系列字节码指令: |
3. BINARY_ADD。来看看Python的a = b + c语句, 其汇编码为:
1 | LOAD_NAME 0 (b) |
相当于把b,c以此压栈,最后add,把结果压栈,最后将栈顶元素给a。
4.JUMP python opcode。
JUMP_FORWARD
: 字节码中的相对跳转。获取要跳过的字节数。JUMP_IF_FALSE_OR_POP
,JUMP_IF_TRUE_OR_POP
,JUMP_ABSOLUTE
,POP_JUMP_IF_FALSE
, 和POP_JUMP_IF_TRUE
都在字节码中采用绝对跳转。
5.FAST_DISPATCH的定义Python/ceval.c#L980-L1013。简要总结就是,当找到预测时,直接进行跳转,否则,回到switch开始部分。“每一个opcode都应该以goto error(当操作失败时)或者fast_dispatch/dispatch结尾”,同样值得注意的时,DISPATCH会执行一次对eval_breaker的原子检查,如果通过了,就会跳过冗长的main loop前置的操作。
6.LOAD_FAST(var_num)将指向局部对象co_varnames[var_num]的引用推入栈顶。STORE_FAST (var_num)将TOS(堆栈顶部项)存放到局部对象 co_varnames[var_num]。
7.LOAD_GLOBAL(namei)
。加载名称为 co_names[namei]
的全局对象推入栈顶。
补充完毕!
仔细分析了一波,关键在这里:
运行流程是这样:
1 | 1. 运行ROT_TWO之后取出栈顶元素给password。 |
写脚本:
1 | #!/usr/bin/env python |
直接输入:
得到flag:hitcon{Now you can compile and run Python bytecode in your brain!}
。
当然,也可以在跳转之后运行咯,进行判断的时候其实栈中就一个元素,就是Call me a Python virtual machine! I can interpret Python bytecodes!!!
。
分别对两个跳转进行分析:若在下图跳转:
则修改脚本为:
1 | #!/usr/bin/env python |
说明跳转过后是错误的分支。
若不跳转,则将上述脚本改为end=648, begin=336
,得到:hitcon{Now you can compile and run Python bytecode in your brain!}
。
真鸡儿爽嘿嘿。
知识点1:py格式化文档
https://blog.csdn.net/qianbin3200896/article/details/90180980
知识点2:常用的pyc转py方法
1. https://tool.lu/pyc/
2. ``uncompyle6 -o xx.py xx.pyc``
知识点3:python -m xx.py意思是将xx.py当作模块处理。
知识点4:python 列表反转是xx.reverse();字符串反转是xx[::-1];list(filter(None, xx))自动去掉xx中为假的值。
0x02 附录:
output.txt如下:
1 | L. 1 0 LOAD_GLOBAL 0 'chr' |
mfc逆向-200
0x00
知识补充:
MFC是啥:MFC(Microsoft Foundation Classes),以C++类的形式封装了Windows的API,并且包含一个应用程序框架。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
补充完毕!
查看了一下大小,1.8M,算是比较大的了。
点开:
发现是VMProtect的壳,32位程序。查了查资料,感觉这个还不好脱壳。
最近学习状态不太好,准备看wp了。我真是废物!!
常见的壳:UPX(CTF用的多,垃圾壳),VMProtect(有虚拟机保护),ASProtect(应用最广),Armadillo(各种保护手段),EXECryptor(猛)。
0x01 fw的wp复现
参考博客1:
https://blog.csdn.net/xiao__1bai/article/details/122244983
首先,他没有重点关注vmp的脱壳。
知识补充:
在mfc-study这一博客中也提到了。简单来说,mfc把监听的消息以及相应的处理函数放到了一个静态数组中,如果能找到这个数组,就容易做逆向分析工作。要在整个内存块中找出一个数组来并不容易,不过好在MFC框架为了方便其自身,定义了查表的接口:
1 | virtual const AFX_MSGMAP* GetMessageMap() const; |
有很多工具可以实现这一点,例如窗口查看分析工具SPY++、专门的 MFC 内部分析工具XSPY等。
更详细的:
补充完毕!
那么,首先打开本题给出的exe文件,之后使用spy++定位窗口:
发现类名944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b
。
之后,使用xspy找到此类的相关信息:
上图红框代表消息处理函数,其中要注意的是0464
。其余函数的信息为:
可以写c脚本:
1 | // 1.cpp : 定义控制台应用程序的入口点。 |
也可以用python写:
1 | import win32gui |
函数补充:
补充完毕!!
发现得到:
直接用密码学综合工具:
得到flag为thIs_Is_real_kEy_hahaaa
。
0x02 总结
其他人的博客差不多也是这么写的,但是真正没有脱vmp的,以后遇到再说吧,感觉是被vmp吓到了。
找到了另一个exeinfope.exe的替代,detect it easy。有时候exeinfo不准,综合起来用更好。
知识点1:
常用的VMProtect脱壳:https://github.com/lmy375/awesome-vmp/blob/master/README.md
知识点2:
stdafx.h在C++中起到的作用是头文件预编译,即把C++工程中使用的MFC头文件预先编译,以后该工程编译时,直接使用预编译的结果,以加快编译速度。C++编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h并不是标准C++头文件,与项目的源代码文件存放在同一个文件文件夹下,通过#include”stdafx.h”引用。stdafx的英文全称为:Standard Application Framework Extensions(标准应用程序框架的扩展)。
编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include “stdafx.h”前的代码都是预编译的,它跳过#include “stdafx. h”指令,使用projectname.pch编译这条指令之后的所有代码。
因此,所有的MFC实现文件第一条语句都是:”#include “stdafx.h”。在它前面的所有代码将被忽略,所以其他的头文件应该在这一行后面被包含。否则,你将会得到“No such file or directory”这样让你百思不得其解的错误提示。
The_Maya_Society
0x00
玛雅社会,有点小帅。
给的文件目录结构为(tree /f > result.txt
):
1 | └─src |
launcher为64位的ELF。
打开The Maya Society.html:
猜测这里的launcher就是给的那个文件。
之后,我就分析那个launcher文件,流程大概就是:
- 产生
014dfa2ce4b0ef535ca0b0094f29f75c.fluxfingers.net
,赋给dest
变量。 sub_56447D2018A4
函数,主要是对上述网址发起DNS询问,返回两个双引号之间的结果v20
。- 将
v20
交给sub_56447D2015E0
处理(主要是类似于base64加密的功能),得到v19
。 - 将
v19
交给sub_56447D201858
(异或操作),生成v18
。 - 最后
v18
是个函数,函数有一个参数就是v19
。
一开始,我想着找到一个没有被引用的函数,找来找去只找到一个base64。明天再瞅瞅!
明天又瞅了瞅,发现第1步产生的结果是变化的,变成了:05469fb88bbaa21271ae17f419011b34.fluxfingers.net
。chatGPT太鸡儿牛了,一眼发现sub_56447D200B5A
函数是md4。又问了问GPT的sub_55CA11A018A4
,是返回ip地址。而sub_56447D2015E
是base64_decode
函数。
看了很久,还是不知道:((void (__fastcall *)(char *))v18)(v8);
是啥意思,我一开始认为v18是某个函数的地址,但又找不到类似的函数。
手足无措了,准备看wp。
看了之后,首先,sub_56447D200B5A
是md5,chatGPT搞错了。之后,他是把linux时间位跳到了玛雅末日那一天,即2012-12-21
,这个的确需要脑洞。但是这个题是复现不出来的,因为题目太老了,对应的xxx.fluxfingers.net
域名已经关闭。
最后,这道题就是构造好xxx.fluxfingers.net
发送给服务器解析,获得一些东西,并使用Base64解密,并异或,最终得到flag。(官方flag:flag{e3a03c6f3fe91b40eaa8e71b41f0db12})
0x01 总结
题目不重要,但这道题能学到很多,要总结。
0x1.1 如何判断base64与md5加密函数
md5加密函数:
1. 2个64元素的数组。
2. 4个起始寄存器。
3. md5加密512位的倍数,但是长度以64比特加在后面,所以内容占448比特。
4. md5算法具体实现:
base64加密:
语雀文档里写过。
base64解密:
0x1.2 此题中爆破日期的方法
当时也想过,能不能遍历日期?
思想是:写一个动态链接库,里面包含一个伪time函数,让launcher运行时调用伪time函数,获取我们指定的时间,另一方面,我们可以再使用一个bash脚本,来进行循环。
1 | // faketime.c |
1 | // victim.c |
1 | # time.sh |
0x1.3 ((void (__fastcall )(char ))v18)(v8); 是啥意思
看了看别人的wp,说是花指令动态函数,解题用不到,我服了。
0x1.4
其实这道题,看完之后觉得不是特别难,只是之前没有掌握遍历的方法,但是最重要的是:看到题目,应该要想到世界末日,但是没想到,唉,脑洞问题,还得继续修炼呀。
What-does-this-button-do
0x00
看到这个名字,肯定是mfc类似的东东,肯定要关注某个按钮,die看一下,发现是apk文件。
用jadx打开瞅瞅:
上述onCreateOptionsMenu
是创建菜单,onCreateOptionsMenu
是根据itemid
来响应事件,都不是重要,重要的是OnCreate
函数,大概意思就是如果输入密码为EYG3QMCS
,就进入FlagActivity
。
再来看FlagActivity
:
直接得到flag了,有点简单。。
上脚本:
1 | #!/usr/bin/env python |
还有一种方法,直接把后缀改成.apk
,放到mumu里,输入密码EYG3QMCS
简单。
0x01 补充
看了其他大佬的wp,补充一下知识,关于android apk文件的结构:
jadx与jeb都能对java 安卓进行反编译。jadx反编译能力较弱,抗干扰能力很弱,对于一些嵌套循环的反编译展示能力很差,jeb反编译能力极强,能够代码跟踪,添加备注,方法重命名等等。
JEB
主要用来反编译apk
文件的,jd-gui
是用来反编译jar
可执行文件的。
感觉jeb更牛逼,和ida似的。
1 | jeb 常用指令 |
有的师傅用dex2jar将classes.dex反编译成jar之后用jd-gui打开。试一试。
首先把后缀改成.zip,然后解压,使用./d2j-dex2jar.bat classes.dex
得到jar。之后放入jd-gui中,分析方法一样。
2ex
0x00
我说我咋觉得以前做过,找了找,真让我找到了,以前做了一道2ex1的题目。当时是看的wp,这次不能看了奥。
查了查die,是32位的mips。
发现就是原题。。是个换码表的base64,是靠逆向嗅觉的。其实想想,2ex不看wp确实挺难的。
复习一下换码表:
- 用新码表转成旧码表。
- 对结果进行base64解码。
1 | # script 1 |
留言
- 文章链接: https://wd-2711.tech/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!