re-part-3
感觉之前一道题写一个post的方式太繁琐了,所以今后打算某一个难度的题写到一个post里,例如re-part-3就是逆向难度为3的问题。
debug-wp
0x00
.NET+dotfuscator,搜了一下用了混淆。回顾一下知识点dnsPY用于动态分析,ILSPY用于静态分析。直接用dnsPY和ILSPY打开瞅瞅,发现:
发现有乱码,应该是用dotfuscator混淆过后的。在网上搜了搜,说是用de4dot对刚才的程序去混淆。
混淆之后,发现:
逆向成python脚本,如下:
1 | #!/usr/bin/env python |
试了试不行,不知道啥原因,然后用dnsPY
动态调试一下,发现:
发现是大写,离谱。
2ex1-wp
0x00
题目有两个文件,分别为mx
和out
,推测out
是输出的文件,将mx
放到exeinfope里面,发现是32位的MIPS。
看了看mx
里面,发现好多函数,直接看蒙了。之前也没做过关于mips的题目,所以果断wp。
0x01
妈的,这个题确实离谱。这个题是一个base64换码表的题,这需要一种逆向嗅觉
。
首先,这么多函数,肯定不能一个一个看啊,通过查找字符串,可以找到:
哎,这里有一个64长度的数组,再结合out
中的内容:_r-+_Cl5;vgq_pdme7#7eC0=
,猜测可能是一个base64换码表,之后直接上脚本:
1 | # script 1 |
看了脚本之后,发现他的流程是这样的:
- 根据
miwen
找key1
中对应的索引值。 - 根据索引值找
base64
中对应的值。 - 对找到的值进行base64解密。
还学到python的maketrans方法,开了眼儿了。
1 | # script 2 |
0x02
感觉这题和mips没啥关系啊。
其实感觉自己能做出来,就是遇到没见过的,心里慌了。
知识点1:Angr,基于python的二进制框架。一个多架构的二进制分析平台,具备对二进制文件的动态符号执行能力和多种静态分析能力。值得一学。
知识点2:三大主流架构:mips,Arm,x86。
catch-me-wp
0x00
看一下格式:
不是exe文件,而是XZ package格式。直接改一下后缀,改成1.xz
,之后用xz -d 1.xz
,报错。
之后用7-Zip解压,解压之后放到exeinfope里看一下,发现是tar
文件,再改后缀,再解压,就得到了原始的文件。原来这个文件原始的后缀名是1.tar.xz
啊。对应linux解压文件命令应该是:tar -xf 1.tar.xz
。
Catch_Me的话,是一个Debian x64的ELF。
0x01
瞅一眼:
主要就是第2个红框,这是判断第1个红框中生成的hatstack是否正确的条件。
分析第2个红框:
其中,_mm_cvtsi128_si32
意思是提取最低的32位。
_mm_ add_epi32
意思是:
_mm_srli_si128
函数意思是(右移imm*8位):
_mm_unpacklo_epi8
函数的意思是(低64位按8位交错):
_mm_unpackhi_epi8
函数的意思是:(交错就是相互叠加,差不多这意思,8指的是按8位交错,最后就是高64位按8为交错)
可以得到v13=2248, v12=2240
。
1 | for v13 in range(4, 1000000): |
再反推v10
与v11
,由于:
1 | v5 = _mm_load_si128((const __m128i *)haystack); |
得到:
最终我们得到:
其中v9
数组是haystack
的后128位,v5
数组是haystack
的前128位。那就继续分析haystack
,这是由byte_6012A8
数组的来的,而byte_6012A8
数组又是由v3
数组得来的,而v3
数组又满足(*(_DWORD *)getenv("CTF") ^ v3) == 0xFEEBFEEB
条件,如下图所示:
其中,HIBYTE
函数的意思是得到高一半字节。由于v3
指向ebx
,那么动调得v3=0xB11924E1
,要满足(*(_DWORD *)getenv("CTF") ^ v3) == 0xFEEBFEEB
条件,可以计算出CTF=0x4FF2DA0A
。
分析了很久,觉得getenv("ASIS")
与getenv("CTF")
没啥用,因为这俩是环境变量,你本地没有这个变量,得到个屁。
在把程序从上到下捋一遍,先上图:
- 动调得到
v3=0xB11924E1
,haystack=[0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60, 0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C, 0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B, 0xB1]
。 - 动调得到第2步的4个数组值:
[0xb1,0x19,0x4,0xa1]
。 - 第3步得到
aCTF=0x4FF2DA0A
。 - 第4步猜测
dword_6012AC=0x4FF2DA0A
,则结合第2步结果,可以得到byte_6012A8=[0xb1,0x19,0x4,0xa1,0x4f,0xf2,0xda,0xa]
或[0xb1,0x19,0x4,0xa1,0xa,0xda,0xf2,0x4f]
。(基础没记牢,不知道字符串怎么放的了,事后发现是按后者存放的,那么记录一下:对于ELFx64,字符串低位放低地址,属于小端序)。 - 第5步对
haystack
做操作:
1 | haystack=[0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60, 0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C, 0xEE, 0x2F, 0x37, 0x96, 0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED, 0xC1, 0x2B, 0xB1] |
还要验证得到的result
是否满足:
发现不满足,感觉是中间操作的进位什么的忽略了,所以导致了错误。
得到flag,太鸡儿幸福了。
补充:HIBYTE是取32位的高8位,BYTE2是取32位的次高8位,以此类推。
0x02
其实,第4步不猜测也可以做出来,脚本为:
1 | #!/usr/bin/env python |
上述solution2跑的太慢了,而且C语言竟然支持_mm_load_si128等函数..(早知道就不写这么多垃圾函数了。。
直接上C脚本:
1 | // FuseCode.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 |
奇怪的点就是顺序跑的时候竟然byte_6012A[8] = {0xb1,0x19,0x4,0xa1,0,128,141,2};
也满足条件,就离谱。看一看其他大佬的wp。
0x03
其他大佬验证getenv("ASIS")
是通过导入环境变量完成的。
知识点1:xz是一种压缩文件格式,采用LZMA SDK压缩,目标文件较gzip压缩文件(.gz或.tgz)小30%,较bz2小15%,是对一串文本压缩。xz是绝大数linux默认就带的一个压缩工具。
知识点2:getenv函数,从环境中取字符串,获取环境变量环境变量值,不是运行时传入参数!!!C 库函数 char *getenv(const char *name) 搜索 name 所指向的环境字符串,并返回相关的值给字符串。
1 | // program.c |
输出:
1 | PATH : /sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin |
知识点3:SSE2,Streaming SIMD Extensions 2,是一种IA-32架构的SIMD指令集。
- IA-32属于X86体系结构的32位版本,即具有32位内存地址和32位数据操作数的处理器体系结构。
- SIMD全称Single Instruction Multiple Data,单指令多数据流。以加法指令为例,单指令单数据SISD的CPU对加法指令译码后,执行部件先访问内存,取得第一个操作数之后再一次访问内存,取得第二个操作数;随后才能进行求和运算。而在SIMD型的CPU中,指令译码后几个执行部件同时访问内存,一次性获得所有操作数进行运算。这个特点使SIMD特别适合于多媒体应用等数据密集型运算。
- SSE2指令的声明都在emmintrin.h头文件中可以找得到。
举例:
知识点4:IDA调试控制台程序,要传递参数。 在debugger->procession options里
知识点5:ELF文件段分布
知识点6:大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。 这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。 小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。
知识点7:补充一些基本知识:(注意:export只能在当前终端中有效,终端关闭就没用了)
1 | # (通过printf可以导入十六进制整数类型,值得积累) |
知识点8:
zorropub
0x00
查看文件类型为ubuntu 64位的ELF。
0x01
感觉还是比较简单的,中间需要自己手动分析一下,直接上C脚本:(我是在ubuntu 20.04上跑的)
1 | /* |
本来想用python写脚本的,但是python的rand什么的得调用C的函数,比较麻烦。
0x02
看了一下其他师傅的wp,发现也可以用pwn做,自己也试一试。(不看wp脚本,自己写
对题目再次进行分析,发现首先发送了一个v5
,然后再发送v5
个v6
。我们要满足16 < v6 <= 0xFFFF
,之后计算seed=0 xor v6_1 xor v6_2 xor .... xor v6_{v5}
并且还要满足:
1 | i = seed |
最后写脚本:
1 | #!/usr/bin/env python |
看运气,一般3min能跑出来。
知识点1:C语言的sprintf
。把格式化的数据写入某个字符串缓冲区。如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
1 |
|
知识点2:vscode中写C时,include路径报错,直接在c_cpp_properties.json
中添加路径即可。
知识点3:size_t是标准C库中定义的,在64位系统中为long long unsigned int,非64位系统中为long unsigned int。在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的,这样利用该类型可以增强程序的可移植性。定义在cstddef
或stddef.h
中。
知识点4:gcc与g++
知识点5:C语言的strcmp
。
strcmp(str1,str2),若str1=str2,则返回零;若str1
知识点6:pwn的基本使用
https://www.jianshu.com/p/355e4badab50
secret-string-400
0x00
下载下来就显示是gz后缀的压缩包,放入exeinfope.exe
中发现是tar
包,用7-Zip
解压,发现是js+html类型的。
仔细瞅了一眼mechine.js
,是js的虚拟机。(之前没遇到过,仔细瞅瞅
看了一下,里面有好多的opcode,首先load字节码,之后逐步运行字节码。在run()
函数上打console.log(command, command.args)
,随机输入字符串,发现输出为:
1 | Opcode11 [1,"Object"] |
结合每一个Opcode
的prototype
,对上述输出进行处理:
1 | obj.registers[1] = eval('new Object'); |
再继续转成js代码:
1 | obj = new Object(); |
直接上脚本:
1 | #!/usr/bin/env python |
嘿嘿。
突然想到一个好玩的,就是把code改一下,让他报错不输出"NOPE!"
,而是输出自己想要的字符串。
1 | #!/usr/bin/env python |
babymips
0x00
发现是32位的MIPS。
知识回顾:三大主流架构:mips,Arm,x86。
题不难,直接上wp:
1 | #!/usr/bin/env python |
知识点1:C语言中,int类型在32位与64位中都是4字节。
知识点2:arm和x86架构是小端序,mips架构是大端序。大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。但是数据存放的时候还是按正常的来。
EASYHOOK
0x00
发现是32位可执行PE。
打开之后,找到main
函数,首先分析下图所示的sub_401220()
。
sub_401220()
如下图所示:
在这里补充常见的win函数:
函数名 | 作用 |
---|---|
GetCurrentProcessId |
返回值是该进程的进程标识符。 |
OpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId) |
dwDesiredAccess 表示对进程的访问权限,bInheritHandle 表示新创建的进程是否继承句柄,dwProcessId 表示要打开进程的标识符,返回值为返回值是指定进程的打开句柄或者Null。 |
LoadLibraryA(lpLibFileName) |
lpLibFileName 表示模块名称(可以是dll也可以是exe),返回值为模块的句柄或者Null。 |
GetProcAddress(hModule,lpProcName) |
hModule 表示模块的句柄,lpProcName 表示函数或变量名称,返回值是此函数或变量的地址,或者Null。 |
CreateFileA(lpFileName,dwDesiredAccess,dwShareMode, lpSecurityAttributes,dwCreationDisposition,dwFlagsAndAttributes,hTemplateFile) |
lpFileName 表示要创建的文件名称,dwDesiredAccess 表示权限,dwShareMode 表示共享模式,lpSecurityAttributes 为指向一个结构的指针,dwCreationDisposition 不重要(没看懂),dwFlagsAndAttributes 表示文件属性,hTemplateFile 不重要,返回值为文件句柄。 |
WriteFile(hFile,lpBuffer,nNumberOfBytesToWrite,lpNumberOfBytesWritten,lpOverlapped ) |
hFile 表示要写入文件的句柄,lpBuffer 表示要写入的内容的指针,nNumberOfBytesToWrite 表示要写入的字节数,lpNumberOfBytesWritten 与lpOverlapped 不重要。 |
看上去这个函数是对内存做了一些操作的,返回值为Bool值。(这是因为sub_4010D0
的返回值为Bool值)。详细说一下sub_401220
的流程:
1 | 1. v2表示进程ID,hProcess表示再打开一个相同的进程,v0为kernel32.dll的句柄,lpAddress为kernel32.dll中WriteFile函数的地址。 |
再来看sub_4010D0
函数:
函数名 | 作用 |
---|---|
VirtualProtectEx(hProcess,lpAddress,dwSize,flNewProtect, lpflOldProtect) |
hProcess 表示要更改内存保护的进程句柄,lpAddress 表示要更改保护属性的地址指针,dwSize 表示更改访问的区域大小,flNewProtect 不重要,lpflOldProtect 接收此更改地址的先前访问保护属性,返回值非零(成功)。 |
WriteProcessMemory(hProcess, lpBaseAddress,lpBuffer,nSize,*lpNumberOfBytesWritten) |
hProcess 表示要写入的进程句柄,lpBaseAddress 表示写入到的地址,lpBuffer 表示要写入的数据,nSize 表示要写入的字节数,*lpNumberOfBytesWritten 不重要。 |
详细说一下sub_4010D0
的流程:就是在刚刚创建的hProcess
的lpAddress(WriteFile)
改为0040C9BC
。
接着看主函数:
1 | 1. 运行完sub_401220()之后,创建一个名为"Your_input"的文件句柄v4,并把buffer(输入值)写入到Your_input文件中,写入19个字符。 |
之后看一下sub_401240
函数:
具体流程:
1 | 1. v4="This_is_not_the_flag",a1为输入的字符串。 |
0x01
最终没看明白,动调发现main函数的14行(如下图)进入了sub_401080
函数(hook所在)。
突然灵光一现,原来sub_401220
函数是将WriteFile
hook 到了sub_401080
里了。
分析一下此函数:
可以看到,参数还是WriteFile
的参数。sub_401080
过程如下
1 | 1. 调用sub_401000,返回值给v5。 |
sub_401000
函数如下图所示:
卧槽,这个a2不就是nNumberOfBytesToWrite么,NumberOfBytesWritten == 1就是我们要找的正确的结果啊!!!!!妈的,这个出题人太狗了。
回过头来再看sub_401080
,做注释:
其实仔细想想,自己就是对hook不熟悉,一会儿总结一下。
1 | #!/usr/bin/env python |
这个题就是:出题人故意写了一个错误的函数流程,永远不可能成立。然后他把系统函数writeFile
给hook了,让他指向了很隐藏的一个函数,这个很隐藏的函数又能影响最终结果。妈的!
知识点1:hook。
1 | BOOL sub_401140() |
留言
- 文章链接: https://wd-2711.tech/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!