用Pin“调试”子进程-N1CTF oflo
N1CTF Oslo WP
题目知识点&需要工具
指令机器码格式、x86架构执行指令的方式和LinuxFork子进程相关知识
Pin动态二进制插桩工具&qira&IDA
程序基本信息
file指令查看 Linux下64位ELF文件
初次运行程序观察用户实图下程序行为

观察到程序输出了我们本机的一些信息,应该是程序中查看了/proc/version信息,然后看到控制台上多了一排Segmentation fault做pwn的应该对这个比较熟悉,一般就是程序运行时候通过访问空指针、野指针或越界引发。
暂时就这么多信息,下一步加载到IDA里查看
静态分析

找到到程序入口,可以看到IDA并没有很好的将main识别为函数,看到Loc_400BB1处有一条奇怪的jmp指令,由x86汇编知识可知short jmp指令的长度应该是2个字节,所以这个跳转是将RIP设置为自身指令+1的地方
由于IDA只是静态的反汇编,并且对于这种两条指令或多条指令复用字节码的混淆方式基本上没办法识别,所以我们就需要自己进行操作,帮助IDA去识别这些指令。
对这条jmp指令按D将其转换为data,如图

接着对着400BB2按C转换为代码

可以看到接下来的主要逻辑为call loc_400BBF步入这个函数进行查看。可以观察到它取出了call指令压入栈中的返回地址,后加一又放回原位,所以retn后的RIP将被改写为400BBD
Call 指令 等于 push rip+len(call) AND jmp 即把下一条指令压入后跳转,此处的call指令地址为400BBC,加一后为400BBD
继续上述的先把400BBD解析为data再解析为code,如下

很明显一个jmp跳转
所以此处的混淆只是为了让IDA无法识别,没有做其他操作,所以可以把上面这些混淆的代码全部用nop指令代替,patch二进制程序把此处的机器码全部改写为90
继续看下面的逻辑,可以看到一个读取输入,然后进行了一个小操作,接着向下,可以看到400CBD还存在这样的混淆,进行同样的操作后继续跟踪,将这样的混淆全部清除
接下来直接按P创建函数,无脑F5,可得到反编译代码

进行简单分析得程序逻辑。
先是fork后,父进程执行cat /proc/version 和之前猜测一致;子进程接着运行,读取输入的前五个字符和疑似核心函数的前10个机器码进行异或操作,可以知道是在恢复程序流,也可以讲作是在进行脱壳操作,也和之前猜测一样。
查看check函数的汇编代码,可以看到前几条指令比较抽象,反编译后的结果同样抽象。由上面的逻辑,我们现在需要做的就是手动patch程序进行‘脱壳’。那么我们知道有一个5个字节的key,然后和代码中的前五个和后五个字节进行异或后即可复原。然后前五个字节码,肯定是
push rbp
mov rbp,rsp
sub rsp,xxx
Push\Mov的机器码就是55 48 89 E5 并且观察其他有类似操作的函数,sub当操作R*类寄存器时,第一个机器码是48。接着就可以逆向求出key(n1ctf)后求出后5个机器码,并且key为前五位输入。
继续解混淆,接着F5

a4是从main函数中fork里传过来的,我这边采用取巧的方式,直接借助Pin动态读取此处的数据。
Pin脚本见附件
Pin脚本大意
当程序运行到0x400B08时候读出rdx,rdi的数据,每执行到此处,rdi会存放*(a4+v5)的值,下文称做key,然后由于程序逻辑原因,当顺序检测,有一位错误则会exit,借助这个特性,逐位获取key,最后爆破求出flag。或者利用正确输入和错误输入执行的指令数不同,可以通过Pin记录指令数爆破。
/*
* Copyright 2002-2020 Intel Corporation.
*
* This software is provided to you as Sample Source Code as defined in the accompanying
* End User License Agreement for the Intel(R) Software Development Products ("Agreement")
* section 1.L.
*
* This software and the related documents are provided as is, with no express or implied
* warranties, other than those that are expressly stated in the License.
*/
#include <stdio.h>
#include "pin.H"
FILE * trace;
int counter = 0;
// This function is called before every instruction is executed
// and prints the IP
VOID printip(VOID *ip,ADDRINT *rax,ADDRINT *rdx,ADDRINT *rcx,ADDRINT *rbx,ADDRINT *rdi) {
if((long int)ip == 0x400B08){
fprintf(trace, "%d:%p xor %p\t\n", counter, rdx, rcx);
counter++;
}
//fprintf(trace, "%p\n", ip); }
}
// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID *v)
{
// Insert a call to printip before every instruction, and pass it the IP
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip,
IARG_INST_PTR,
IARG_REG_VALUE,REG_RAX,
IARG_REG_VALUE,REG_RDX,
IARG_REG_VALUE,REG_RCX,
IARG_REG_VALUE,REG_RBX,
IARG_REG_VALUE,REG_RDI,
IARG_END);
}
// This function is called when the application exits
VOID Fini(INT32 code, VOID *v)
{
fprintf(trace, "#eof\n");
fclose(trace);
}
/* ===================================================================== */
/* Print Help Message */
/* ===================================================================== */
INT32 Usage()
{
PIN_ERROR("This Pintool prints the IPs of every instruction executed\n"
+ KNOB_BASE::StringKnobSummary() + "\n");
return -1;
}
/* ===================================================================== */
/* Main */
/* ===================================================================== */
int main(int argc, char * argv[])
{
trace = fopen("itrace.out", "w");
// Initialize pin
if (PIN_Init(argc, argv)) return Usage();
// Register Instruction to be called to instrument instructions
INS_AddInstrumentFunction(Instruction, 0);
// Register Fini to be called when the application exits
PIN_AddFiniFunction(Fini, 0);
// Start the program, never returns
PIN_StartProgram();
return 0;
}

浙公网安备 33010602011771号