Unfortunately, it has started snowing in the land ...
생성일: 2026년 7월 31일
답변에 사용된 모델 GPT-5.6 Thinking by Chat01
생성일: 2026년 7월 31일
답변에 사용된 모델 GPT-5.6 Thinking by Chat01
Unfortunately, it has started snowing in the land of Windows XP. But there is a QR code in the image... somewhere. Uncovering it is all a matter of timing.
flag format: bushbash{....}
solved challenge rồi viết writeup chi tiết
không bịa flag
không được dừng lại khi chưa tìm ra flag
file đính kèm challenge đã gửi đủ
pythonfrom PIL import Image img=Image.open('/mnt/data/bliss_2.png') img.mode, img.size, img.info ('L', (300, 200), {'xmp': b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0">\n <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n <rdf:Description rdf:about=""\n xmlns:tiff="http://ns.adobe.com/tiff/1.0/"\n xmlns:dc="http://purl.org/dc/elements/1.1/"\n xmlns:exif="http://ns.adobe.com/exif/1.0/"\n xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"\n xmlns:xmp="http://ns.adobe.com/xap/1.0/"\n xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"\n xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"\n tiff:ImageLength="200"\n tiff:ImageWidth="300"\n tiff:ResolutionUnit="2"\n tiff:XResolution="368014/3833"\n tiff:YResolution="368014/3833"\n exif:PixelXDimension="300"\n exif:PixelYDimension="200"\n exif:ColorSpace="65535"\n photoshop:ColorMode="1"\n photoshop:ICCProfile="Greyscale D50"\n xmp:ModifyDate="2026-07-23T19:04:31+10:00"\n xmp:MetadataDate="2026-07-23T19:04:31+10:00">\n <dc:title>\n <rdf:Alt>\n <rdf:li xml:lang="x-default">bliss_2</rdf:li>\n </rdf:Alt>\n </dc:title>\n <xmpMM:History>\n <rdf:Seq>\n <rdf:li\n stEvt:action="produced"\n stEvt:softwareAgent="Affinity 3.2.3"\n stEvt:when="2026-07-23T19:04:31+10:00"/>\n </rdf:Seq>\n </xmpMM:History>\n </rdf:Description>\n </rdf:RDF>\n</x:xmpmeta>\n<?xpacket end="r"?>', 'XML:com.adobe.xmp': '<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>\n<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.5.0">\n <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n <rdf:Description rdf:about=""\n xmlns:tiff="http://ns.adobe.com/tiff/1.0/"\n xmlns:dc="http://purl.org/dc/elements/1.1/"\n xmlns:exif="http://ns.adobe.com/exif/1.0/"\n xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"\n xmlns:xmp="http://ns.adobe.com/xap/1.0/"\n xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"\n xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"\n tiff:ImageLength="200"\n tiff:ImageWidth="300"\n tiff:ResolutionUnit="2"\n tiff:XResolution="368014/3833"\n tiff:YResolution="368014/3833"\n exif:PixelXDimension="300"\n exif:PixelYDimension="200"\n exif:ColorSpace="65535"\n photoshop:ColorMode="1"\n photoshop:ICCProfile="Greyscale D50"\n xmp:ModifyDate="2026-07-23T19:04:31+10:00"\n xmp:MetadataDate="2026-07-23T19:04:31+10:00">\n <dc:title>\n <rdf:Alt>\n <rdf:li xml:lang="x-default">bliss_2</rdf:li>\n </rdf:Alt>\n </dc:title>\n <xmpMM:History>\n <rdf:Seq>\n <rdf:li\n stEvt:action="produced"\n stEvt:softwareAgent="Affinity 3.2.3"\n stEvt:when="2026-07-23T19:04:31+10:00"/>\n </rdf:Seq>\n </xmpMM:History>\n </rdf:Description>\n </rdf:RDF>\n</x:xmpmeta>\n<?xpacket end="r"?>', 'icc_profile': b'\x00\x00\x01\\lcms\x040\x00\x00mntrGRAYXYZ \x07\xea\x00\x07\x00\x13\x00\x07\x00\x0e\x00,acspAPPL\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\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-lcms\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\x04desc\x00\x00\x00\xb4\x00\x00\x006cprt\x00\x00\x00\xec\x00\x00\x00Lwtpt\x00\x00\x018\x00\x00\x00\x14kTRC\x00\x00\x01L\x00\x00\x00\x10mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x00\x1a\x00\x00\x00\x1c\x00G\x00r\x00e\x00y\x00s\x00c\x00a\x00l\x00e\x00 \x00D\x005\x000\x00\x00mluc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0cenUS\x00\x00\x000\x00\x00\x00\x1c\x00N\x00o\x00 \x00c\x00o\x00p\x00y\x00r\x00i\x00g\x00h\x00t\x00,\x00 \x00u\x00s\x00e\x00 \x00f\x00r\x00e\x00e\x00l\x00yXYZ \x00\x00\x00\x00\x00\x00\xf6\xd6\x00\x01\x00\x00\x00\x00\xd3-para\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0233', 'dpi': (96.012, 96.012)})
pythonimport numpy as np, matplotlib.pyplot as plt, os, pandas as pd, math a=(arr>0).astype(float) # Gaussian/box blur at various sizes, save grid perhaps from scipy.ndimage import uniform_filter, gaussian_filter fig, axes=plt.subplots(2,3, figsize=(12,6)) for ax, s in zip(axes.ravel(), [1,2,3,4,6,10]): b=uniform_filter(a, size=s) ax.imshow(b, cmap='gray', vmin=0.35, vmax=0.65) ax.set_title(f'box {s}') ax.axis('off') plt.tight_layout() p='/mnt/data/blur_grid.png' plt.savefig(p,dpi=200) p '/mnt/data/blur_grid.png'
pythonimport struct, zlib, binascii, os, json, textwrap, datetime data=open('/mnt/data/bliss_2.png','rb').read() pos=8 chunks=[] while pos<len(data): L=struct.unpack('>I',data[pos:pos+4])[0] typ=data[pos+4:pos+8].decode('latin1') chunks.append((typ,L,pos)) pos += 12+L chunks [('IHDR', 13, 8), ('iTXt', 1368, 33), ('iCCP', 220, 1413), ('pHYs', 9, 1645), ('IDAT', 8192, 1666), ('IDAT', 5297, 9870), ('IEND', 0, 15179)]
python# Compute 2D FFT power prominent peaks x=(a-a.mean()) F=np.fft.fftshift(np.abs(np.fft.fft2(x))**2) # top peaks excluding center neighborhood h,w=x.shape F2=F.copy() F2[h//2-2:h//2+3,w//2-2:w//2+3]=0 inds=np.argpartition(F2.ravel(), -20)[-20:] tops=sorted([(F2.ravel()[i], np.unravel_index(i,F2.shape)) for i in inds], reverse=True) tops[:20] [(np.float64(151198.73942875554), (np.int64(175), np.int64(92))), (np.float64(151198.73942875548), (np.int64(25), np.int64(208))), (np.float64(141848.37790311855), (np.int64(180), np.int64(92))), (np.float64(141848.37790311855), (np.int64(20), np.int64(208))), (np.float64(136804.46851189368), (np.int64(119), np.int64(110))), (np.float64(136804.46851189368), (np.int64(81), np.int64(190))), (np.float64(129681.6970868835), (np.int64(51), np.int64(234))), (np.float64(129681.69708688346), (np.int64(149), np.int64(66))), (np.float64(128144.99106541695), (np.int64(118), np.int64(232))), (np.float64(128144.99106541695), (np.int64(82), np.int64(68))), (np.float64(125191.59813712955), (np.int64(21), np.int64(176))), (np.float64(125191.59813712948), (np.int64(179), np.int64(124))), (np.float64(124064.70999708072), (np.int64(144), np.int64(137))), (np.float64(124064.70999708072), (np.int64(56), np.int64(163))), (np.float64(122259.73000756568), (np.int64(118), np.int64(210))), (np.float64(122259.73000756568), (np.int64(82), np.int64(90))), (np.float64(121655.09394290831), (np.int64(73), np.int64(165))), (np.float64(121655.09394290826), (np.int64(127), np.int64(135))), (np.float64(120985.92573947432), (np.int64(171), np.int64(204))), (np.float64(120985.92573947432), (np.int64(29), np.int64(96)))]
python# parse IDAT and decompress raw pos=8 idat=b'' chunkdata=[] while pos<len(data): L=struct.unpack('>I',data[pos:pos+4])[0] typ=data[pos+4:pos+8] d=data[pos+8:pos+8+L] if typ==b'IDAT': idat+=d pos+=12+L raw=zlib.decompress(idat) len(raw), raw[:20], set(raw[::301]), collections.Counter(raw[::301]) (60200, b'\x00\xff\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\xff\x00\xff\xff\xff\xff\x00\xff', {0, 1, 2, 4}, Counter({1: 73, 2: 69, 4: 40, 0: 18}))
pythonimport numpy as np, math, pandas as pd, os from PIL import Image arr=np.array(Image.open('/mnt/data/bliss_2.png')) flat=(arr.ravel()>0).astype(np.uint8) states=np.arange(1<<17,dtype=np.uint32) scores=np.zeros_like(states,dtype=np.int32) ntest=3000 for i in range(ntest): states=(states*214013+2531011)&((1<<17)-1) pred=(states>>16)&1 scores += (pred==flat[i]) top=np.argpartition(scores,-20)[-20:] sorted([(int(scores[i]),int(i)) for i in top],reverse=True)[:20] [(1632, 24451), (1618, 110918), (1616, 1647), (1614, 29981), (1613, 470), (1610, 107109), (1608, 38752), (1607, 68490), (1606, 54869), (1605, 38614), (1605, 28033), (1602, 14937), (1600, 41084), (1600, 29068), (1599, 119928), (1599, 92142), (1599, 91713), (1599, 62338), (1599, 19251), (1597, 89370)]
pythonfrom PIL import Image bl=Image.open('/mnt/data/Bliss.jpg') bl.size, bl.mode ((800, 600), 'RGB')
pythonimport numpy as np, pandas as pd, math A=(np.array(Image.open('/mnt/data/bliss_2.png'))>0).astype(np.float64) recs=[] for p in range(2,65): mx=np.array([A[:,i::p].mean() for i in range(p)]) my=np.array([A[i::p,:].mean() for i in range(p)]) # weighted variance approximate recs.append((p,mx.std(),my.std(),mx.min(),mx.max(),my.min(),my.max())) sorted(recs,key=lambda x:max(x[1],x[2]),reverse=True)[:20] [(56, np.float64(0.017976505831355606), np.float64(0.01486786461503925), np.float64(0.485), np.float64(0.563), np.float64(0.49444444444444446), np.float64(0.5611111111111111)), (59, np.float64(0.016414722653555962), np.float64(0.01524329139261019), np.float64(0.489), np.float64(0.562), np.float64(0.49), np.float64(0.5633333333333334)), (64, np.float64(0.01631121096140149), np.float64(0.014846982647309331), np.float64(0.4875), np.float64(0.558), np.float64(0.49666666666666665), np.float64(0.5611111111111111)), (61, np.float64(0.01626533861491962), np.float64(0.015143256220646125), np.float64(0.49), np.float64(0.563), np.float64(0.49666666666666665), np.float64(0.555)), (63, np.float64(0.016055183833938093), np.float64(0.01555512563043922), np.float64(0.48125), np.float64(0.566), np.float64(0.4875), np.float64(0.555)), (55, np.float64(0.015210901798282316), np.float64(0.015922067819495282), np.float64(0.486), np.float64(0.5616666666666666), np.float64(0.4925), np.float64(0.5588888888888889)), (45, np.float64(0.012817949275650808), np.float64(0.015731661869783028), np.float64(0.5035714285714286), np.float64(0.555), np.float64(0.495), np.float64(0.5575)), (53, np.float64(0.014143001265323774), np.float64(0.015514411207435947), np.float64(0.49), np.float64(0.5558333333333333), np.float64(0.4816666666666667), np.float64(0.5616666666666666)), (62, np.float64(0.015230007386734922), np.float64(0.01545528794335641), np.float64(0.4975), np.float64(0.567), np.float64(0.48083333333333333), np.float64(0.5511111111111111)), (57, np.float64(0.013724515168494635), np.float64(0.015408498249319036), np.float64(0.489), np.float64(0.555), np.float64(0.47583333333333333), np.float64(0.5522222222222222)), (60, np.float64(0.015387756677162393), np.float64(0.015082389046903009), np.float64(0.487), np.float64(0.57), np.float64(0.4866666666666667), np.float64(0.5577777777777778)), (50, np.float64(0.013234309367868213), np.float64(0.015353510275431404), np.float64(0.4825), np.float64(0.5516666666666666), np.float64(0.485), np.float64(0.5608333333333333)), (58, np.float64(0.015272491098181566), np.float64(0.01300640462064304), np.float64(0.494), np.float64(0.568), np.float64(0.49583333333333335), np.float64(0.55)), (48, np.float64(0.01500464281278931), np.float64(0.01409938920126562), np.float64(0.49166666666666664), np.float64(0.5635714285714286), np.float64(0.49583333333333335), np.float64(0.554)), (44, np.float64(0.013015016199315582), np.float64(0.014839102535953563), np.float64(0.5), np.float64(0.5492857142857143), np.float64(0.49133333333333334), np.float64(0.5486666666666666)), (54, np.float64(0.014366943147487567), np.float64(0.014493508795107416), np.float64(0.484), np.float64(0.5625), np.float64(0.49583333333333335), np.float64(0.5733333333333334)), (43, np.float64(0.01443068647580056), np.float64(0.012742903422884965), np.float64(0.49785714285714283), np.float64(0.5535714285714286), np.float64(0.49533333333333335), np.float64(0.5516666666666666)), (42, np.float64(0.013904791864502797), np.float64(0.014264808888055555), np.float64(0.49642857142857144), np.float64(0.5621428571428572), np.float64(0.4866666666666667), np.float64(0.5506666666666666)), (52, np.float64(0.01177908424667789), np.float64(0.01393848393712994), np.float64(0.499), np.float64(0.5475), np.float64(0.49666666666666665), np.float64(0.5516666666666666)), (38, np.float64(0.010823273340467579), np.float64(0.013763543963565085), np.float64(0.50375), np.float64(0.545), np.float64(0.49533333333333335), np.float64(0.5546666666666666))]
pythonimport qrcode, qrcode.constants, random, numpy as np, cv2, math, time, os from PIL import Image A=(np.array(Image.open('/mnt/data/bliss_2.png'))>0).astype(np.uint8) X=2*A.astype(np.float32)-1 # white +1 black -1 eccs=[qrcode.constants.ERROR_CORRECT_L,qrcode.constants.ERROR_CORRECT_M,qrcode.constants.ERROR_CORRECT_Q,qrcode.constants.ERROR_CORRECT_H] ecc_names={qrcode.constants.ERROR_CORRECT_L:'L',qrcode.constants.ERROR_CORRECT_M:'M',qrcode.constants.ERROR_CORRECT_Q:'Q',qrcode.constants.ERROR_CORRECT_H:'H'} def gen_matrix(v,ecc,mask,data): qr=qrcode.QRCode(version=v,error_correction=ecc,box_size [(6.13398668255796, 0.3483870327472687, 0.3483870327472687, 4, 33, 'L', 4, 61, 107, 310), (5.874424205113135, 0.3195265531539917, 0.3195265531539917, 5, 37, 'L', 4, 61, 107, 338), (5.835814045591619, 0.3475176692008972, -0.3475176692008972, 2, 25, 'Q', 3, 51, 155, 282), (5.607672321773051, 0.3310104310512543, -0.3310104310512543, 2, 25, 'M', 3, 51, 155, 287), (5.577873093207889, 0.3309858739376068, -0.3309858739376068, 2, 25, 'H', 4, 51, 155, 284), (5.459194835429495, 0.3239436149597168, -0.3239436149597168, 2, 25, 'H', 3, 51, 155, 284), (5.255956144043184, 0.09866100549697876, -0.09866100549697876, 18, 89, 'Q', 6, 82, 68, 2838), (5.252423621269462, 0.257831335067749, -0.257831335067749, 6, 41, 'L', 6, 257, 116, 415), (5.12122503023294, 0.30496451258659363, -0.30496451258659363, 2, 25, 'Q', 1, 51, 155, 282), (5.103160564331977, 0.30281686782836914, -0.30281686782836914, 2, 25, 'Q', 2, 51, 155, 284), (5.080617259014029, 0.09186006337404251, -0.09186006337404251, 20, 97, 'Q', 5, 42, 100, 3059), (5.0675984861119945, 0.2986111044883728, -0.2986111044883728, 2, 25, 'H', 2, 134, 68, 288), (5.052724387387405, 0.3003532886505127, -0.3003532886505127, 2, 25, 'L', 0, 51, 155, 283), (5.034965372685424, 0.2982456088066101, -0.2982456088066101, 2, 25, 'M', 7, 51, 155, 285), (4.988644102351174, 0.1050066351890564, -0.1050066351890564, 16, 81, 'Q', 6, 180, 93, 2257), (4.933837627686456, 0.29328620433807373, -0.29328620433807373, 2, 25, 'H', 1, 51, 155, 283), (4.932437649201885, 0.28289470076560974, 0.28289470076560974, 4, 33, 'Q', 3, 40, 135, 304), (4.92181046917567, 0.1101100966334343, -0.1101100966334343, 16, 81, 'M', 7, 139, 79, 1998), (4.91814972738469, 0.14483952522277832, -0.14483952522277832, 11, 61, 'H', 2, 87, 114, 1153), (4.912292342533241, 0.11436312645673752, -0.11436312645673752, 17, 85, 'L', 7, 189, 65, 1845), (4.910101810468367, 0.13468772172927856, -0.13468772172927856, 12, 65, 'Q', 5, 125, 27, 1329), (4.899334095703789, 0.28919854760169983, -0.28919854760169983, 2, 25, 'H', 6, 51, 155, 287), (4.883028691780046, 0.29078012704849243, -0.29078012704849243, 2, 25, 'L', 7, 51, 155, 282), (4.883028191313948, 0.29078009724617004, 0.29078009724617004, 2, 25, 'L', 6, 42, 161, 282), (4.881275490970529, 0.09129063785076141, 0.09129063785076141, 20, 97, 'M', 3, 140, 75, 2859), (4.866198992535345, 0.09003763645887375, -0.09003763645887375, 19, 93, 'Q', 2, 41, 76, 2921), (4.865804551012852, 0.2887323796749115, -0.2887323796749115, 2, 25, 'M', 1, 51, 155, 284), (4.847762701568041, 0.19422151148319244, -0.19422151148319244, 8, 49, 'Q', 4, 146, 23, 623), (4.8438724181685044, 0.09059111773967743, 0.09059111773967743, 20, 97, 'M', 3, 101, 30, 2859), (4.822639445576192, 0.2704401910305023, 0.2704401910305023, 4, 33, 'L', 2, 40, 135, 318)]
pythonfrom PIL import Image img_fixed=Image.open('/mnt/data/cand_fixed.png') try: from pyzbar.pyzbar import decode print(decode(img_fixed)) except Exception as e: print('pyzbar error',repr(e)) # OpenCV multi z=np.array(img_fixed) det=cv2.QRCodeDetector() for k in range(4): r=np.rot90(z,k).copy() val,pts,straight=det.detectAndDecode(r) print(k,repr(val))
pythonimport qrcode, numpy as np from PIL import Image flag='bushbash{///hillside-errand-lateral}' true=gen_matrix(4,qrcode.constants.ERROR_CORRECT_L,4,flag.encode()) # qrcode matrix black=1, convert white bit true_white=1-true crop=A[107:140,61:94] same,target=invariant_template(4,qrcode.constants.ERROR_CORRECT_L,4,20) print('overall match',np.mean(crop==true_white)) print('known match',np.mean(crop[same]==true_white[same]),'known count',same.sum()) print('data match',np.mean(crop[~same]==true_white[~same]),'data count',(~same).sum()) # exact mismatch counts np.sum(crop[same]!=true_white[same]),np.sum(crop[~same]!=true_white[~same]) (np.int64(101), np.int64(0))
textbushbash{///hillside-errand-lateral}
Flag trên được giải mã độc lập bằng cả OpenCV và pyzbar; cả hai đều trả về đúng chuỗi có ba dấu / sau dấu {.
Ảnh có các đặc điểm:
textKích thước: 300 × 200 Chế độ màu: grayscale Giá trị pixel duy nhất: 0 và 255
Nói cách khác, đây không phải ảnh grayscale chứa nhiều bit-plane để bóc LSB. Nó thực sự là một bitmap đen trắng gồm 60.000 bit.
Thống kê pixel:
textĐen: 28.511 pixel Trắng: 31.489 pixel
Tỷ lệ gần 50/50 khiến ảnh trông giống hoàn toàn với TV static hoặc “snow”.
Các cách thông thường như:
đều không trực tiếp làm QR xuất hiện.
QR không chỉ chứa dữ liệu. Nó còn có nhiều module có giá trị xác định trước:
Phần dữ liệu sau khi áp dụng mask vốn đã nhìn khá ngẫu nhiên. Nếu phá các pattern cố định ở trên, QR sẽ mất hoàn toàn hình dạng đặc trưng và trông giống nhiễu.
Vì hint nhấn mạnh “timing”, hướng hợp lý là tìm một QR có timing pattern và các module chức năng đã bị phủ nhiễu, thay vì cố làm mịn toàn bộ ảnh.
Ta chưa biết:
Tuy nhiên, với một bộ tham số cố định:
textversion + ECC + mask
các module chức năng của QR luôn giữ nguyên, bất kể payload là gì.
Ta có thể lợi dụng điều đó:
Đoạn code cốt lõi:
pythondef invariant_template(version, ecc, mask, tries=12): matrices = [] for _ in range(tries): payload = os.urandom(random.randint(1, 60)) qr = qrcode.QRCode( version=version, error_correction=ecc, box_size=1, border=0, mask_pattern=mask, ) qr.add_data(payload, optimize=0) try: qr.make(fit=False) except Exception: continue matrices.append( np.asarray(qr.get_matrix(), dtype=np.uint8) ) stack = np.stack(matrices) # Những module không thay đổi theo payload known = np.all(stack == stack[0], axis=0) # Quy ước dùng cho correlation: # trắng = +1, đen = -1 target = np.where(stack[0] == 1, -1.0, 1.0) return known, target
Cách này còn tự động bao gồm format information tương ứng với ECC và mask đang thử.
Kích thước cạnh của QR version v là:
textside = 21 + 4 × (v - 1)
Ta thử:
textVersion: 1 → 20 ECC: L, M, Q, H Mask: 0 → 7
Với mỗi template, dùng cross-correlation để trượt nó trên ảnh:
pythonsignal = 2.0 * white_pixels.astype(np.float32) - 1.0 template = known * target corr = cv2.matchTemplate( signal, template.astype(np.float32), cv2.TM_CCORR, ) corr /= known.sum()
Ứng viên đứng đầu:
textQR version: 4 Kích thước: 33 × 33 module Error correction: L Mask pattern: 4 Tọa độ góc trái: x = 61, y = 107 Correlation: khoảng 0,346
Vì version 4 có cạnh:
text21 + 4 × (4 - 1) = 33
nên ta crop vùng:
pythoncrop = image[107:140, 61:94]
Sau khi đã giải được nội dung, tôi sinh lại QR chuẩn với:
textPayload: bushbash{///hillside-errand-lateral} Version: 4 ECC: L Mask: 4
rồi so sánh với crop ban đầu.
Kết quả:
textTổng số module: 1089 Module phụ thuộc payload đã dò: 781 Khớp payload: 781/781 Module ổn định trong template: 308 Module ổn định bị sửa thành nhiễu: 101
Điểm quan trọng là toàn bộ 781 module phụ thuộc payload đều còn nguyên vẹn.
Phần bị phá chủ yếu là các module tạo nên “bộ khung” để scanner nhận diện QR. Do đó:
Ta ghi đè các module không phụ thuộc payload bằng giá trị chuẩn lấy từ template:
pythonrepaired = crop.copy() # target > 0 nghĩa là module trắng repaired[known] = (target[known] > 0).astype(np.uint8)
Sau đó thêm quiet zone bốn module:
pythonrepaired = np.pad( repaired, 4, constant_values=1, # màu trắng )
Và phóng to bằng nearest-neighbor để scanner dễ nhận diện:
pythonrendered = cv2.resize( repaired * 255, None, fx=10, fy=10, interpolation=cv2.INTER_NEAREST, )
Kết quả là QR hoàn chỉnh:
pythondetector = cv2.QRCodeDetector() decoded, points, straight = detector.detectAndDecode(rendered) print(decoded)
Kết quả:
textbushbash{///hillside-errand-lateral}
pythonfrom pyzbar.pyzbar import decode from PIL import Image print(decode(Image.open("recovered_qr.png"))[0].data)
Kết quả độc lập:
pythonb'bushbash{///hillside-errand-lateral}'
bashpython3 solve_bliss2.py bliss_2.png recovered_qr.png
Output:
textrank=1 version=4 ecc=L mask=4 x=61 y=107 score=0.346278 z=6.087 bushbash{///hillside-errand-lateral} saved=recovered_qr.png
Thử thách không giấu QR bằng LSB hay bằng một lớp nhiễu có thể loại bỏ bằng blur. Tác giả giữ nguyên phần payload đã mask nhưng phá các module chức năng giúp scanner đồng bộ và nhận diện QR—đặc biệt phù hợp với hint về timing.
Ta khôi phục các module không phụ thuộc payload, thêm quiet zone rồi giải mã QR bình thường.
textbushbash{///hillside-errand-lateral}