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.