DEFCON 2023 Quals - kkkkklik

오래간만에 데프콘 CTF 온라인 예선에 참가했다. 문제가 정말 다양하게 나왔는데 ChatGPT를 활용한 문제도 있었고 Torque3D라는 게임엔진에 대한 문제도 있었다. 모든 문제는 깃헙에 소스코드가 공개돼있어 언제든 살펴볼 수 있다 (https://github.com/Nautilus-Institute/quals-2023).

내가 해결했던 kkkkklik은 윈도우즈 리버싱 문제인데, 바이너리 리버싱을 몇 년만에 해 보는지라 쉬운 문제임에도 하루 종일 걸렸지만 옛날 생각(?)이 나서 개인적으로 즐거웠다.

The click handler

kkkkklik.exe 프로그램을 구동하면 작은 창이 하나 뜨는데 특별히 입력창도 없고 클릭해도 반응이 없다. 그래서 바로 바이너리 분석을 시작했다.

GUI 프로그램이라서 main함수에서 따라가는 방식이 아니라 이벤트 핸들러를 발견하는 방식으로 분석해야 한다. 크고 작은 함수들 중에서 한 함수 안을 보면 수상하게 1337이라는 숫자가 있다. x32dbg에서 함수에 브레이크포인트를 걸어보면 GUI 창을 클릭했을 때 불리는 클릭 핸들러라는 점을 알 수 있다. 클릭 핸들러는 커다란 switch-case를 기준으로 세 부분으로 나뉘어있다.

klik_gui.png

  • case 1337
    • rtcInputBox("Please provide an encryption key:") 로 encryption key를 입력받는다
    • 입력받은 encryption key로 flag{this_is_a_fake_flag_find_the_real_one!} 를 암호화한다
    • 암호문을 base64 인코딩해서 rtcMsgBox("Encrypted result: ...") 로 보여준다.
  • case 133337
    • 알 수 없는 여러 숫자들을 가지고 외부 라이브러리 API를 호출한다
  • case 1333337
    • rtcMsgBox("Decrypt it to get your flag: jEJclmCsLkox48h7uChks6p/+Lo6XHquEPBbOJzC3+0Witqh+5EZ2D7Ed7KiAbJq") 를 출력한다.

실제로 해당 case문을 실행하기 위해서 x32dbg에서 분기문을 패치한 다음 GUI를 클릭하는 방법으로 디버깅했다.

풀이 과정은 다음과 같다. 먼저 case 1337에 있는 루틴을 분석해 암호화 알고리즘을 밝혀낸다. 내가 입력한 키로 데이터를 암호화해주므로 알고리즘을 이해하는 데 도움을 받을 수 있다. 그 다음 복호화 알고리즘을 코딩한다. 마지막으로 프로그램이 요구하는 암호문을 복호화하면 될 것이다.

Recreating the encryption

암호화 알고리즘을 분석하고 암호화 과정을 코드로 재현해서 내가 맞게 이해했는지 확인했다. 잔실수를 많이 해서 이 부분에서 가장 오랜 시간이 걸렸다.

Key scheduling과 Feistel round로 구성된 블록암호로 보이는데, key scheduling 과정에서는 임의 길이의 키를 받아서 각각 72B와 4096B짜리 테이블을 수정하고, Feistel round에서는 16라운드동안 8바이트 블록을 암호화한다. 디버거를 썼더니 대체로 분석이 어렵진 않았지만 딱 한가지 애를 먹은 부분이 있었다.

int sub_783A40(int a1) {
  if ( !dword_791038 )
  {
    dword_791038 = 0x424448B;
    dword_79103C = 0x8245C8B;
    dword_791040 = 0x539FB81;
    dword_791044 = 0xF0750000;
    dword_791048 = 0x18293566;
    dword_79104C = 0x70000035;
    dword_791050 = 0x10C2CC;
  }
  v1 = VarPtr(&dword_791038);
  v2 = do_CallWindowProcW_77C2C4(v1, a1, 1337, 0, 0);
  _vbaSetSystemError(v3, v4, v5, v6, v7);
  return v2;
}

데이터 영역에 코드를 쓰고 CallWindowProcW로 호출하는 트릭인데, 디스어셈블하면 고정된 상수로 xor하는 짧은 코드가 된다.

0:  8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]
4:  8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]
8:  81 fb 39 05 00 00       cmp    ebx,0x539
e:  75 f0                   jne    0x0
10: 66 35 29 18             xor    ax,0x1829
14: 35 00 00 70 cc          xor    eax,0xcc700000
19: c2 10 00                ret    0x10

Feistel round 부분은 상술한 xor이 귀찮게 곳곳에 들어있는 것 빼고는 평범한 모양이었다.

klik_cipher.png

파이썬으로 암호화 알고리즘을 똑같이 구현한 다음 프로그램이 출력해준 값과 일치하는지 확인했다.

Finding the decryption key

이제 복호화 키를 찾아야 하는데, 클릭 핸들러에서 분석을 미루었던 case 133337에 키가 있어야만 할 것 같았다. 그러나 이 부분을 실행해도 아무 일도 일어나지 않았고, 무슨 내용인지 파악이 안됐다.

디버깅을 통해 비주얼 베이직 dll을 호출한다는 점을 발견했고, msvbvm60.dll과 msvbvm60.pdb 파일을 다운받아서 (https://chentiangemalc.wordpress.com/2023/01/20/visual-basic-6-debugging-symbols-pdbs/) 다시 디버깅했더니 imethLINE() 함수를 호출한다는 것을 확인했다. float 좌표를 받아서 선을 그리는 함수같다.

klik_dll.png

바이너리 상에 나오는 0x4485C000같은 int32들을 float32로 변환한 다음에 (x1,y1,x2,y2) 좌표로 해석하고 그리면 된다. 뭘로 그릴까 하다가 HTML5 Canvas 샘플코드(https://www.javascripttutorial.net/web-apis/javascript-draw-line/)를 고쳐서 얼른 그려보았다. 8글자의 암호화 키가 나왔다.

klik_dll.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JavaScript - Drawing a Line</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <h1>JavaScript - Drawing a Line</h1>
    <canvas id="canvas" height="400" width="500">
    </canvas>
<script>
function line(ctx, x1, y1, x2, y2) {
    ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
}

function draw() {
    const canvas = document.querySelector('#canvas');
    if (!canvas.getContext) {
        return;
    }
    const ctx = canvas.getContext('2d');

    // set line stroke and line width
    ctx.strokeStyle = 'red';
    ctx.lineWidth = 5;

    // draw a red line
  line(ctx, 40, 40,  30, 70);
  line(ctx, 40, 40,  50, 70);
  line(ctx, 35, 55,  45, 55);
  line(ctx, 60, 40,  60, 70);
  line(ctx, 60, 55,  80, 40);
  line(ctx, 60, 55,  80, 70);
  line(ctx, 95, 40,  85, 70);
  line(ctx, 95, 40, 105, 70);
  line(ctx, 90, 55, 100, 55);
  line(ctx,115, 70, 120, 40);
  line(ctx,120, 40, 125, 70);
  line(ctx,125, 70, 130, 40);
  line(ctx,130, 40, 135, 70);
  line(ctx,155, 40, 155, 70);
  line(ctx,152, 43, 155, 40);
  line(ctx,150, 70, 160, 70);
  line(ctx,180, 40, 200, 47);
  line(ctx,200, 47, 180, 54);
  line(ctx,180, 54, 200, 61);
  line(ctx,200, 61, 180, 70);
  line(ctx,210, 40, 230, 47);
  line(ctx,230, 47, 210, 54);
  line(ctx,210, 54, 230, 61);
  line(ctx,230, 61, 210, 70);
  line(ctx,240, 40, 260, 40);
  line(ctx,260, 40, 240, 70);
}
draw();
</script>
</body>
</html>

Decryption algorithm

Feistel round 부분을 역순으로 계산하면 어렵지 않게 복호화 알고리즘을 완성할 수 있다.

#!/usr/bin/env python3
import base64
import binascii
import struct

def stringify(arr):
    return ''.join(map(chr, arr))

def hexlify(arr):
    return binascii.hexlify(bytes(arr))

def hexnums(nums):
    return ' '.join(map(lambda n: "%08x" % n, nums))

def unhex(dump):
    return list(binascii.unhexlify(dump.replace('\n','').replace(' ','')))

def readnums(arr):
    assert len(arr) % 4 == 0
    L = len(arr)//4
    nums = [0]*L
    for i in range(L):
        nums[i] = struct.unpack("<I", bytes(arr[i*4 : i*4+4]))[0]
    return nums

def readnumsBE(arr):
    assert len(arr) % 4 == 0
    L = len(arr)//4
    nums = [0]*L
    for i in range(L):
        nums[i] = struct.unpack(">I", bytes(arr[i*4 : i*4+4]))[0]
    return nums


def writenums(nums):
    arr = b''
    for num in nums:
        arr += struct.pack("<I", num)
    return arr

T1024 = unhex('''
8F13411DC0680A8741424D251ED649F685ADAF546D31C379DE58F158E6ED8A1FF26A8DE307110517353E3C3A1E6FB2679EC76A1C0A3E69081D3119583235B596
C4B7917499BE1E61DE38658DB77FC090BF6656A654C7D785DECC72BA6B2FD3836C880C7691789E5007738470693FF21FB0675C3D4FAA9D434118D2189783CC55
6E81D1E85894DA20583C7818B4966119DE74E17FD60FEAA543EC50FF3C6B7F73CBEA71C4454A149A9ECCC78F57045D1A3FE4FE49C886C10E8679709C52DC700B
F13819AF8C1A46D507EE49D24203FC7B405627BD005479B96F5D545B6C88D1ED8AE62868690B296C5D5751D897A91E7E5725E338172268286990FB739D7646A6
A66CE5C1B38024F334E4E58106B3389B71AEFEBE4C8532978689C55A5076E47071D5FBBDFFFCFFA7FAC584BCFB6ED30AC752654EFF2787556C37D0AAE1DA39A9
34BC24B72E84A26DC511CC73C7E07F9F9C412A0EDC289823AC8FCDCF54C6FD8A10CD4050CF205D81F975DCB3341203193A7882E6E845553C2D9DBBFDEF55A080
0AA8A109AF38AD809A3F9B5AF2A349E5D99D10E40FF3004868218D99795ECA6531613106EFF1F2AFCF5F5516C13EE560C620AB7477D46ECEB312BA672DFB2E72
99C40942167318C50C6020E4D9CD8A6D27004AACE0F7CAF2DD31749FB3495DA6A216EEA03D00E7F0F39E5CC6CB949FAF17926E7C88681AA7D275C6250BF6F656
E86F651BAD2D0FA44B0CACA491DAF90C0E534171AFFAD09E2971381BDF36548FF337DFB42C4BEC7B8DD87EA4831B6E69494410991E1F2066C795D1EB8DC88250
DA3D252A359C77F28BE64F839379B04FBDB3256675B6AEB3A5B5F724647299574B80389BC55C0D422FF8FC79790D9543690C98AF91EA669B9FCE84B6FF431476
4321BA991EC24A7C5506BE66D0BA56E49F08DBE62414203CC547471FC8224A6B1D44BC782D076C3CB0BB0802AF8DD987E7F031DDD6AB70CE6B321B8CC07A2523
869E246D33ED7C621C868EECFAF75F0BBAF102B09B6CC5F0909D8315F3EF223B380C9E7F7162F3E982CF4922407774F303A41FAF94397910A25662F770128ABB
74DDD9E7D00BE11DDEE2B9D13CB1944CDF2968B8DF37D9B07F001D87289EC04B3F262C025A5F4258187ED3EA84FE7957378BF757285F85EE9B8F9326BAFD4EF7
1AA2A663A8FD95F65DE21EF673E5E02575D754A0F5C2B2FB1B5B2B11BECF4452A84B42B61D6EC504DEFF31A4F0AF80E05E9EE5E48EC58356D238080678935BCE
B150FFF76F79346567ED7A3713B4A55A86A13BA7271BA0C3BEAB8E1454BE0DCD32F0CF0817DFB820854E3089FF26BF1DBA3958AA680605680E8D387601350DB0
E511A8ADB0D5482E13222399E63DEFD3B8B1513706169AF7AE95F3ECB2A0826149B40C8488A3F0FE9EB11B325BACA6961B989C9118AB4ED4628DE61C65EDF896
7445F42311932482957FD89958B4592C986DF52521A11D837182656DCFBD692C2B3B56102A1532A34A31D900D4B4C08BA1031527961C7A3A1AC39155B2E2E321
A826F9EFB90AC8E07F525A6AA4DCA324ECB4E61F5064E7E8D03D41F3E54F4BE4DA771DC35BA8099A35668492007EA534105A844F8691DF705529595C013663B5
AB5C7BE2366FEA122BF08831B8192FB42D38F4683910E315593757C87C78052163E8B8A53BB6FB7F750DCB4C6D16E63B77836F520727BF10CA3458C9A546A32F
6B70B6ED366A6299610DB159FD7575D9B374993A0D691BE20B75B6289375844448847CABCFC56A9C160BB1840C79D1CFD990A367AED5F453F59E7F0B94E814C9
FBB821A6315F28B6C7D189CB3C869B0F413724143EC278B82607748D8B4FE0F001BF7F5A9582EF708D61378CC502575B8A2B2167A5653B253E76F891031F4A65
45139FA213F40A20C2492FFEB2754FD7CD230ADFD305F517F8D8EB19DC3913D279E84B764F5B79AFA6D9CC3ED27EEC39B1328BB2FBDB14084D2D618D30EBACEA
347D816D6E006C231D600BE901C143B95F19DFF521C165FE49845AACDCE5257D1741BAAA1E2333118AF08813AB2C26CFA116334E3FA2B2E8327413D39224CA46
309E9E406A55D1DEEBAC62C2386F21E49D871F8978DC15E6B72A91CED1C17A0EEABDF4B12B18E49CF85716634E49BC679746FBF7F4FC4ADF3C99A106768ADD00
F16D1F2CB7E0AFBDC98D53A7780F98815A38B1497C5641DCC88A4EFFA7C440F4B65C6A8CFF6FDC4D4B1354F74B40EDFB8F72B19AB20161930BA1CE22B8E1505F
4BB2A382D84E45C827BAC249EB880A262F6F4FFA42DFD31BB015CA2AE7634E375BC78ED7120061F0A51402124DD6519D141AEB8E20BD549501EFD2E11BA63FBB
0DCFA0FBC4FEFF3E6C60621C57FBC664610A7A1CD3E3815BD48CC7596F25590FFAF27F1705A7CA524B100DA8404BAE84B2D88185472465D2D9EDBC2BA9FE63A8
E06A23CB595D934A46BB39983910DE6E5203E94C98779926D3500D4B0DAA1D11F161A4E923467E4A0EE5ED0FD435F5A5DEC6983A9A324E9637954E3F4F3977C5
33488E2F35FF6FBB687B37C6235EEA7F1254097AD31E4D825DE75E55F4D835A894F81C5B90C415E582761FF6E6C61C94931EB0C8260597551EE5883887D050D0
9F57D90DFF914E4C49C46264F4EFCE97ED78EF8C0CD0169ED1C59B6D699528D7EB862C9251D13CE265F96B555619A2004A3C1AD543AB605024731E1792FBC4A7
86778BA49316650A394D0B0A5772D2119C4B1CF251F292581E3417A16CE729F6C2AA49DF7A248C6912CC15EB6D1245F246F422F7DD357AD22DF0A010FCD5C470
36498CA18E56873EEE15593D81D69ABE058D405714336DFA8AE77000929C14366D5DF100263E49D5BB174979870A164120A52E634961B2D522F57FA56E774C73
2DC8937221BF539ED2870BAA4AFCEB1ED45243129F0A633BB565AB02B7455F982E307FAA47E6DD2722D7E16C326FB2629A535ED54F07B3268A466515597B3E3A
7EB0BB0CBC5DCC2FA1376377A4157EB8266CB889AAD00B6A0DB52B9D7E0B2B2B10477B1EF82F0F7D9661E4B7580E0234F2E3A37501E7FCCDC2CE4BBA74652363
94D80999C6C5420F9A3649FB21D3308C232A10D68C421C7250416100E5FAC482EF18D11AAC3928A9BEFA564C435EA2F8506A5C8C2B80DBA404295E38ADB765CD
D73DEFAB26BDBE228EB532A4011CC02DE5BB6F37128D5F1712331A0A3422E859D1F1D54284659FE6E55405DE9D87C8CAD13A4217AD371E9735E95EB461B81E02
F60E05F001AE51D91E5A62A6AB234FA33C731131597977E5CE49E27BABB350F9E10620E35C5FAD20CFA3D1CA62056ACD824A7561390DEFAD797B8B87D13F02EB
D3AD4DFE19B0BCDF31081BD6987865AD499F5331BFA51127D3F5BADDF527E32B6163419F37E644CFF1A555F102614A77ABC770F2E67B7366E0DB912E943D35F8
924F2C52B9440379700E3288C821F86C89941F0610BA0080AF0B62C66261BE9D074EF7D62286EE1947F47C159ED142E3F27167133DC6DA0703F2DB19E0A26F6C
DFB03219959EBC22677FDFA857D06C2CEAE70EE48E3412AC76B0F616DFC9B770EF2A17608244DB50A1F1CF72EA0971035A4D3F40479C837ED7DB94A8EEB2986D
993F2BA58606FBA87E98CC51609FE0D6E140BA77E3E8CDD5AFD8873CB3A53F1874BB8F2D9071536CD16308ACE2C6AA1C8909807479A32AA9647873AC11C27A19
B1258ADC1B42188C6F9B8D1D03DB49CF919B51319AAC5AF09907483A4E2EE10A45AD8C86FCF1EEFD2DB635BB5529894172CBA1E1DEA0510CE5E4461B6633C12C
50FC2356301324571A73324F9E41EE3B4C5D887AB0B82F4B58B36E3C13A3858F9551FE1E57818759AE59F07CD601A53EB98F8B8781654DAE764670F0B55DA9EB
F3EAAD2DB3904734974FD0BB053AE7731A66BB685E35935B0DB6987103E496D9680B8BAE768B9DDDB05A369958E4E1C3C1DE9402A80A18DA483628730C0DE457
F3D25023009045C2A6EC2882488B9536285407FAFF07960B8BE5AD3EC284C602D7860E1C88C7AE5A11F70438707CD80E9D0781E7B0A228B4EBA5F94BF8B0CADE
64C2AB598C9C859BEAE1169F771FB17AB189E0624A6A52D75D96C3042572752F5896DD26D6DBF3577CEA05784C48A2DC89CDE3A7BF5EB2D690C18C8A6BBC7307
F9C9FE1CC212C301483E9BB627769C2CC93DB7637D285E9FADC76D4712C3E8DA06434C42CD50A94350161A4897B8E8809E8C05420129CCA1CB8D2F5D4DF108FE
D2FA8643C6EA9B94A7411E8A1B8D6F534D33623EC3E7B6F8594FC4ECF68AA32C3BA0F84448F55832B84DA540022CD01F35E87D5C5A240C2265C6720537EA0145
8946DD83F00C3A91C8B47B75686C7AD735DBFFA4CAAF1424F91DF277A52CD387B8E9BF1D3D45608E617AD8DD0969CE0984D9D87FC90B4EECB7B104B9F12A060F
313A5FE39FFA9E899F010F7BA48745135E0F7E72C3B3DA6F20C4D92C0737E957D73505263C571C1788115DAA6E777B2A88077247F957BB361A5E420834E993C3
26D4D0296BEC320B2B072A247DC23C29C16C1F799CA31A23A5A680C5B8C0AAD2FFEBDCD434233FA90CB8E986E6611202B0FAF9022C39BD8D39E61ED146664E01
C957D878B7616E143425C9D64FA968DA9EF86331EE55F54AF6BCD5C72C055C31A823B4B0435F3B2826EAF66DECCAFF48F0B0DD1E797AF1F140E918E4B03A8B3A
4FBA2FDADBB91203AAC2C7107EEB53392C6FE54C6F3EFD97D71E499B0A6E426A3D6BBC5F899BF830B2D6926D182DD85F5E0C6AED8AAEB70D7B67BD832BD5BC9A
4C38DD2AEA3C65B32846619C4B998060AFE2C5BBBB6CBBA5D39B766B9CF30596DC5A240B2292F48BEDAD726CBE2E66A2E62DED37ACAAE29A0EC896C1E56BA244
25B7BD2729A72BC50E948856BB7AE61289914EB7B4506961689E4FBBF951C94DFA03311A5DA912D82F54100F3288208060666E622916F2EF9C1ED8AD3D4EB6BD
041655CC039532940162673C94DFB62A77AB01ECC3ED25C0C99E850C230C0AFE921818EE17ECDDD1834010CC2FC8918986F8C89B48684FEF4B65ACFCB363820F
B22E14E8BBE802FFFE8696DDD44BDA0537A1793C6866E3414AF248EF2917D8AE34891399D8F42F1ABDC5B29FCBA7557783BEAF95F22352A01D0EB20EDFCACDF9
A05BB1B4702FAEB07FF6BB772C7162BD564B2A15496C9E07F7AECC5C0B1A747E8B430DEC8EEAF58C88658C2755D7BB7AECA195CE472A07025F05290202840601
5F1B564FAD98776A20FC75A3E926619F80D7E5AEB74888D5A1190C87FAFB30DA4101B8DD7CC09F24141202F549A5DBF4685203821C8FA9AD0D64E2B0D9B537E9
E335377F83BF1965766A934AB538487663B164B7EB1E7C0990853DBE5FD6363B7B1821D7D5B374969D43B1D6ECB9DFBB3C312356F5D27B4CD5A0EE1F497805EC
164F7F1A07623452514D242167E6BB49CDDEEB70AD2C350F9CBD8CC4F19598465FBC10E72C7FA531FA644D1499E1DAB6296C964DE0866EC2ED17DD8157B28980
9C77CAC4FAC3031777F720D275DA38D536F16B9BE49025DCD1FE117DA5928BCE42F4E63E50C22FABF00CF56ECD72B3CD3CC17DE6695B172F150B21A0D0F99B1A
087D137A4C2CB409CEDFA5A340E0A45C9FE1C92BF1204EBDED56919A89C62C6A071D4433B7E058F1E7A75AFA043D79F34D4EF50909E71D3D1ED0B611B6FE780E
7435C09FCE394ED91D2AEA1B1B793E7B8897EF656325C0433B9A135E72FA0702B05FCAC40287932AA7E27EABCAC7FF9B431FF5A2DEB5F317C918108CCF6AB3F6
ABABABABABABABAB0000000000000000''')[:4096]
N256 = readnums(T1024)

T18 = unhex('''
A1 72 4F E8 FA 10 D3 49 07 92 69 DF 6D 6B 00 CF
0B 20 79 68 F9 29 EF E5 B1 E2 5E C4 A0 74 3E 20
CF 39 58 89 5E 0B A0 F4 E6 7E 24 72 45 14 99 F8
9E 31 DC 0C F4 48 0C 05 9C CD F4 F3 3E 11 37 79
F0 CD 66 5E 32 E3 09 45''')[:18*4]

ENC = list(base64.b64decode('''
VBlspNcGguJ7NRfoYHEeC3/U/bFq6cAZR2NYB3vrOfQKM7/SOqzlng==
'''))

FLAGENC = list(base64.b64decode('''
jEJclmCsLkox48h7uChks6p/+Lo6XHquEPBbOJzC3+0Witqh+5EZ2D7Ed7KiAbJq
'''))

def sxor_key(buf, key): # first part of 783ca0
    longkey = list(key*100)
    for i in range(0, len(longkey), 4):
        longkey[i:i+4] = longkey[i:i+4][::-1]

    out = [0]*len(buf)
    for i in range(len(buf)):
        out[i] = buf[i] ^ longkey[i]
    return out

def xorconst(n): # 783a40. jump to dynamic assembly at 791038
    return n ^ 0xcc701829

def box(n): # 783ac0
    a = (n >> 24) & 0xff
    b = (n >> 16) & 0xff
    c = (n >>  8) & 0xff
    d = (n     ) & 0xff

    aa = xorconst(N256[4*a+0]) # 1fe6b4ec
    bb = xorconst(N256[4*b+1])
    cc = xorconst(N256[4*c+2])
    dd = xorconst(N256[4*d+3])

    y = (aa + bb) & 0xffffffff
    y = y ^ cc
    y = (y + dd) & 0xffffffff
    return y

def round(keysched,L,R): # 783bc0
    for i in range(16):
        L ^= xorconst(keysched[i])
        v4 = box(L)
        v5 = L
        L = v4 ^ R
        R = v5

    oL, oR = L, R
    R = oL ^ xorconst(keysched[16])
    L = oR ^ xorconst(keysched[17])
    return L,R

def unround(keysched,L,R):
    L16, R16 = L, R
    nL = R16 ^ xorconst(keysched[16])
    nR = L16 ^ xorconst(keysched[17])

    for i in range(15,-1,-1):
        oL = nR ^ xorconst(keysched[i])
        b = box(oL ^ xorconst(keysched[i]))
        oR = nL ^ b
        nL, nR = oL, oR
    return oL, oR

def decrypt():
    key = b"abcdefghijklmnopqrstuvwxyz"
    key = b"AKAM1337"
    keyxor = sxor_key(T18, key)
    keysched = readnums(keyxor)

    L, R = 0, 0
    for i in range(0, 18, 2):
        L, R = round(keysched, L, R)
        keysched[i] = xorconst(L)
        keysched[i+1] = xorconst(R)

    for j in range(4):
        for k in range(0,256,2):
            L, R = round(keysched, L, R)
            N256[j + 4*k] = xorconst(L)
            N256[j + 4*(k+1)] = xorconst(R)

    print(hexlify(FLAGENC))
    blocks = readnumsBE(FLAGENC)

    output = b''
    while len(blocks):
        L, R = blocks[:2]
        blocks = blocks[2:]

        L, R = unround(keysched, L, R)
        output += writenums([R,L])[::-1]
    print(output)

decrypt()

플래그를 얻었다.

flag{vb6_and_blowfish_fun_from_the_old_days}

Other posts (list)


Docker Buildkit 으로 빌드 시간 단축하기
Deleting Azure "dangling" role assignments
DEFCON 2023 Quals - kkkkklik