Post

k17CTF - qna

这是一个python打包成的ELF文件:

1
2
3
4
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  Nuitka_Main(argc, (char **)argv);
}

nuitka和pyinstaller等打包工具不同,它所做的工作更类似于“编译器”,因此不可能直接提取主代码。网上的nuitka extractor工具也只是提取内嵌的动态库

那么只能自己找核心逻辑了。

先进入Nuitka_Main中看看,里面是很长的启动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void __fastcall __noreturn Nuitka_Main(unsigned int argc, char **argv)
{
  PyThreadState *v2; // rbx
  PyObject *ImportLibBootstrapModule; // rax
  PyObject *SysModules; // rax
  unsigned int v5; // eax

  signal(11, nuitka_segfault_handler);
  original_argv0 = *argv;
  *argv = (char *)getBinaryFilenameHostEncoded(0);
  prepareStandaloneEnvironment();
  prepareFrozenModules();
  Py_DebugFlag = 0;
  Py_InspectFlag = 0;
  Py_InteractiveFlag = 0;
  Py_OptimizeFlag = 0;
  Py_DontWriteBytecodeFlag = 0;
  Py_NoUserSiteDirectory = 1;
  Py_IgnoreEnvironmentFlag = 0;
  Py_VerboseFlag = 0;
  Py_BytesWarningFlag = 0;
  Py_UTF8Mode = 0;
  Py_FrozenFlag = 1;
  Py_NoSiteFlag = 1;
  orig_argv = convertCommandLineParameters(argc, argv);
  Py_SetProgramName(*orig_argv);
  orig_argc = argc;
  setCommandLineParameters(argc, argv);
  Nuitka_Py_Initialize();
  v2 = (PyThreadState *)PyThreadState_Get();
  Py_NoSiteFlag = 1;
  PySys_SetArgv(argc, orig_argv);
  initBuiltinModule();
  createGlobalConstants(v2);
  createMainModuleConstants(v2);
  initBuiltinOriginalValues();
  initCompiledCellType();
  initCompiledGeneratorType();
  initCompiledFunctionType();
  initCompiledMethodType();
  initCompiledFrameType();
  initSlotCompare();
  initSlotIterNext();
  patchTypeComparison();
  patchTracebackDealloc();
  setInputOutputHandles(v2);
  ImportLibBootstrapModule = getImportLibBootstrapModule();
  if ( ImportLibBootstrapModule )
  {
    if ( ImportLibBootstrapModule->ob_refcnt > 0 )
    {
      setEarlyFrozenModulesFileAttribute(v2);
      PyImport_FrozenModules = old_frozen;
      setupMetaPathBasedLoader(v2);
      PyWarnings_Init();
      patchInspectModule(v2);
      undoEnvironmentVariable(v2, "PATH", old_env_path);
      undoEnvironmentVariable(v2, "PYTHONHOME", old_env_pythonhome);
      SysModules = Nuitka_GetSysModules();
      PyDict_DelItemString(SysModules, &module_name);
      DROP_ERROR_OCCURRED(v2);
      EXECUTE_MAIN_MODULE(v2, (const char *)&module_name, 0);
      checkModuleConstants___main__(v2);
      v5 = HANDLE_PROGRAM_EXIT(v2);
      Py_Exit(v5);
    }
    __assert_fail("Py_REFCNT(importlib_module) > 0", "static_src/MainProgram.c", 0x6C0u, "Nuitka_Main");
  }
  __assert_fail("(importlib_module) != NULL", "static_src/MainProgram.c", 0x6C0u, "Nuitka_Main");
}

其中的EXECUTE_MAIN_MODE更像一个普适性的模块执行器,似乎没有找到main module的直接入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void __fastcall EXECUTE_MAIN_MODULE(PyThreadState *tstate, char *module_name, bool is_package)
{
  const char *v5; // r13
  char *v6; // rax
  char *v7; // rsi
  int v8; // edi
  char buffer[1024]; // [rsp+0h] [rbp-438h] BYREF
  unsigned __int64 v10; // [rsp+408h] [rbp-30h]

  v10 = __readfsqword(0x28u);
  if ( is_package )
  {
    v5 = module_name;
    while ( 1 )
    {
      v6 = strchr(v5, 46);
      if ( !v6 )
        break;
      v5 = v6 + 1;
      memset(buffer, 0, sizeof(buffer));
      __memcpy_chk(buffer, module_name, v6 - module_name, 1024LL);
      v7 = buffer;
      v8 = (int)tstate;
      IMPORT_EMBEDDED_MODULE(tstate, buffer);
      if ( tstate->current_exception )
        goto LABEL_7;
    }
  }
  v7 = module_name;
  v8 = (int)tstate;
  IMPORT_EMBEDDED_MODULE(tstate, module_name);
LABEL_7:
  if ( v10 != __readfsqword(0x28u) )
    setCommandLineParameters(v8, (char **)v7);
}

定位

通过函数名过滤,有限搜索关键词main

因为执行后需要一个输入,在gdb中按下Ctrl-C记录rip,然后在ida中下断点,一步步step until return

单步执行,看什么时候出现输出提示词(虽然方法简单,但很多时候非常有效)

最后找到modulecode__main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
void __fastcall modulecode___main__(
        PyThreadState *tstate,
        PyObject *module,
        const Nuitka_MetaPathBasedLoaderEntry *loader_entry)
{
 ......
  PyTracebackObject *v85; // rax
  Nuitka_ExceptionPreservationItem exception_state; // [rsp+0h] [rbp-48h] BYREF
  unsigned __int64 v87; // [rsp+8h] [rbp-40h]
 ......
  UPDATE_STRING_DICT1(moduledict___main__, (PyUnicodeObject *)mod_consts[13], v12);
  v14 = LIST_COPY(tstate, mod_consts[14]);
  UPDATE_STRING_DICT1(moduledict___main__, (PyUnicodeObject *)mod_consts[15], v14);
  v15 = DICT_COPY(tstate, mod_consts[16]);
  v16 = MAKE_FUNCTION___main_____function__1_a(tstate, v15);
  UPDATE_STRING_DICT1(moduledict___main__, (PyUnicodeObject *)mod_consts[3], v16);
  v17 = DICT_COPY(tstate, mod_consts[16]);
  v18 = _b::MAKE_FUNCTION___main__$$$function(tstate, v17);
  UPDATE_STRING_DICT1(moduledict___main__, (PyUnicodeObject *)mod_consts[4], v18);
  v19 = DICT_COPY(tstate, mod_consts[17]);
  v20 = MAKE_FUNCTION___main_____function__3_c(tstate, v19);
  UPDATE_STRING_DICT1(moduledict___main__, (PyUnicodeObject *)mod_consts[18], v20);
  v21 = LOOKUP_BUILTIN(mod_consts[19]);
......
  UPDATE_STRING_DICT0(moduledict___main__, (PyUnicodeObject *)mod_consts[22], mod_consts[21]);
  v26 = module_var_accessor___main___og(tstate);
  if ( !v26 )
    __assert_fail("!(tmp_assign_source_11 == NULL)", "module.__main__.c", 0x8D5u, "modulecode___main__");
  UPDATE_STRING_DICT0(moduledict___main__, (PyUnicodeObject *)mod_consts[23], v26);
  v27 = MAKE_ITERATOR_INFALLIBLE(mod_consts[24]);
  if ( !v27 )
    __assert_fail("!(tmp_assign_source_12 == NULL)", "module.__main__.c", 0x8DDu, "modulecode___main__");
  for ( i = 0LL; ; i = index )
  {
    if ( v27->ob_refcnt <= 0 )
      __assert_fail("Py_REFCNT(tmp_for_loop_1__for_iterator) > 0", "module.__main__.c", 0x8E6u, "modulecode___main__");
    index = ITERATOR_NEXT_ITERATOR(v27);
    ......
        if ( v38 )
        {
          ......
          }
          if ( v40 )
          {
            v7->m_frame.f_lineno = 22;
            v42 = CALL_FUNCTION_WITH_SINGLE_ARG(tstate, v34, v40);
            ......
            if ( v42 )
            {
              .......
                v51 = RICH_COMPARE_EQ_NBOOL_BYTES_OBJECT(v47, v49);
                if ( (int)v47->ob_refcnt >= 0 )
                {
                  v52 = v47->ob_refcnt - 1;
                  v47->ob_refcnt = v52;
                  if ( !v52 )
                    Py_Dealloc(v47);
                }
                if ( (int)v50->ob_refcnt >= 0 )
                {
                  v53 = v50->ob_refcnt - 1;
                  v50->ob_refcnt = v53;
                  if ( !v53 )
                    Py_Dealloc(v50);
                }
              ......
}

这里有很多全大写的函数名,但是并不全是有利于分析的,比如大量充斥的 UPDATE_STRING_DICT1就没什么用。所以下一步我们需要筛选有用的节点分析。比如我们看见有MAKE_ITERATOR_INFALLIBLE和 ITERATOR_NEXT_ITERATOR结构,对应一个for xxx in xxx循环,更重要的是我们发现了下面的RICH_COMPARE_EQ_NBOOL_BYTES_OBJECT,只不过参数都是pyobject形式

dump pyobject

查阅资料后了解到gdb中能够用一些python原生函数,比如PyObject_Repr

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) call PyObject_Repr($rdi)
'PyObject_Repr' has unknown return type; cast the call to its declared return type
(gdb) (PyObject*)PyObject_Repr($rdi)
Undefined command: "".  Try "help".
(gdb) call (PyObject*)PyObject_Repr($rdi)
$1 = (PyObject *) 0x7ffff7393150
(gdb) set $tmp = (PyObject*)PyObject_Repr($rdi)
(gdb) print $tmp
$2 = (PyObject *) 0x7ffff7393120
(gdb) call (char*)PyUnicode_AsUTF8($tmp)
$3 = 0x7ffff7393148 "b'abc'"

但是直接使用后提示需要转换类型,转为PyObject* 然而repr方法返回的str也是一个PyObject,需要进一步转化为字符,而且也要加上(char*)强转。我们发现rdi就是我们的输入’abc’,这是个好消息,意味着没有复杂的输入加密

1
2
3
4
5
6
7
b *0x00005555556862B8
r

define dump_pyobj
	set $tmp = (PyObject*)PyObject_Repr($arg0)
	call (char*)PyUnicode_AsUTF8($tmp)
end
1
2
3
4
(gdb) dump_pyobj $rdi
$1 = 0x7ffff7393178 "b'abc'"
(gdb) dump_pyobj $rsi
$2 = 0x7ffff73c48d8 "b'lo0k_At_m3'"

然后直接输入口令即可

This post is licensed under CC BY 4.0 by the author.