吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4033|回复: 0
收起左侧

[CTF] Bamboofox-CTF-Move or not详解

[复制链接]
Spwpun 发表于 2020-1-18 16:22
本帖最后由 Spwpun 于 2020-1-18 16:25 编辑

[TOC]

1.前言

一道bamboofoxctf上的中等难度逆向题。
主要想讲讲通过这道题学到的东西,看完这篇文章,你可以:

  • 一般逆向题的静态分析技术;
  • Windows上IDA结合Linux虚拟机远程动态调试技术;
  • Linux上初级expect编程技术
  • ltrace动态调试技术(更适合这道题)

题目描述:

des

2.静态分析

题目给了一个文件pro,使用file命令查看文件格式:

spwpun@ubuntu:~/Documents/20200102$ file pro
pro: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=7bfce31b622a4e5bd9db43154888a3e1891ccac9, stripped
spwpun@ubuntu:~/Documents/20200102$ 

是一个ELF64位文件,在Linux虚拟机下执行该文件,首先需要输入password,随便输入之后验证错误就结束了:

spwpun@ubuntu:~/Documents/20200102$ ./pro
First give me your password: 2312
You don't know static analysis !
spwpun@ubuntu:~/Documents/20200102$ 

提示需要静态分析,使用IDA64位打开,程序没有混淆,在反编译后的main函数中很容易就能看清楚程序的逻辑:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+8h] [rbp-38h]
  int i; // [rsp+Ch] [rbp-34h]
  char s2; // [rsp+10h] [rbp-30h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  i = 0;
  printf("First give me your password: ", a2, a3);
  __isoc99_scanf("%d", &v4);
  if ( v4 != 98416 )
  {
    puts("You don't know static analysis !");
    exit(0);
  }
  printf("Second give me your key: ");
  __isoc99_scanf("%d", &v4);
  v4 -= 49;
  for ( i = 0; i <= 11; ++i )
    *((_BYTE *)&loc_201020 + i) += v4;
  ((void (__fastcall *)(char *))loc_201020)(s1);
  printf("Then Verify your flag: ");
  __isoc99_scanf("%s", &s2);
  if ( !strcmp(s1, &s2) )
    puts("You are right. Congratulations !!");
  else
    puts("You don't know dynamic analysis !");
  return 0LL;
}

总的来说,程序的逻辑如下:

  1. 首先获取用户输入,验证password
  2. 然后继续获取用户输入,验证key
  3. key减去49得到新的key
  4. 修改loc_201020处的前11个字节的数据,在原始的数据上加上新的key的值
  5. 然后把loc_201020当做函数来执行,参数为s1,应该是修改s1的内容
  6. 再次获取用户输入,验证flag值
  7. 最后比较输入的flag和s1,相同则验证成功

从上面的反编译伪代码中可以很容易知道password的值为98416,  但是key的值却不知道。貌似key是为了解码出正确的函数,然后利用该函数再解码真正的flag:s1,下面我使用动态调试来验证一下。

3.动态调试
gdb

首先在IDA中查看main函数的地址,下图中为0x00000000000007FA

image-20200102153118458

看上去这个地址是有点奇怪,暂时不管,先用gdb试试。

使用gdb启动该程序,设置断点,然后执行:

spwpun@ubuntu:~/Documents/20200102$ gdb ./pro
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./pro...(no debugging symbols found)...done.
gdb-peda$ b *0x7fa
Breakpoint 1 at 0x7fa
gdb-peda$ r
Starting program: /home/spwpun/Documents/20200102/pro 
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x7fa

gdb-peda$ 

上面的代码中提示不能设置断点,因为内存地址不可用。原来是地址随机化保护,以为是逆向题就没在意这个,使用checksec命令查看:

gdb-peda$ checksec pro
CANARY    : ENABLED
FORTIFY   : disabled
NX        : disabled
PIE       : ENABLED
RELRO     : FULL
gdb-peda$ 

果然开启了PIE。不设断点,直接执行,输入password和key之后,程序会报段错误:

gdb-peda$ r
Starting program: /home/spwpun/Documents/20200102/pro 
First give me your password: 98416
Second give me your key: 31

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x70 ('p')
RDX: 0x555555755020 --> 0x6deeb47035141c6d 
RSI: 0x1 
RDI: 0x555555755100 ("iAxU.|&30\f) (Heh2G:bdyRF;\nOYn=l%")
RBP: 0x7fffffffdd50 --> 0x555555554960 (push   r15)
RSP: 0x7fffffffdd08 --> 0x5555555548e2 (lea    rdi,[rip+0x162]        # 0x555555554a4b)
RIP: 0x555555755020 --> 0x6deeb47035141c6d 
R8 : 0x0 
R9 : 0x0 
R10: 0x7ffff7b82cc0 --> 0x2000200020002 
R11: 0x555555554a08 --> 0x0 
R12: 0x5555555546f0 (xor    ebp,ebp)
R13: 0x7fffffffde30 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55555575501a:        add    BYTE PTR [rax],al
   0x55555575501c:        add    BYTE PTR [rax],al
   0x55555575501e:        add    BYTE PTR [rax],al
=> 0x555555755020:        ins    DWORD PTR es:[rdi],dx
   0x555555755021:        sbb    al,0x14
   0x555555755023:        xor    eax,0x6deeb470
   0x555555755028:        hlt    
   0x555555755029:        or     eax,0x1c77035
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd08 --> 0x5555555548e2 (lea    rdi,[rip+0x162]        # 0x555555554a4b)
0008| 0x7fffffffdd10 --> 0x1 
0016| 0x7fffffffdd18 --> 0xcffffffee 
0024| 0x7fffffffdd20 --> 0x7ffff7de59a0 (<_dl_fini>:        push   rbp)
0032| 0x7fffffffdd28 --> 0x0 
0040| 0x7fffffffdd30 --> 0x555555554960 (push   r15)
0048| 0x7fffffffdd38 --> 0x5555555546f0 (xor    ebp,ebp)
0056| 0x7fffffffdd40 --> 0x7fffffffde30 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000555555755020 in ?? ()
gdb-peda$ 

且根据gdb的gdb-peda插件,可以看到,执行的地址(0x0000555555755020)确实和IDA中显示的不一样,不过有一个地方需要注意,地址的后3位数是一样的。

gdb我现在用得还不是太熟,还是习惯OD之类的图形化调试工具,知道IDA有一个功能可以远程调试Linux上的程序,之前也没试过,就趁这次学习记录一下吧。

IDA远程调试Linux程序

首先将Windows上IDA安装目录下的调试程序复制到虚拟机中:

image-20200102160016312

image-20200102160212026

查看Linux虚拟机的IP,并运行该程序:

spwpun@ubuntu:~/Documents/20200102$ ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.119.132  netmask 255.255.255.0  broadcast 192.168.119.255
        inet6 fe80::9d14:35b9:dc2a:66c6  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:fe:9a:13  txqueuelen 1000  (Ethernet)
        RX packets 49088  bytes 61213168 (61.2 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 13714  bytes 1047395 (1.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 678  bytes 60507 (60.5 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 678  bytes 60507 (60.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

spwpun@ubuntu:~/Documents/20200102$ ./linux_server64 
IDA Linux 64-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017
Listening on 0.0.0.0:23946...

然后到IDA中设置调试器信息,Debugger>Select debugger>Remote Linux Debugger:

image-20200102161021326

确定之后,再设置进程信息,Debugger>Process options:

image-20200102161415095

文件路径要设置为Linux上的路径,IP为虚拟机的IP,端口就用默认的就行。

确定之后,点击上方的绿色三角按钮或者按下F9启动调试:

image-20200102161638027

弹出下面的警告,大意就是小心代码执行所带来的的危害,确定就行,不是恶意软件:

image-20200102161904643

由于我没有设置任何断点,所以程序还是没能按照我想的情况走下去,在Linux上提示输入Password了,但是IDA中的寄存器却什么信息也没有:

image-20200102162357326

这时结束掉进程,在main函数设置断点:

image-20200102162509413

image-20200102162544765

这时我们也可以看到,指令前面的地址是真实的内存地址了。

继续执行,程序断在了刚才的地方:

image-20200102162815144

先来简单看一下调试界面,左上方是汇编代码的窗口,左下方是内存区域的数据显示,右上方是三个小窗口(寄存器、已加载模块、线程),右下方是堆栈窗口。基本布局和OD中的一样,习惯了OD,这样看起来特别舒服。

基本的调试快捷键如下:

  • F7:单步步进(进入调用内部)
  • F8:单步步过(不进入)
  • Ctrl+F7:执行到返回
  • F4:执行到光标
  • F2:设置断点

回到刚才说的,要验证Key对loc_201020区域的作用,在这里这片区域的地址和刚才是是不一样的,来看一下反编译的伪码:

printf("Second give me your key: ", &v4);
__isoc99_scanf("%d", &v4);
v4 -= 49;
for ( i = 0; i <= 11; ++i )
    *((_BYTE *)&loc_56290DD48020 + i) += v4;
((void (__fastcall *)(char *, int *))loc_56290DD48020)(s1, &v4);
printf("Then Verify your flag: ");

在“输入Key之后调用scanf函数”处设置断点:

image-20200102164719204

然后执行,转到Linux上输入password之后,断在了此处:

spwpun@ubuntu:~/Documents/20200102$ ./linux_server64 
IDA Linux 64-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017
Listening on 0.0.0.0:23946...
=========================================================
[1] Accepting connection from 192.168.119.1...
First give me your password: 98416
Second give me your key: [1] Closing connection from 192.168.119.1...
=========================================================
[2] Accepting connection from 192.168.119.1...
First give me your password: 98416

仔细分析:

.text:000056290DB47872 lea     rax, [rbp+var_38]
.text:000056290DB47876 mov     rsi, rax                   ;传参&var_38,var_38就是伪码中的v4
.text:000056290DB47879 lea     rdi, aD         ; "%d"
.text:000056290DB47880 mov     eax, 0
.text:000056290DB47885 call    ___isoc99_scanf
.text:000056290DB4788A mov     eax, [rbp+var_38] ;将获取到的key赋值给eax
.text:000056290DB4788D sub     eax, 49                         ;然后再减去49
.text:000056290DB47890 mov     [rbp+var_38], eax ;再重新赋值给var_38

在这里单步步过,转到Linux上随便输入一个Key:

=========================================================
[2] Accepting connection from 192.168.119.1...
First give me your password: 98416
Second give me your key: 78

返回IDA,随后进入修改数据的for循环:

image-20200102170301204

详细分析一下循环中的汇编代码:

.text:000056290DB4789C loc_56290DB4789C:
.text:000056290DB4789C mov     eax, [rbp+var_34]      ;var_34在循环开始前设置为0,是索引
.text:000056290DB4789F movsxd  rdx, eax
.text:000056290DB478A2 lea     rax, loc_56290DD48020  ;需要修改的数据的基地址
.text:000056290DB478A9 movzx   eax, byte ptr [rdx+rax];根据索引找到的本次循环需要修改的数据data[i]
.text:000056290DB478AD mov     edx, [rbp+var_38]      ;key
.text:000056290DB478B0 lea     ecx, [rax+rdx]         ;相加的结果放到ecx中
.text:000056290DB478B3 mov     eax, [rbp+var_34]      ;索引i
.text:000056290DB478B6 movsxd  rdx, eax        
.text:000056290DB478B9 lea     rax, loc_56290DD48020  ;数据的基地址
.text:000056290DB478C0 mov     [rdx+rax], cl          ;最后存放的数据只存放了CL寄存器中的,也就是只取最低的字节
.text:000056290DB478C3 add     [rbp+var_34], 1        ;i+1

关键的是最后存放数据的时候只取了key和data相加之后结果的低8位,意思就是如果key的值超过了0xFF,其结果仍然会在0-0xFF中重复,这和之后我写代码爆破可用的Key有关。

循环执行完,会将刚才那一部分区域的数据当做代码执行,这里看到的是call rdx:

.text:000056290DB478CD lea     rdx, loc_56290DD48020
.text:000056290DB478D4 lea     rdi, s1         ; "iAxU.|&30"
.text:000056290DB478DB mov     eax, 0
.text:000056290DB478E0 call    rdx ; loc_56290DD48020
.text:000056290DB478E2 lea     rdi, aThenVerifyYour ; "Then Verify your flag: "
.text:000056290DB478E9 mov     eax, 0
.text:000056290DB478EE call    _printf

跟踪进去看了之后,确实是将修改后的数据当做代码来执行:

image-20200102172808761

但是由于key不正确,所以解码出来的汇编代码是会出大问题的,继续执行了几步之后程序就崩溃了:

image-20200102172944597

而根据上面的汇编代码,如果解码之后的代码能够正常执行的话,应该会继续提示让输入flag验证,根据这个思路,我们可以写一段简单的代码来爆破可能的key,这里我用到的是expect

4.expect编程

expect是一个用来实现自动化交互功能的软件套件,基于tcl包。安装命令可以使用下面的,这里我已经安装过了:

spwpun@ubuntu:~/Documents/20200102$ sudo apt install tcl expect
[sudo] password for spwpun: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
expect is already the newest version (5.45.4-1).
tcl is already the newest version (8.6.0+9).
0 upgraded, 0 newly installed, 0 to remove and 130 not upgraded.
spwpun@ubuntu:~/Documents/20200102$ 

我的基本思路是运行题目给出的pro程序,使用expect自动填入password和key,通过一个循环控制key的值来测试,如果接收到"Then Verify your flag: ",就说明key解码后的代码是可以正常执行的,最后输出所有的key。代码很简单,简单借鉴一下网上的一些基础脚本就可以写出来:

#!/usr/bin/expect            
# For bamboofoxctf-Move or not
# filename:crack.sh
set time 30
set keys ""
for {set key 0} {$key<=255} {incr key} {     #incr在这里是增加1
        spawn ./pro                                                                 #spawn是另起一个子进程
        expect "*password: " {send "98416\r"}    #如果收到子进程的结果为*password: ,则发送98416\r,这里可以使用正则来匹配,发送的数据最后要加一个换行符
        expect "*key: " {send "$key\r"}
        expect "*flag: " {
                send "Test_flag\r"
                set keys "$keys $key"
        }
}
puts "All keys: $keys\r"

执行结果:

spwpun@ubuntu:~/Documents/20200102$ ./crack.sh 
spawn ./pro
First give me your password: 98416
Second give me your key: 0
spawn ./pro
......
First give me your password: 98416
Second give me your key: 254
spawn ./pro
First give me your password: 98416
Second give me your key: 255
All keys:  39 43 48 50 114 117 206
spwpun@ubuntu:~/Documents/20200102$ 

到此知道了所有可能的key,就可以使用IDA动态调试一波,最后在测试50的时候顺利在比对字符串的时候拿到了flag,其中正确解码后的汇编代码如下:

image-20200102181832258

rdi是传入变量s1的地址,可以看到,依次对s1的数据进行sub操作,得到正确的flag,strcmp函数比较时可以清楚看到正确的flag:

image-20200102182217223

5.ltrace动态调试

ltrace可以跟踪程序执行时库函数的调用(包括参数),所以也会跟踪strcmp函数的调用,在上面得到所有的keys之后,可以使用它来测试,测试的过程如下,很轻松就得到了flag:

spwpun@ubuntu:~/Documents/20200102$ ltrace ./pro
printf("First give me your password: ")          = 29
__isoc99_scanf(0x5563a643ea06, 0x7fffd7e61358, 0, 0First give me your password: 98416
) = 1
printf("Second give me your key: ")              = 25
__isoc99_scanf(0x5563a643ea06, 0x7fffd7e61358, 0, 0Second give me your key: 50
) = 1
printf("Then Verify your flag: ")                = 23
__isoc99_scanf(0x5563a643ea63, 0x7fffd7e61360, 0, 0Then Verify your flag: aaa
) = 1
strcmp("BambooFox{dyn4mic_1s_4ls0_gr34t}"..., "aaa") = -31
puts("You don't know dynamic analysis "...You don't know dynamic analysis !
)      = 34
+++ exited (status 0) +++
spwpun@ubuntu:~/Documents/20200102$
  • flag: BambooFox{dyn4mic_1s_4ls0_gr34t}
6.总结

元旦那晚为了这道题肝了一晚,一直在尝试些angr的脚本来爆破,无奈之前没有接触过,最后没有写出来,分析出key的范围之后,手工解出了这道题,实在是菜。看到赛后各位大佬wp中的轻描淡写,我觉得我和他们真是差了不是一点半点。希望看到这篇文章的师傅们不吝建议!

道阻且长,Happy New Year!

Move or not.zip

942.87 KB, 下载次数: 7, 下载积分: 吾爱币 -1 CB

题目文件及pdf的wp

免费评分

参与人数 4威望 +2 吾爱币 +11 热心值 +3 收起 理由
为海尔而战 + 1 + 1 我很赞同!
二娃 + 2 谢谢@Thanks!
Hmily + 2 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
FleTime + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

wisoft 发表于 2020-1-19 16:21
学习爆破思路及ltrace,感谢分享!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-29 01:45

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表