http://overthewire.org/wargames/semtex/
自顺利解决natas之后,踏上新的征程,semtex在向我招手。
Semtex 0:
题目的要求很简单,连接到一个服务器的特定端口,接收发来的数据。其中奇数字节可以组成一个可执行程序,偶数字节是逗你玩的。基本的socket网络编程。
#!/usr/bin/env python
import socket
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
f = file("ex","w")
if conn:
conn.connect(("semtex.labs.overthewire.org", 24001))
while True:
x = conn.recv(1)
if not x:
break
f.write(x)
conn.recv(1)
f.close()
运行这个可执行程序即可得到密码。
Semtex 1:
/semtex/semtex1是一个它提供给你的加密程序。只能执行无法scp到本地或者逆向。于是就相当于是进行选择明文攻击。提供给我们了的是口令的加密结果,我们需要做的就是破解它。
输入“A”×13,逐个改变字母,发现明文中每个字母的改变只会引起密文中一个字母的改变,而且是固定位置的一个字母。于是只要首先确定明文与密文的一一映射关系,再逐个枚举就好了。
确定映射这一步我是手动完成的,而枚举这一步则是跑程序。
#!/usr/bin/env python
import string
import subprocess
encrypted="HRXDZNWEAWWCP"
a=[11,8,13,10,3,12,5,2,7,4,9,6,1]
a=[x-1 for x in a ]
print a
#a=[0,1,2,3,4,5,6,7,8,9,10,11,12]
s="AAAAAAAAAAAAA"
for i in range(0,13):
print "enumrating char %d in %s" % (i,s)
for c in string.ascii_uppercase:
s2=s[:i]+c+s[i+1:]
print "\t testing %s" %s2
#ec = subprocess.Popen("./encode.py %s"%s2,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
ec = subprocess.Popen("/semtex/semtex1 %s" %s2,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
s3,s4= ec.communicate()
s3=s3.split()[4]
print "\t encode as %s" %s3
#print s3[i],encrypted[a[i]]
if s3[a[i]]==encrypted[a[i]]:
s=s[:i]+c+s[i+1:]
print "Ans changed to %s " %s
break
最后一个输出字符串即是我们需要的口令。
Semtex 2:
题目要求很古怪,它会check我们的uid,以确定是否是devils number。运行/semtex/semtex2的结果是当前uid为6002,与id命令的返回结果相同。google了一下,devils number指的是666。
在有suid权限的可执行程序上改变euid倒是见过,可这个程序是怎么回事?用strace查看了一下它的系统调用,略去其它无关紧要的部分,可以看到它调用了geteuid32(),也就是32位环境的geteuid() syscall。
没有hack这样一个简简单单看起来刚正朴实的程序的经验,看到它给的参考文献是"link",也就是生成可执行程序的“链接”步骤。开始朝这个方向动脑筋查资料。
查到了这样一篇文章,切中要害,一下子解决了问题。懒得看的同学我可以解释一下:unix下运行程序之前会先载入LD_PRELOAD环境变量指定的文件。而我们可以通过自己写的程序重载libc.so.6当中的geteuid()函数,从而达成hack euid的目的。
#include<unistd.h>
#include<sys/types.h>
uid_t geteuid(void){
return 666;
}
这是用于重载的代码。
gcc -shared -o hack.so hack.c -m32
<pre name="code" class="plain">export LD_PRELOAD=./hack.so
执行这条命令生成.so文件,并设定LD_PRELOAD环境变量。别忘了编译时的-m32选项,若是一不小心编译成了64位的.so文件,载入时会出错。
接下来再运行/semtex/semtex2即可:
EUID == 666The devils number is 666, the devils password is jJjl2Msl
Semtex 3:
第三题,据(题目)说期望的解决方法是用数学方法分析八种数字的增减怎么组合才能到达我们要的结果。可惜的是我一向不觉得自己数学好,那就只好上暴力手段解决了。
一开始我觉得是搜索,觉得实现倒是不难但我对剪枝也不是很在行,而且这道题看起来也不太像能剪枝的样子;然后我觉得是背包,想了想原理上是没错的,但大小起码有10G的数组大概在超算上才能开出来。到这里我已经完全陷入了做oi,acm题目的思路了。
直到,我看到了另一个人处理这道题的方法,真是简单粗暴……直接上随机,反正可以想见次数不会太多。我们假设总共需要20次操作,那么期望需要尝试……我也不知道是多少次,反正经实测一般能在100w次以内跑出来,加起来不到1s。关于这个期望的问题,概率论和组合数学学的好的同学们可以去算一算。
#include<iostream>
#include<cstdlib>
#include<memory.h>
#include<algorithm>
using namespace std;
int keys[8][5] = {
{5, 2, 1, 7, 5}, // 1
{13, -7, -4, 1, 5}, // 2
{ 9, 12, 9, 70, -4}, // 3
{-11, 9, 0, 5, -13}, // 4
{4, 17, 12, 9, 24}, // 5
{11, -17, 21, 5, 14}, // 6
{15, 31, 22, -12, 3}, // 7
{19, -12, 4, 3, -7} //8
};
const int MAX_STEPS= 256;
void apply(int lock[],int x){
for (int i=0;i<5;++i){
lock[i]+=keys[x][i];
}
}
int check(int lock[]){
int ret = 0;
for (int i=0;i<5;++i){
if (lock[i]!=400) ret--;
if (lock[i]<-1000||lock[i]>1000) ret-=10;
}
return ret;
}
void reset(int lock[]){
for (int i=0;i<5;++i) lock[i]=300;
}
int main(){
int tries=0;
srand(time(NULL));
char step[MAX_STEPS];
int steps=0;
int lock[5]={300,300,300,300,300};
do{
tries++;
int x=rand()%8;
step[steps++] = char(x+1+0x30);
apply(lock,x);
int ck = check(lock);
if (steps>=MAX_STEPS|| ck<=-10){
reset(lock);
steps=0;
memset(step,0,sizeof(step));
}
}while(check(lock)<0);
printf("%d times tried\n",tries);
sort(step,step+steps);
printf("[%03d %03d %03d %03d %03d] = %s\n",lock[0],lock[1],lock[2],lock[3],lock[4],step);
}
突然发现,我太久没写c++,又写成那年学oi那会儿的c/c++混编风格了,看起来很丑,见谅。
Semtex 4:
与第二题类似,给了一个调用geteuid的程序,需要我们对其结果进行hack。只不过区别是上一次要hack成666,这一次要hack成6005。我没有尝试LD_PRELOAD环境变量,估计是不可能成功的(其实我应该试一试的,也算是体验一下这个BUG是如何防御的)。根据题目下方提示,这次要用ptrace来解决。
我花了很长时间才明白过来ptrace究竟是个什么东西,充分反映了我linux的半调子水平。这是一个用于调试其他程序的系统调用,我们常用的strace,gdb命令都是用它来写成。具体可查看manpage或者网上的介绍,相关很多,不过没找到特别一阵见血的。我们的目的就是通过ptrace捕捉到geteuid的调用,并将其返回值hack为6005。
#include<sys/ptrace.h>
#include<sys/syscall.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdio.h>
#include<sys/reg.h>
int main(){
pid_t pid=fork();
if (pid!=0){
int val;
long flag=0,returnval;
wait(&val);
if (WIFEXITED(val)){
printf("ptrace aborted\n");
return 0;
}
long syscallID = ptrace(PTRACE_PEEKUSER, pid ,ORIG_EAX*4, NULL);
/*printf("Process executed system call ID = %ld\n",syscallID);*/
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
while(1){
wait(&val);
if (WIFEXITED(val)){
printf("aborted!\n");
return 0;
}
if (!flag){
syscallID=ptrace(PTRACE_PEEKUSER,pid,ORIG_EAX*4,NULL);
/*printf("Process executed system call ID = %ld\n",syscallID);*/
flag=1;
} else{
if (syscallID==201)
returnval=ptrace(PTRACE_POKEUSER,pid,EAX*4,6005);
/*returnval=ptrace(PTRACE_PEEKUSER,pid,EAX*4,NULL);*/
/*printf("returned with value= %ld\n",returnval);*/
flag=0;
}
ptrace(PTRACE_SYSCALL,pid,NULL,NULL);
}
}
else{
ptrace(PTRACE_TRACEME,0,NULL,NULL);
char* argv[]={
(char*)"./test",
(char*)0
};
execve("./test",argv,NULL);
/*char* argv[]={*/
/*(char*)"/semtex/semtex4",*/
/*(char*)0*/
/*};*/
/*execve("/semtex/semtex4",argv,NULL);*/
}
return 0;
}
这个程序只能在-m32下编译通过,大概是因为64位环境不使用EAX宏而是使用RAX宏。
Semtex 5:
题目要求是通过十个不同的ip在短时间内同时连接本地某个端口,收到发回的字符串,与手头的密码逐字符抑或后加上标识符发回去,成功的话会从随机一个连接内收到发回的密码。
第一个点在于:哪里去找十个不同的ip。方法有很多,题目的下方给的提示是sock5代理。当然实测各种免费的sock5代理并不是很好用,哪怕是大半夜也总有很多人连接在上面,想要凑出十个能用的代理有一定难度,而且麻烦。另一个方法是利用AWS或者国内的云服务。第三个也是最简单的方法是:利用bind函数绑定到127.0.0.x的ip上,轻松凑出十个ip。
第二个点在于:各个连接要在短时间内同时连接,这可以通过多线程并发来实现。
其它就没什么了,只要搞清楚数据包发送和接收的各种字符串与byte格式的弯弯绕就好。
#!/usr/bin/env python
import socket
import threading
socket.setdefaulttimeout(100)
password = "HELICOTRMA"
target=("127.0.0.1",24027)
class conn(threading.Thread):
def __init__(self,num,address):
threading.Thread.__init__(self)
self.thread_num = num
self.address = address
def run(self):
global target
global password
s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(self.address)
s.connect(target)
print "thread %d connected" % self.thread_num
t1 = s.recv(10)
print "thread %d received %s" %(self.thread_num,t1)
t2=""
for i in range(10):
t2+= chr(ord(password[i]) ^ ord(t1[i]))
s.send(t2+"winkarwink")
print "thread %d sended %s" % (self.thread_num,repr(t2+"winkarwink"))
import time
time.sleep(5)
data = s.recv(256)
print repr(data)
for i in range(10,20):
a = conn(i-10,("127.0.0.%d"%i,24028))
a.start()
将它在服务器上运行,就能在某一个线程上收到想要的结果。
Semtex 6:
我很想说,我知道这道题怎么做。但是题目里出现的home目录下的额外提示没有出现,所以我也没能知道该怎么样才能在伪造ip成功的情况下获得下一关卡的密码。查找这道题目的结果是:在payload中附上自己的irc id,然后在登陆到irc.pulltheplug.org的时候会收到私信……然而我并不是从这个地方找到的wargame,而我也没有找到其它的办法,所以我想,这套题的进度只能到这里了。我感到很悲伤。如果有人知道怎么进一步,请一定告诉我。