CTF逆向分析
9unk Lv5

ctfshow-逆向签到题re1(linux)

linux 可执行文件在内存中运行时,如下几个段:

内存段 说明
BSS段 存储未初始化的全局变量或者是默认初始化为0的全局变量
data段 用于存储初始化的全局变量,初始化为 0 的全局变量处于编译优化的策略还是被保存在BSS段中
.rodata 该段也叫常量区,用于存放常量数据,ro(Read Only)只读,注意:不是所有的常量都是放在常量数据段中
text段 用于存放程序代码
stack段 栈段,用于存储参数变量及局部变量
heap段 堆,由用户申请和释放内存空间
  1. 查看文件类型
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
root@kali:/opt/C/CTF# rabin2 -I re1
arch x86
baddr 0x0
binsz 6585
bintype elf #elf文件
bits 64 #64位
canary true #开启了 canary 栈溢出保护机制
class ELF64
compiler GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c #编写语言:C
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic true
relocs true
relro full
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
  1. 执行程序判断大体架构
1
2
3
4
5
6
7
8
root@kali:/opt/C/CTF# ./re1
plz input the key:
jflsdfs
key error

1、打印字符串:"plz input the key:"
2、获取用户输入
3、通过判断输出信息:错误"key error"
  1. 查看 .rodata段的常量
1
2
3
4
5
6
root@kali:/opt/C/CTF# rabin2 -z re1 
[Strings]
Num Paddr Vaddr Len Size Section Type String
000 0x00000884 0x00000884 16 17 (.rodata) ascii flag{7ujm8ikhy6}
001 0x00000895 0x00000895 18 19 (.rodata) ascii plz input the key:
002 0x000008ab 0x000008ab 9 10 (.rodata) ascii key error
  1. 获得 flag{7ujm8ikhy6}

注:签到题一般就是把 flag 放在常量区。

静态分析

  1. 使用 r2 的 -A 选项分析代码,并进入程序入口点
1
2
3
4
5
6
7
8
9
10
11
root@kali:/opt/C/CTF# r2 -A re1
[Cannot analyze at 0x00000650g with sym. and entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[Cannot analyze at 0x00000650ac)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
  1. 查看 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
[0x00000660]> pdf @ sym.main
/ (fcn) main 146
| int main (int argc, char **argv, char **envp);
| ; var char *s1 @ rbp-0x78
| ; var char *s2 @ rbp-0x70
| ; var int32_t canary @ rbp-0x8
| ; DATA XREF from entry0 @ 0x67d
| 0x0000076a 55 push rbp
| 0x0000076b 4889e5 mov rbp, rsp
| 0x0000076e 4883c480 add rsp, -0x80
| 0x00000772 64488b042528. mov rax, qword fs:[0x28]
| 0x0000077b 488945f8 mov qword [canary], rax
| 0x0000077f 31c0 xor eax, eax
| 0x00000781 488d05fc0000. lea rax, qword str.flag_7ujm8ikhy6 ; 0x884 ; "flag{7ujm8ikhy6}"
| 0x00000788 48894588 mov qword [s1], rax
| 0x0000078c 488d3d020100. lea rdi, qword str.plz_input_the_key: ; 0x895 ; "plz input the key:" ; const char *s
| 0x00000793 e878feffff call sym.imp.puts ; int puts(const char *s)
| 0x00000798 488d4590 lea rax, qword [s2]
| 0x0000079c 4889c6 mov rsi, rax
| 0x0000079f 488d3d020100. lea rdi, qword [0x000008a8] ; "%s" ; const char *format
| 0x000007a6 b800000000 mov eax, 0
| 0x000007ab e890feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
| 0x000007b0 488d5590 lea rdx, qword [s2]
| 0x000007b4 488b4588 mov rax, qword [s1]
| 0x000007b8 4889d6 mov rsi, rdx ; const char *s2
| 0x000007bb 4889c7 mov rdi, rax ; const char *s1
| 0x000007be e86dfeffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
| 0x000007c3 85c0 test eax, eax
| ,=< 0x000007c5 750e jne 0x7d5
| | 0x000007c7 488b4588 mov rax, qword [s1]
| | 0x000007cb 4889c7 mov rdi, rax ; const char *s
| | 0x000007ce e83dfeffff call sym.imp.puts ; int puts(const char *s)
| ,==< 0x000007d3 eb0c jmp 0x7e1
| || ; CODE XREF from main @ 0x7c5
| |`-> 0x000007d5 488d3dcf0000. lea rdi, qword str.key_error ; 0x8ab ; "key error" ; const char *s
| | 0x000007dc e82ffeffff call sym.imp.puts ; int puts(const char *s)
| | ; CODE XREF from main @ 0x7d3
| `--> 0x000007e1 b800000000 mov eax, 0
| 0x000007e6 488b4df8 mov rcx, qword [canary]
| 0x000007ea 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x000007f3 7405 je 0x7fa
| | 0x000007f5 e826feffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; CODE XREF from main @ 0x7f3
| `-> 0x000007fa c9 leave
\ 0x000007fb c3 ret

pdf表示p(打印)d(反汇编)f(函数), @表示取地址, sym.main为函数符号。

开辟栈空间

1
2
3
|           0x0000076a      55             push rbp             |保存栈底
| 0x0000076b 4889e5 mov rbp, rsp |提升栈底
| 0x0000076e 4883c480 add rsp, -0x80 |提升栈顶

设置 canary

1
2
|           0x00000772      64488b042528.  mov rax, qword fs:[0x28]     |
| 0x0000077b 488945f8 mov qword [canary], rax |开启 canary 机制

局部变量

  1. [rbp-0x8]
1
2
|           0x00000772      64488b042528.  mov rax, qword fs:[0x28]
| 0x0000077b 488945f8 mov qword [canary], rax

[rbp-0x08] 是开启 canary 机制后,生成的随机验证码。程序自动分析后,定义为 canary

  1. [rbp-0x78]
1
2
3
|           0x0000077f      31c0           xor eax, eax
| 0x00000781 488d05fc0000. lea rax, qword str.flag_7ujm8ikhy6 ; 0x884 ; "flag{7ujm8ikhy6}"
| 0x00000788 48894588 mov qword [s1], rax

把 flag 存到了 [rbp-0x78] 中。

这里 r2 对程序自动分析,将 [rbp-0x78] 定义为 s1

  1. [rbp-0x70]
1
2
3
4
5
|           0x00000798      488d4590       lea rax, qword [s2]
| 0x0000079c 4889c6 mov rsi, rax
| 0x0000079f 488d3d020100. lea rdi, qword [0x000008a8] ; "%s" ; const char *format
| 0x000007a6 b800000000 mov eax, 0
| 0x000007ab e890feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)

[rbp-0x70] 是输入字符串。程序自动分析后,定义为 s2

  1. 观察程序功能用到了哪些局部变量

从后面的程序来看,只用到了[rbp-0x70] 和 [rbp-0x78] 这两个局部变量。我们分别重命名为 input 和 flag

分析程序功能

  1. 内置函数分析
1
2
3
4
5
6
7
8
9
10
11
12
|           0x0000078c      488d3d020100.  lea rdi, qword str.plz_input_the_key:
| 0x00000793 e878feffff call sym.imp.puts ; puts("plz input the key:")
| 0x00000798 488d4590 lea rax, qword [s2]
| 0x0000079c 4889c6 mov rsi, rax
| 0x0000079f 488d3d020100. lea rdi, qword [0x000008a8] ;
| 0x000007a6 b800000000 mov eax, 0
| 0x000007ab e890feffff call sym.imp.__isoc99_scanf ; scanf("%s",input)
| 0x000007b0 488d5590 lea rdx, qword [s2]
| 0x000007b4 488b4588 mov rax, qword [s1]
| 0x000007b8 4889d6 mov rsi, rdx ;
| 0x000007bb 4889c7 mov rdi, rax ;
| 0x000007be e86dfeffff call sym.imp.strcmp ; int strcmp(flag,input)
  1. JCC 判断分析
1
2
3
4
5
6
7
8
9
|           0x000007c3      85c0           test eax, eax
| ,=< 0x000007c5 750e jne 0x7d5
| | 0x000007c7 488b4588 mov rax, qword [s1]
| | 0x000007cb 4889c7 mov rdi, rax ;
| | 0x000007ce e83dfeffff call sym.imp.puts ; int puts(flag)
| ,==< 0x000007d3 eb0c jmp 0x7e1
| || ; CODE XREF from main @ 0x7c5
| |`-> 0x000007d5 488d3dcf0000. lea rdi, qword str.key_error ;
| | 0x000007dc e82ffeffff call sym.imp.puts ; int puts("key error")

从这题结构来看,这是一个 if…else 语句。test eax,eax 是将返回值只作为判断条件。

代码分析

1
2
3
4
5
6
7
8
if(strcmp(flag,input) == 0)
{
puts(flag);
}
else
{
puts("key error");
}

main 函数结尾部分

1
2
3
4
5
6
7
8
|      `--> 0x000007e1      b800000000     mov eax, 0
| 0x000007e6 488b4df8 mov rcx, qword [canary]
| 0x000007ea 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x000007f3 7405 je 0x7fa
| | 0x000007f5 e826feffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; CODE XREF from main @ 0x7f3
| `-> 0x000007fa c9 leave
\ 0x000007fb c3 ret

代码还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
char flag[]={0};
strcpy(flag,"flag{7ujm8ikhy6}");
char input[20];
scanf("%s",input);

if(strcmp(flag,input) == 0)
{
puts(flag);
}
else
{
puts("key error");
}
}

知识补充

  1. Canary 机制

Canary 机制是一种栈溢出保护机制,栈溢出保护是一种缓冲区溢出攻击环节手段(只是环节机制,不能彻底阻止)。当弃用栈保护后,函数开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法(栈帧销毁前测试该值是否被改变),如果不合法就停止程序运行(栈溢出发生)。

gcc 相关参数及意义

1
2
3
4
5
-fstack-protector           启用保护,不过只为局部变量中含有数组的函数插入保护
-fstack-protector-all 启用保护,为所有函数插入保护
-fstack-protector-strong
-fstack-protector-explicit 只对有明确 stack_protect attribute 的函数开启保护
-fno-stack-protector 禁用保护

Canary 机制插入结构

1
2
3
4
5
6
7
8
9
10
11
//函数头部添加
mov rax,QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8],rax



//结尾部分添加
mov rax,QWORD PTR [rbp-0x8]
sub rax,QWORD PTR fs:0x28
je 0x1161 <test+44>
call 0x1030 <__stack_chk_fail@plt>

攻防世界-insanity(linux)

  1. 查看文件类型
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
root@kali:/opt/C/CTF# rabin2 -I insanity 
arch x86
baddr 0x8048000
binsz 5915
bintype elf #elf文件
bits 32 #32位
canary false #未开启 canary 机制
class ELF32
compiler GCC: (Debian 4.7.2-5) 4.7.2/GCC: (Debian 4.4.7-2) 4.4.7
crypto false
endian little
havecode true
intrp /lib/ld-linux.so.2
laddr 0x0
lang c #编写语言:C
linenum true
lsyms true
machine Intel 80386
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro no
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
  1. 执行程序判断程序架构
1
2
3
4
5
6
7
root@kali:/opt/C/CTF# ./insanity 
Reticulating splines, please wait..
Your ability to hack is about as good as my ability to have free will.

1、 打印字符串:"Reticulating splines, please wait.."
2、 等待了几秒
3、 打印字符串:"Your ability to hack is about as good as my ability to have free will."
  1. 查看常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@kali:/opt/C/CTF# rabin2 -z insanity
[Strings]
Num Paddr Vaddr Len Size Section Type String
000 0x000005d0 0x080485d0 35 36 (.rodata) ascii Reticulating splines, please wait..
001 0x000005f4 0x080485f4 63 64 (.rodata) ascii If you're pretending to suck, you just passed that Turing test.
002 0x00000634 0x08048634 69 70 (.rodata) ascii There aren't enough bits in my memory to represent how hard you fail.
003 0x0000067c 0x0804867c 70 71 (.rodata) ascii Your ability to hack is about as good as my ability to have free will.
004 0x000006c4 0x080486c4 77 78 (.rodata) ascii Have you considered becoming a vacuum cleaner? You're pretty good at sucking.
005 0x00000714 0x08048714 69 70 (.rodata) ascii I've got a good feeling about this one..... wait no. Maybe next time.
006 0x0000075c 0x0804875c 41 42 (.rodata) ascii Knock knock..\nWho's there?\nUDP.\nUDP who?\n
007 0x00000788 0x08048788 20 21 (.rodata) ascii 9447{This_is_a_flag}
008 0x0000079d 0x0804879d 27 28 (.rodata) ascii Congrats, you hacked me!\n$
009 0x000007b9 0x080487b9 28 29 (.rodata) ascii rm -rf / : Permission denied
010 0x000007d6 0x080487d6 29 30 (.rodata) ascii #define YOU "massive failure"
  1. 获取 flag:9447{This_is_a_flag}

静态分析

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
root@kali:/opt/C/CTF# r2 -A insanity 
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x08048450]> pdf @ sym.main
;-- section..text:
;-- .text:
/ (fcn) main 96
| int main (int argc, char **argv, char **envp);
| ; DATA XREF from entry0 @ 0x8048467
| 0x080483f0 55 push ebp ; [14] -r-x section size 448 named .text
| 0x080483f1 89e5 mov ebp, esp
| 0x080483f3 83e4f0 and esp, 0xfffffff0
| 0x080483f6 83ec10 sub esp, 0x10
| 0x080483f9 c70424d08504. mov dword [esp], str.Reticulating_splines__please_wait.. ; [0x80485d0:4]=0x69746552 ; "Reticulating splines, please wait.." ; const char *s
| 0x08048400 e89bffffff call sym.imp.puts ; int puts(const char *s)
| 0x08048405 c70424050000. mov dword [esp], 5 ; int s
| 0x0804840c e87fffffff call sym.imp.sleep ; int sleep(int s)
| 0x08048411 c70424000000. mov dword [esp], 0 ; time_t *timer
| 0x08048418 e863ffffff call sym.imp.time ; time_t time(time_t *timer)
| 0x0804841d 890424 mov dword [esp], eax ; int seed
| 0x08048420 e89bffffff call sym.imp.srand ; void srand(int seed)
| 0x08048425 e8b6ffffff call sym.imp.rand ; int rand(void)
| 0x0804842a bacdcccccc mov edx, 0xcccccccd
| 0x0804842f 89c1 mov ecx, eax ;
| 0x08048431 f7e2 mul edx ;eax*edx
| 0x08048433 c1ea03 shr edx, 3 ;edx 逻辑右移 3 位(edx=ecx/10)
| 0x08048436 8d0492 lea eax, dword [edx + edx*4];eax = edx*5
| 0x08048439 01c0 add eax, eax ;eax=edx*10
| 0x0804843b 29c1 sub ecx, eax ;ecx=ecx-eax
| 0x0804843d 8b048dc09904. mov eax, dword [ecx*4 + obj.strs];
| 0x08048444 890424 mov dword [esp], eax ; const char *s
| 0x08048447 e854ffffff call sym.imp.puts ; int puts(const char *s)
| 0x0804844c 31c0 xor eax, eax
| 0x0804844e c9 leave
\ 0x0804844f c3 ret

这段程序如何执行的还没看懂,暂时就放一放不分析了。

知识点补充

  1. 取余算法的优化
1
2
3
4
5
6
7
8
9
10
11
12
13
mov edx, 0xcccccccd
mov ecx, eax ;ecx == 0xcccccccd

//edx=ecx/10
mul edx ;eax*edx
shr edx, 3 ;edx 逻辑右移 3

//(ecx/10)*10:这里存的是整数,并没有 除以10 之后的小数
lea eax, dword [edx + edx*4];eax = edx*5
add eax, eax ;eax=edx*10

//ecx-(ecx/10)*10 == 余数
sub ecx, eax ;ecx=ecx-eax

示例:

1
2
3
101/10=10
10*10=100
101-100=1

攻防世界-python-trade

对于 pyc 文件的反汇编成源码,可以使用 pip 模块 “uncompyle6” 进行反汇编

  1. 反汇编成源码,并分析程序
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
root@kali:/opt/C/CTF# uncompyle6 py1.pyc 
# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.8.5 (default, Aug 2 2020, 15:09:07)
# [GCC 10.2.0]
# Embedded file name: 1.py
# Compiled at: 2017-06-03 10:20:43
import base64

def encode(message): #定义函数:encode 函数;参数:message
s = ''
for i in message: #使用 for 循环,将参数加密。python 的 for 循环默认遍历参数
x = ord(i) ^ 32 #ord(i):将 i 转换成十进制,然后与 32 异或
x = x + 16 #x+16
s += chr(x) #将 x 转换成字符

return base64.b64encode(s) #将 message 参数使用 base64 编码加密


correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
flag = ''
print 'Input flag:'
flag = raw_input()
if encode(flag) == correct: #encode(flag) == XlNkVmtUI1MgXWBZXCFeKY+AaXNt
print 'correct'
else:
print 'wrong'
#okay decompiling py1.pyc
  1. 通过逆算法,计算出 flag

逆算法

  • base64 解码成 flag1
  • flag2=(ord(flag)-16) ^ 32:将字符串转换成十进制,并进行逆向运算
  • flag3 = str(flag2)
1
2
3
4
5
6
7
8
9
10
 1 #!/usr/bin/python
2 import base64
3
4 flag = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
5 flag1 = base64.b64decode(flag)
6 flag2 = ''
7 for i in flag1:
8 flag2 += chr((ord(i)-16)^32)
9
10 print (flag2)
  1. 执行程序,获得flag
1
2
root@kali:/opt/C/CTF# ./flag.py   
nctf{d3c0mpil1n9_PyC}
  1. 获得 flag:nctf{d3c0mpil1n9_PyC}

攻防世界-re1(windows)

  1. PEID 查看文件信息

这是一个 32 位的程序,没有壳

1.jpg

  1. 执行程序,分析程序的功能

9.jpg

1
2
3
4
5
6
打印 "欢迎来到DUTCTF呦"
打印 "这是一道很可爱很简单的逆向题呦"
打印 "输入flag吧:"
获取用户输入:"fucker"
打印 "flag不太对呦,再试试呗,加油呦"
system("pause") "请按任意键继续. . ."
  1. 使用 x64dbg 加载程序到主模块

2.jpg

注:主模块的入口点 EntryPoint 是 main 函数执行前的一些初始化的操作,并不是 main 函数的入口点。

  1. 查找 main 函数入口点

右键–>搜索–>当前模块–>跨模块调用,来查找程序使用了哪些 windows API

3.jpg

4.jpg

搜索 GetCommandLine 函数

5.jpg

双击 GetCommandLine 函数,定位到反汇编窗口

6.jpg

查找调用了 3 个参数的函数,该函数很有可能就是 main 函数

7.jpg

左击该函数,然后按 Enter,进入函数入口点

8.jpg

如上图所示,已经定位到了 main 函数。从 main 函数开头就已经看到了 flag。不过还是要静态分析,提升自己的分析能力。

win32 程序大多是以 push 指令来传递参数的

程序静态分析

开辟栈空间

1
2
3
00641000 <re1.sub_641000>                 | 55                    | push ebp                                              |保存栈底
00641001 | 8BEC | mov ebp,esp |提升栈底
00641003 | 83EC 44 | sub esp,44 |提升栈顶
1
2
3
00641006                                  | A1 00506500           | mov eax,dword ptr ds:[655000]                         |
0064100B | 33C5 | xor eax,ebp |
0064100D | 8945 FC | mov dword ptr ss:[ebp-4],eax |___security_cookie

分析局部变量

局部变量常以:[ebp-xxx] 的形式出现

  1. [ebp-44]
1
2
3
4
5
6
[ebp-44]="DUTCTF{We1c0met0"     //xmm0 = 128 bit = 16(128/8) 字节,所以这里只存储了 16 个字符串。

movdqu xmm0,xmmword ptr ds:[653E34] | "DUTCTF{We1c0met0DUTCTF}"
movdqu xmmword ptr ss:[ebp-44],xmm0 | "DUTCTF{We1c0met0"

//注:movdqu 属于 SSE2 指令集。
  1. [ebp-2C]
1
2
3
4
[ebp-2C] = 0

xor eax,eax |
mov dword ptr ss:[ebp-2C],eax
  1. [ebp-34]
1
2
3
4
[ebp-44]+[ebp-34] = "DUTCTF{We1c0met0DUTCTF}"

movq xmm0,qword ptr ds:[653E44] | "DUTCTF}}"
movq qword ptr ss:[ebp-34],xmm0 | "DUTCTF}}"
  1. [ebp-28]
1
2
[ebp-28] = 0
mov word ptr ss:[ebp-28],ax
  1. [ebp-24]
1
2
3
4
5
6
这是 scanf 函数反汇编特征,所以这里 [ebp-24] 是我们输入的 flag

lea eax,dword ptr ss:[ebp-24] |
push eax | Arg2
push re1.653E8C | Arg1 = "%s"==L"猥"
call <re1.sub_6410D1> | sub_8410D1
  1. 观察程序功能用到了哪些局部变量

从后面的程序中,只看到了 [ebp-44](包括 [ebp-34]) 和 [ebp-24] 这两各个局部变量。其他的都没用到,那么我们把 [ebp-44] 和 [ebp-34] 定义为局部变量:flag、input

至此局部边变量已经分析结束。这里把汇编代码的顺序改了一下,方便看懂。我们分析局部变量的时候,不需要太过关注这些汇编指令的顺序。我们只需要知道这个局部变量存的值是什么,它在哪里被到了,分析它到底是个什么东西。

分析程序功能

  1. 内置函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
push re1.653E4C                                       | Arg1 = "欢迎来到DUTCTF呦\n"
call <re1.sub_64127B> | printf("欢迎来到DUTCTF呦\n"),调用上面的 push 参数

push re1.653E60 | Arg1 = "这是一道很可爱很简单的逆向题呦\n"
call <re1.sub_64127B> | printf("这是一道很可爱很简单的逆向题呦\n")

push re1.653E80 | Arg1 = "输入flag吧:"
call <re1.sub_64127B> | printf("输入flag吧:")

lea eax,dword ptr ss:[ebp-24] |
push eax | Arg2
push re1.653E8C | Arg1 = "%s"==L"猥"
call <re1.sub_6410D1> | scanf("%s",input)
add esp,14 | 堆栈平衡
  1. 循环代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
00641062                                  | 8D45 DC               | lea eax,dword ptr ss:[ebp-24]                         | 获取 input 的内存地址
00641065 | 8D4D BC | lea ecx,dword ptr ss:[ebp-44] | 获取 flag 的内存地址
00641068 | 8A11 | mov dl,byte ptr ds:[ecx] |
0064106A | 3A10 | cmp dl,byte ptr ds:[eax] | 判断 flag 和 input 是否相等
0064106C | 75 1A | jne re1.641088 | 不相等就跳到 641088
0064106E | 84D2 | test dl,dl | test 指令用得是 and 运算,即 flag and input 。如果等于0,就说明我的输入为 0,或是循环结束为 0
00641070 | 74 12 | je re1.641084 | 如果等于 0 就跳出循环
00641072 | 8A51 01 | mov dl,byte ptr ds:[ecx+1] |
00641075 | 3A50 01 | cmp dl,byte ptr ds:[eax+1] | 继续循环比较
00641078 | 75 0E | jne re1.641088 | 如果等于 0 就跳到 641088
0064107A | 83C1 02 | add ecx,2 |
0064107D | 83C0 02 | add eax,2 | 内存地址+2
00641080 | 84D2 | test dl,dl | 再次测试循环是否结束
00641082 | 75 E4 | jne re1.641068 | 不等于 0 ,就跳到 641068 继续比较字符串

00641084 | 33C0 | xor eax,eax | 字符串比较结束,返回 0

00641088 | 1BC0 | sbb eax,eax |
0064108A | 83C8 01 | or eax,1 | 返回 -1

这段分析完成:这段代码就是将两个字符串进行比较,并返回值 0 , -1 。这段代码既然是比较字符串的代码,所以我们可以用 strcmp 进行代替。这段代码就分析为:strcmp(flag,input)

  1. JCC 比较分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
00641086                                  | EB 05                 | jmp re1.64108D                                        | 字符串相等

// 这里的结构是 if..else 结构

0064108D | 85C0 | test eax,eax | and 运算之前的返回值,判断返回值是不是等于 0
0064108F | 75 07 | jne re1.641098 | 不等于就报错,等于就打印 flag_get
00641091 | 68 903E6500 | push re1.653E90 | 653E90:"flag get√\n"
00641096 | EB 05 | jmp re1.64109D |
00641098 | 68 9C3E6500 | push re1.653E9C | Arg1 = "flag不太对呦,再试试呗,加油呦\n"
0064109D | E8 D9010000 | call <re1.sub_64127B> | sub_84127B
006410A2 | 83C4 04 | add esp,4 |

006410A5 | 68 BC3E6500 | push re1.653EBC | Arg1 = "pause"==L"慰獵e"
006410AA | E8 C2000000 | call <re1.sub_641171> | system(pause)

这段分析完成:

1
2
3
4
5
6
7
8
if(strcmp(flag,input) == 0)
{
printf("flag get√\n");
}
else
{
printf("flag不太对呦,再试试呗,加油呦\n");
}

main 函数结尾部分分析

1
2
3
4
5
6
7
8
9
006410AF                                  | 8B4D FC               | mov ecx,dword ptr ss:[ebp-4]                          |
006410B2 | 83C4 04 | add esp,4 |
006410B5 | 33CD | xor ecx,ebp |
006410B7 | 33C0 | xor eax,eax |
006410B9 | E8 04000000 | call <re1.sub_6410C2> |__security_check_cookie:cookie 检测

006410BE | 8BE5 | mov esp,ebp |降低栈顶
006410C0 | 5D | pop ebp |降低栈底
006410C1 | C3 | ret |函数返回

代码还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, char* argv[])
{
char flag[]={0};
strcpy(flag,"DUTCTF{We1c0met0DUTCTF}");
char input[23];

printf("欢迎来到DUTCTF呦\n");
printf("这是一道很可爱很简单的逆向题呦\n");
printf("输入flag吧:");
scanf("%s",input);

if(strcmp(input,flag) == 0)
{
printf("flag get√\n");
}
else
{
printf("flag不太对呦,再试试呗,加油呦\n");
}
system("pause");
return 0;
}

知识点补充

  1. if…else 结构
1
2
3
4
5
cmp
JCC(跳转到程序2//JCC与源码相反
//程序1
jmp //跳转到程序结束的位置
//程序2
  1. ___security_cookie:广义上来讲是栈的保护机制,加上 /GS 编译选项,编译器就会自动添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
//程序头部添加
mov eax,dword ptr ds:[655000] //先获取随机值:cookie
xor eax,ebp //再进行异或,加密 cookie
mov dword ptr ss:[ebp-4],eax //最后将 cookie 存储到 [ebp-4] 中


//程序尾部添加
mov ecx,dword ptr ss:[ebp-4] //获取加密后的 cookie
add esp,4 //堆栈平衡,将 esp 还原
xor ecx,ebp //再次异或,获取原始 cookie
xor eax,eax
call <re1.sub_6410C2> //检测 cookie 值

总结

我们通过学习 C/C++ 反汇编是为了提高逆向的效率,当我们看到没见到的架构类型就需要手动分析。然后把分析出来的汇编总结出来,当以后看到与之类似的汇编的时候,就能够快速分析出这块汇编是干什么的。然后用功能类似的函数来代替,并还原成源码。

reference

Linux段管理,BSS段,data段,.rodata段,text段

___security_cookie

c –x86解码汇编函数

Canary机制及绕过策略

  • 本文标题:CTF逆向分析
  • 本文作者:9unk
  • 创建时间:2021-04-21 15:26:28
  • 本文链接:https://9unkk.github.io/2021/04/21/ctf-ni-xiang-fen-xi/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!