吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 13407|回复: 14
收起左侧

[CTF] 2016-12 HXBCTF一道python打包的逆向分析

[复制链接]
6767 发表于 2017-5-31 22:20
本帖最后由 6767 于 2017-7-15 01:11 编辑

这个是2016年12月HXBCTF的线上赛的逆向题目
本题为2016年下半年湖湘杯一道逆向。 从本思路处理,需要一定的python虚拟机知识,和python打包逆向的知识。 本题的所有命令都在git-shell下执行,兼容类unix。
预处理
<login.exe>文件可以从
这里下载 https://dn.jarvisoj.com/challengefiles/login.exe.0e043cc84e9273f1e34b6b27330c8e5a
看到有同学说看不懂,我就讲的更细致一点。
首先拿到文件一定要分析,是什么东西写的,一般直接丢IDA看看关键函数名 ,或者看字符串有什么特性,
这个文件里用strings login.exe|less 看有这么些东西:
[Plain Text] 纯文本查看 复制代码
...more
bapi-ms-win-crt-utility-l1-1-0.dll
bmain.exe.manifest
bpyexpat.cp35-win32.pyd
bpython35.dll
bselect.cp35-win32.pyd
bunicodedata.cp35-win32.pyd
opyi-windows-manifest-filename main.exe.manifest
xbase_library.zip
zout00-PYZ.pyz
#python35.dll

由于以前用过python做二进制分发,这个马上就可以认出来是py2exe一类的东西,至于为什么是只能说多尝试,多积累经验,python目前还是比较小众的。
使用pyinstaller extractor https://sourceforge.net/projects/pyinstallerextractor/ 提取器,会有很多文件,我们关注这个文件:main <.pyc>。


这个主文件是一个pyObject转储的字符串文件,pyObj相关的资料大家可以看python的文档,也可以看看源代码剖析。 将其转换为正常的pycfile(添加一个pyc文件头,4字节MAGIC_NUMBER,4bytes Timestamp,具体做法是),用uncompyle6 将其反向到源代码:
[Python] 纯文本查看 复制代码
# Embedded file name: main.py
# Compiled at: 2016-08-16 19:36:25
# Size of source mod 2**32: 89 bytes
import hxbctf
print(hxbctf.Login(0, input('UserName: ')))
# okay decompiling tryrepair.main.pyc

本身的Bug
点击运行这个exe,会提示无法运行,放进虚拟机里也是一个德行,当时做题的时候真是x了狗了。

在网上搜索了一阵子,发现这个使用Onefile参数压缩的pyinstaller bootloader有一个奇怪的bug ,主要是由vc运行库的压缩引发的。
当vcruntime被upx压缩时在64位Windows下无法运行,这可能会导致一些错误,比如一直弹无法找到dll。
知道错误就好办了,参考pyinstaller打包文件的格式是n个文件直接二进制append的类型,所以只要修改索引表Table of Contents就好了。

现在我们使用010editor来更改login.exe文件中的字符串, 执行strings login.exe |grep dll字符串搜索,找到字符串bVCRUNTIME140.dll的位置,
并将其重命名为bccruntime140.dll或者任何其他字符串以绕过此错误。
记得在系统的搜索路径下放置一个 VCRUNTIME140.dll文件。


仔细分析
首先file *所有文件,看看有什么,很明显的一条是upx-compressed.

用ls *.dll |xargs upx -d解压缩这些文件。对pyd 文件也执行一样的操作。
如果我们在所有二进制文件被upx解压缩之后使用strings hxbctf进行搜索,会发现“python.dll”报告了一个字符串引用hxbctf。这里的技巧是充分利用shell提供的工具快速在目录里搜索,否则一个文件一个文件找就累死人了。
这里已经很清楚了,下一步逆核心dll。


Crack

一般逆向就几步走,找关键词,定位代码,分析逻辑。
如果使用 python 前端shell python.exe来运行核心DLL,会发现hxbctf已经被编译成这个python core文件中的内置模块。
拉进IDA,Shift+F12看字符串。 幸运的是,字符串“Congratulations”是.rodata段中的一个纯文本。 按下X反查使用点,很快就能找到他的使用位置,只有一处:
在dll,段位置.text:1E183D20,按F5 直接看C代码,这是主要功能:
[C] 纯文本查看 复制代码
//已修改注释和部分变量名
int __cdecl coreCTFfunction(int a1, void *pyobj)
{
  int result; // eax@1
  int i; // edx@5
  int v4; // eax@6
  int v5; // ecx@6
  char *userName; // [sp+4h] [bp-40Ch]@2
  int paraNumber1; // [sp+8h] [bp-408h]@1
  char Dst; // [sp+Ch] [bp-404h]@8
  char inputStrDstcache[512]; // [sp+20Ch] [bp-204h]@1

  memset(inputStrDstcache, 0, 0x200u);
  result = PyArg_ParseTuple(pyobj, "is", (unsigned int)&#182;Number1);// http://wiki.jikexueyuan.com/proj ... s/python_c_api.html
  if ( result )
  {
    sub_1E04E550((int)"Password: ", (char)userName);
    getInputScanf("%s", inputStrDstcache);
    if ( paraNumber1 == 0x1352 && strlen(inputStrDstcache) == 16 && !strncmp("HXB_Admin", userName, 9u) )
    {
      i = 0;
      while ( 1 )
      {
        v4 = inputStrDstcache;
        v5 = i ^ v4 ^ secrectKey;
        if ( i != (v4 ^ secrectKey) )
          break;
        if ( ++i >= 16 )
        {
          memset(&Dst, v5, 0x200u);
          printfFormator(&Dst, "Congratulations!\nflag{%s}.", inputStrDstcache);
          return Py_BuildValue("s", (unsigned int)&Dst);
        }
      }
    }
    result = Py_BuildValue("s", (unsigned int)"Access Denied!");
  }
  return result;
}


找出secKey中的字符串:.rdata:1E253040 secrectKey

这里是算法:Xor与每个输入的char和他的索引;
[Python] 纯文本查看 复制代码
sec='Pxvk4kYcIVlJSeO?'
res=[]
for i in range(len(sec)):
    n1=ord(sec)
    res.append(chr(i^n1))
print(''.join(res))
## -- End pasted text --
#got >>>Pyth0n_dA_fA_hA0

在一个python shell里执行:
[Python] 纯文本查看 复制代码
import hxbctf
>>> hxbctf.Login(0x1352,'HXB_Admin')
Password: Pyth0n_dA_fA_hA0
'Congratulations!\nflag{Pyth0n_dA_fA_hA0}.'
>>> 

我们已经得到了我们想要的了

总结
题目核心其实非常简单,但是在64bit下的bug平添巨大的麻烦。
就虚拟机本身而言,题目隐藏得还是不错的,如果动态解密字符串的话。
相似的题目还有2017年5月出的更加暴力的西安的SSCTF的bpython。

参考
强行动态调试解出,这个很暴力:http://bbs.pediy.com/thread-215002.htm
python 源代码剖析这本书



关于pyc文件头的分析,以下对应于py3.5 的,py2和py3.3还不一样的。
[C] 纯文本查看 复制代码

//In Python 3.5.2 source code tree, file:[pythonrun.c]
//Line:  981

static PyObject *
run_pyc_file(FILE *fp, const char *filename, PyObject *globals,
             PyObject *locals, PyCompilerFlags *flags)
{
    PyCodeObject *co;
    PyObject *v;
    long magic;
    long PyImport_GetMagicNumber(void);           //00000000: 160d 0d0a b9fa b257 5900 0000 e300 0000

    magic = PyMarshal_ReadLongFromFile(fp);    //                  160d 0d0a
    if (magic != PyImport_GetMagicNumber()) {
        if (!PyErr_Occurred())
            PyErr_SetString(PyExc_RuntimeError,
                       "Bad magic number in .pyc file");
        return NULL;
    }
    /* Skip mtime and size */
    (void) PyMarshal_ReadLongFromFile(fp);      //                                   b9fa b257;time stamp,1471347385,'Tue Aug 16 19:36:25 2016'
    (void) PyMarshal_ReadLongFromFile(fp);      //                                                   5900 0000 ;source code <.py> size,here is 59 bytes


//these bytes are all dropped  when reading in.
    if (PyErr_Occurred())
        return NULL;

    v = PyMarshal_ReadLastObjectFromFile(fp);  //                                                                    e300 0000......to the end of pyc file.
                                                                //pyObject structure
    if (v == NULL || !PyCode_Check(v)) {
        Py_XDECREF(v);
        PyErr_SetString(PyExc_RuntimeError,
                   "Bad code object in .pyc file");
        return NULL;
    }
    co = (PyCodeObject *)v;
    v = PyEval_EvalCode((PyObject*)co, globals, locals);
    if (v && flags)
        flags->cf_flags |= (co->co_flags & PyCF_MASK);
    Py_DECREF(co);
    return v;
}


免费评分

参与人数 9威望 +1 吾爱币 +18 热心值 +9 收起 理由
missdiog + 1 + 1 谢谢@Thanks!
狗年快乐 + 1 + 1 谢谢@Thanks!
Azure_atk + 1 + 1 谢谢@Thanks!
Git_man + 1 + 1 已答复!
zsyhn + 1 + 1 谢谢@Thanks!
夏末moent + 1 + 1 鼓励转贴优秀软件安全工具和文档!
jaffa + 1 + 1 谢谢@Thanks!
littlebit + 1 + 1 用心讨论,共获提升!
Hmily + 1 + 10 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 6767 发表于 2017-7-15 01:16
canic 发表于 2017-7-14 23:13
将其转换为正常的pycfile(添加一个pyc文件头,4字节MAGIC_NUMBER,4bytes Timestamp),用uncompyle6 将其 ...

对py3.5 ,直接在main文件前面加这  160d 0d0a b9fa b257 5900 0000  12bytes 的16进制就好了。main的内容和头关系不是特别大,除了前4个magic_number,其他随便改。具体可以看我的更新的内容。
Quincy379 发表于 2018-2-7 07:11
大神,这句话:这个主文件是一个pyObject转储的字符串文件,pyObj相关的资料大家可以看python的文档,也可以看看源代码剖析。 将其转换为正常的pycfile(添加一个pyc文件头,4字节MAGIC_NUMBER,4bytes Timestamp,具体做法是),用uncompyle6 将其反向到源代码:
怎么找python36的MAGIC_NUMBER,你只写了个具体做法然后就没有了,谢谢赐教!!!
mayl8822 发表于 2017-6-1 11:26
myouter 发表于 2017-6-1 13:03
思路清奇,666
anfu1989 发表于 2017-6-1 15:50
感觉很NB的样子,只是我看不懂!
canic 发表于 2017-7-14 23:13
将其转换为正常的pycfile(添加一个pyc文件头,4字节MAGIC_NUMBER,4bytes Timestamp),用uncompyle6 将其反向到源代码
——这个添加pyc头的步骤,能否详细说一下,菜鸟表示做不出来。
铁轮上的风景 发表于 2017-7-28 13:06
学习了,谢谢
heverst 发表于 2017-8-29 19:16

感谢分享, 学习了了
Quincy379 发表于 2018-2-6 21:22
你好,请教一下,那个main文件是如何转成py文件的???谢谢!
Quincy379 发表于 2018-2-6 21:24
Quincy379 发表于 2018-2-6 21:22
你好,请教一下,那个main文件是如何转成py文件的???谢谢!

看到了,谢谢啦!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-17 00:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表