Rev①
一道简单的Reverse题,由蓝鲸安全平台提供
题目:入门语言,地址:https://pan.baidu.com/s/1NfDsl9B7LoUeAwI5m66Hjg
知识点:IDA分析,NOP指令,gdb调试
目录
背景知识
GDB概述
- GDB是linux中的动静态分析调试工具,功能非常强大,不但可以静态分析程序代码;如果生成程序的时候带有调试信息,还能够看到相当于反汇编的源代码。
- 同时GDB还是linux程序动态调试的绝佳工具。虽然是命令行程序相对比windows中的程序没有那么方便,但是使用起来非常灵活。我们可以下断点或者逐句调试。也可以设置寄存器或者汇编指令等。
- GDB作为命令行的调试工具,它的指令就比较重要了:首先启动gdb调试某个程序使用gdb \
启动。
GDB常用命令
运行
- run(简写r):其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令;
- continue(简写c):继续执行,到下一个断点处(或运行结束)
- next(简写n):单步跟踪程序,当遇到函数调用,也不进入函数体;此命令同step的主要区别是,step遇到用户自定义的函数,将步入到函数中去运行,而next则直接调用函数,不会进入到函数体内。
- step(简写s):单步调试如果有函数调用,则进入函数;与命令n不同。
- until:当在调试时厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
- until+行号:运行至某行,不仅仅用来跳出循环。
- finish:运行程序,直到当函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
- call函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(33)
- quit(简写q):退出qdb
设置断点
- break n(简写b n):在第n行处设置断点
- b fn1 if a>b :条件断点设置
- break func :在函数func()的入口处设置断点,如:break main
- delete 断点号n:删除第n个断点
- disable断点号n:暂停第n个断点
- enable断点号n:开启第n个断点
- clear行号n:清除第n行的断点
- info b:显示当前程序的断点设置情况
- delete breakpoints:清除所有断点
查看源代码
- list 行号:将显示当前文件以“行号”为中心的前后10行代码
- list函数名:将显示“函数名”所在函数的源代码
打印表达式
- print表达式(简写p):其中“表达式”可以是任何当前正在被测试程序的有效表达式,假如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
- print a:将显示整数a的值
- print name:将显示字符串name的值
- print gdb_test(22):将以整数22作为参数调用gdb_test()函数
- print gdb_test(a):将以变量a作为参数调用gdb_test()函数
- display 表达式:在单步调试非常有用,使用该命令设置一个表达式后,他将在每次单步进行指令后,紧接着输出被设置的表达式及其值。
- watch 表达式:设置一个监视点,一旦被监视的“表达式“的值改变,gdb将强行终止正在被调试的程序。
- whatis:查询变量或函数
- info function:查询函数
- info locals:显示当前堆栈页的所有变量
查询运行信息
- where/bt:当前运行的堆栈列表
- bt backtrace:显示当前调用堆栈
- up/down:改变堆栈显示的深度
- set args参数:指定运行时的参数
- show args:查看设置好的参数
- info program:来查看程序的是否在运行,进程号,被暂停的原因
分割窗口
- layout:用于分割窗口,可以一边查看代码,一边测试
- layout src:显示源代码窗口
- layout asm:显示反汇编窗口
- layout regs:显示源代码/反汇编和CPU寄存器窗口
- layout split:显示源代码和反汇编的窗口
- Ctrl+L:刷新窗口
题目解析
拿到程序,首先使用file命令,分析程序结构,该程序是64位的ELF可执行程序,尝试执行,逻辑是输入密码后进行检查,经典的验证式CTF题目:

接下来使用64位的IDA分析一下程序,找到main函数:

定位到关键函数sub_4006FD,发现是个算法校验,可以直接写脚本解题

脚本如下:密码 Code_Talkers
1 | import numpy as np |
另一种方法通过使用gdb尝试动态调试进行解题:
启动Gdb启动程序,执行start命令

程序阻塞了,我们用ctrl+z终止执行,看到停止的命令地址为:0x00000000004007e4

返回IDA查看到底什么地方阻止程序的运行,在ida中使用快捷键g,输入刚才的内存地址

发现是因为跳转到了一个子程序中loc_4007e4中。F5反汇编,看到了while 1的循环,原来根源在此

返回到gdb中,我们使用两个0x90(nop)将跳转终止
命令:set *(byte)*0x4007e4=0x90 set *(byte)*0x4007e5=0x90
接着程序可以重新开始运行了,我们需要重新检查程序检验密码的地方,找到寄存器存放我们输入的值,并且离最后判断处最近一处设为断点。

找到最终判断在40078B的位置,而检验是通过eax和edx的差是否为1进行判断的,所以讲断点下到eax赋值的位置。
使用命令b *0x400784

紧接着利用c执行到断点(两次执行,碰到主函数会有中断)

我们输入密码后运行到判断断点:

使用print(p) \$edx和print(p) \$eax检查两个寄存器的值

可以看到eax为我们自己输入A的ascii码,那么对比的edx应该就是需要的密码对照值了,为了让程序继续,使用set命令讲eax设置为比edx小1的数(最后使用十进制)

最后依次循环,直到输出nice的判断结果为止,收集到所有的edx的值,发现在可打印字符中。对应输出:

总结:
这道题属于逆向中最简单的一种类型,这题主要是练习一下gdb的使用方法,总结了gdb常用命令,以后逐渐熟悉gdb调试器。