拿到题目先查壳和文件类型

1790074685084

发现是个elf文件,没用壳。直接用IDA打开。

在ODA里面找到main函数,发现不能直接转换为伪代码。

1790074925023

直接看汇编指令。在0000000000400BB1处IDA解析的汇编有问题,依据loc_400BB1+1,将400BB1处第一个字节nop,继续往下看 ,看到一个奇怪的调用( call loc_400BBF),

1790075227617

call 400BBF 执行时会将下一条指令地址 400BBC 压入栈中,随后 pop rax 将该返回地址取出,再通过 add rax, 1 将其修改为 400BBD,最终通过 retn 将执行流转移到 400BBD。

1790075692509

已知从400BBD开始,所以将400BBD开始的内容转换为code,400BBC直接nop.

1790075789466

这里直接跟进loc_400BD1,

1790075831228

这里看到三个函数sub_4008B9,loc_400BEF,loc_400CAC,通过跟进函数可以发现sub_4008B9,loc_400BEF这两个函数都没用问题,但是loc_400CAC处有和之前相似的花指令。

1790076086373

直接用之前的方法,把

400CB5-400CBA 和400CBD-400CCE

全部patch 为nop。

1790076315193

出现

jmp     short loc_400CCF

继续跟进这个跳转。

1790076450282

这里可以看到 在 0x400D04 又发现一个非常经典的花指令,还是直接将第一个字节 patch 为 nop。

1790076652013

这样看起来就像一个正常的函数结尾了。

现在返回400B54(main的开头),先 p 一下重新建立函数,之后就可以正常 F5 反编译了 。

1790077079570

main函数的主要流程:

首先调用sub_4008B9;接着接收输入的19个字符;随后调用mprotect,修改sub_400B54 & 0xFFFFC000为首页地址区域的权限,将其改为 r | w | x ;紧跟着是改变400A69处的前10个字节;最终是调用sub_400A69检查flag是否正确。

程序开始
   ↓
调用 sub_4008B9
   ↓
接收用户输入 19 个字符
   ↓
调用 mprotect
   ↓
将代码所在内存页权限修改为
R | W | X
   ↓
使用输入的前 5 个字符
循环 XOR 修改 sub_400A69
的前 10 个字节
   ↓
恢复出运行时真正执行的代码
   ↓
调用 sub_400A69
   ↓
进行 Flag 校验
   ↓
┌───────────────┐
│   校验是否正确 │
└───────┬───────┘
        ↓
   正确        错误
    ↓            ↓
输出 Cong!     返回失败

由于题目是:N1CTF2020 - oflo,所以输入的前5个字符是n1ctf,

使用IDA中的python,修改400A69。

import idc

s = 'n1ctf'

for i in range(0x400A69, 0x400A69 + 10):
    c = idc.get_db_byte(i)
    c ^= ord(s[(i - 0x400A69) % 5])
    idc.patch_byte(i, c)

改完发现400A69还是不能进行反汇编,

1790078061795

在400AC4处发现问题,前面是

jz      short loc_400AC9
jnz     short loc_400AC9

说明不论条件如何,最终都应该跳转到400AC9处。

那么

jmp     near ptr 801AC9h

就是没用的指令,直接nop

继续往下看发现1790078474309

400B0E处的花指令和之前遇到的一样,按照之前的方法nop。

处理完就能看到400A69的反汇编1790078686105

根据传入的参数,a2是V7+5,即去掉开头的输入。现在还有buf不知道,在main函数中寻找哪里出现过buf。

发现

 (unsigned int)sub_4008B9(buf, a2, buf) == -1

这里出现buf。由于内部太复制,直接动态调试,查看buf的值。

我在400CE7处下断点,查看rdi的内容。1790080533775

1790080570937

这里说明了sub_A69函数调用的位置和buf作为第一个参数传入函数。在LINUX中第一个参数由rdi保存。

1790080470356在这里能看到buf的前14个字符是 “Linux version "

因此我们很容易便能得到 flag 内容

s = "5-\x11\x1AI}\x11\x14+;>=<_"
b = "Linux version "
ans = ""

for i in range(14):
    ans += chr(ord(s[i]) ^ (ord(b[i]) + 2))
print(ans)

1790080887242

结果是 {Fam3_is_NULL}

加上前面的5个字符,完整的flag就是n1ctf{Fam3_is_NULL}。